{"html_url": "https://github.com/simonw/datasette/issues/785#issuecomment-636553736", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/785", "id": 636553736, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU1MzczNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T00:18:40Z", "updated_at": "2020-06-01T00:18:40Z", "author_association": "OWNER", "body": "That documentation: https://github.com/simonw/datasette/blob/c818de88a9c2683437875f788e325d911c8b767b/docs/config.rst#configuring-the-secret", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628025100, "label": "Datasette secret mechanism - initially for signed cookies"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/784#issuecomment-636554258", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/784", "id": 636554258, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU1NDI1OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T00:21:33Z", "updated_at": "2020-06-01T00:21:33Z", "author_association": "OWNER", "body": "The URL for this will be:\r\n\r\n`/-/auth-token?token=xxx`\r\n\r\nThe token will be generated by Datasette on startup and will only be valid for a single request, at which point it will be used to set a signed `ds_actor` cookie and then redirect to the homepage.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628003707, "label": "Ability to sign in to Datasette as a root account"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636562658", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636562658, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2MjY1OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:08:20Z", "updated_at": "2020-06-01T01:08:54Z", "author_association": "OWNER", "body": "OK, the implementation in PR #783 is in a good state now - it implements the new plugin hooks with tests and documentation, plus it implements this:\r\n\r\n $ datasette . --root\r\n http://127.0.0.1:8001/-/auth-token?token=3ca9ee460a6451142389351d19b147bce27d2a785dfb6b5a74f82211be1ede49\r\n ...\r\n\r\nThat URL, when clicked, will set a cookie for the `{\"id\": \"root\"}` user. The cookie is respected and used to populate `scope[\"actor\"]`.\r\n\r\nI'm going to merge that pull request and continue working on this stuff on master.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636562999", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636562999, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2Mjk5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:09:47Z", "updated_at": "2020-06-01T01:09:47Z", "author_association": "OWNER", "body": "I should add an entire page to the documentation describing Datasette authentication.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/784#issuecomment-636565242", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/784", "id": 636565242, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2NTI0Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:18:20Z", "updated_at": "2020-06-01T01:18:20Z", "author_association": "OWNER", "body": "I'm considering this done. I'm going to leave it to plugins to implement a web-based sign-in flow for accounts (at least for the moment).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628003707, "label": "Ability to sign in to Datasette as a root account"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636565610", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636565610, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2NTYxMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:19:45Z", "updated_at": "2020-06-01T01:19:45Z", "author_association": "OWNER", "body": "I rebased in #783 so all of this is on master now.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636566433", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636566433, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2NjQzMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:22:59Z", "updated_at": "2020-06-01T01:22:59Z", "author_association": "OWNER", "body": "Some next steps:\r\n\r\n- Try out a branch of `datasette-auth-github` that builds on these new plugin hooks\r\n- Build a `datasette-api-tokens` plugin which implements `Authorization: bearer xxx` token support for API access\r\n- Maybe prototype up a `datasette-user-accounts` plugin which supports username/password accounts and allows an admin user to create/delete them\r\n- Do more work on writable canned queries in #698 and see what they look like if they take advantage of the permissions hook (to restrict some to only allowing authenticated users)", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636566616", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636566616, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2NjYxNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:23:48Z", "updated_at": "2020-06-01T01:23:48Z", "author_association": "OWNER", "body": "https://latest.datasette.io/-/actor is now live (it returns `null` because there's no current way to sign into the `latest.datasette.io` site - not even with a fake `ds_actor` cookie because there's no way to know what that site's random secret is).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/698#issuecomment-636569917", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/698", "id": 636569917, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU2OTkxNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T01:39:44Z", "updated_at": "2020-06-01T01:39:44Z", "author_association": "OWNER", "body": "Idea for the authentication piece: I'll have the canned query code execute the following:\r\n\r\n```python\r\nif await datasette.permission_allowed(\r\n request.scope.get(\"actor\"), \"execute_query\", \"canned_query\", query_name, default=True\r\n):\r\n```\r\nThen I'll add a default plugin to Datasette which implements that plugin hook, looks at the Datasette metadata for that query, and says \"No\" if the following (and `request.scope[\"actor\"]` is empty):\r\n\r\n```json\r\n{\r\n \"databases\": {\r\n \"my-database\": {\r\n \"queries\": {\r\n \"add_twitter_handle\": {\r\n \"sql\": \"insert into twitter_handles (username) values (:username)\",\r\n \"write\": true,\r\n \"requires_actor\": true\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\nI think I'll support this too:\r\n\r\n```json\r\n \"allowed_actors\": [\"root\"]\r\n```\r\nSo you can configure queries to only be available to specific `{\"id\": xxx}` actors.\r\n\r\nThis will be the first time the new `permission_allowed` mechanism from #699 will be exercised in Datasette core.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582517965, "label": "Ability for a canned query to write to the database"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636576252", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636576252, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU3NjI1Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T02:11:40Z", "updated_at": "2020-06-01T02:11:40Z", "author_association": "OWNER", "body": "Plugin idea: `datasette-allow-all` - really simple plugin which just says \"yes\" to every permission check.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636576603", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636576603, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU3NjYwMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T02:13:26Z", "updated_at": "2020-06-01T03:13:31Z", "author_association": "OWNER", "body": "Debugging tool idea: `/-/permissions` page which shows you the actor and lets you type in the strings for `action`, `resource_type` and `resource_identifier` - then shows you EVERY plugin hook that would have executed and what it would have said, plus when the chain would have terminated.\r\n\r\nBonus: if you're logged in as the `root` user (or a user that matches some kind of permission check, maybe a check for `permissions_debug`) you get to see a rolling log of the last 30 permission checks and what the results were across the whole of Datasette. This should make figuring out permissions policies a whole lot easier.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/788#issuecomment-636598949", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/788", "id": 636598949, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjU5ODk0OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T03:53:00Z", "updated_at": "2020-06-01T03:53:00Z", "author_association": "OWNER", "body": "I can use a deque with a max length for this: https://docs.python.org/3/library/collections.html#deque-objects", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628121234, "label": " /-/permissions debugging tool"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/786#issuecomment-636614062", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/786", "id": 636614062, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjYxNDA2Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T05:02:18Z", "updated_at": "2020-06-01T05:02:18Z", "author_association": "OWNER", "body": "The skeleton of this page now exists at https://datasette.readthedocs.io/en/latest/authentication.html", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628087971, "label": "Documentation page describing Datasette's authentication system"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/789#issuecomment-636616155", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/789", "id": 636616155, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjYxNjE1NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T05:10:27Z", "updated_at": "2020-06-01T05:10:27Z", "author_association": "OWNER", "body": "Easiest way to do this would be with an environment variable.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628156527, "label": "Mechanism for enabling pluggy tracing"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/789#issuecomment-636616307", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/789", "id": 636616307, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjYxNjMwNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T05:11:03Z", "updated_at": "2020-06-01T05:11:03Z", "author_association": "OWNER", "body": "Or I could get fancy and implement my own `pm.trace.root.setwriter` function which collects data that can be appended to the `?_trace=1` dump.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628156527, "label": "Mechanism for enabling pluggy tracing"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/788#issuecomment-636616638", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/788", "id": 636616638, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjYxNjYzOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T05:12:30Z", "updated_at": "2020-06-01T05:12:30Z", "author_association": "OWNER", "body": "Looks like this (at the moment):\r\n\r\n\"Debug_permissions\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628121234, "label": " /-/permissions debugging tool"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/698#issuecomment-636617140", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/698", "id": 636617140, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjYxNzE0MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T05:14:39Z", "updated_at": "2020-06-01T05:14:39Z", "author_association": "OWNER", "body": "Here's the new `default_permissions.py` file I can add this permission check to: https://github.com/simonw/datasette/blob/dfdbdf378aba9afb66666f66b78df2f2069d2595/datasette/default_permissions.py#L1-L7", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582517965, "label": "Ability for a canned query to write to the database"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636906581", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636906581, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkwNjU4MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T14:56:42Z", "updated_at": "2020-06-01T14:56:42Z", "author_association": "OWNER", "body": "I can use the new signed values support from #785 to help build this.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636906773", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636906773, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkwNjc3Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T14:57:02Z", "updated_at": "2020-06-01T14:58:14Z", "author_association": "OWNER", "body": "Actually I'm inclined to use cookies now, ala Django: https://docs.djangoproject.com/en/3.0/ref/contrib/messages/\r\n\r\n> This class stores the message data in a cookie (signed with a secret hash to prevent manipulation) to persist notifications across requests. Old messages are dropped if the cookie data size would exceed 2048 bytes.\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636908972", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636908972, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkwODk3Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:01:00Z", "updated_at": "2020-06-01T15:01:00Z", "author_association": "OWNER", "body": "Setting messages just needs access to the response. Reading messages needs access to both request AND response, since it needs to clear the messages that are being displayed.\r\n\r\nThat's if the messages are persisted exclusively in cookies - which makes sense for Django since it's designed to run as many different load-balanced processes.\r\n\r\nSince Datasette is a single process which can access an on-file database, maybe consider storing the flash messages within Datasette memory itself - a sort of session mechanism?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636912730", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636912730, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkxMjczMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:08:13Z", "updated_at": "2020-06-01T15:08:13Z", "author_association": "OWNER", "body": "I'm going to build the first version of this with signed cookies.\r\n\r\nI'm inclined to do this all on the request object, since it's the object representing the current request as it flows through the application. I need the ability to remember which messages were set and which need to be cleared, so I need to do that on something that is available for the lifetime of the request.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636915499", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636915499, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkxNTQ5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:13:40Z", "updated_at": "2020-06-01T15:13:40Z", "author_association": "OWNER", "body": "Maybe two utility functions:\r\n\r\n`add_message(request, message)` - adds a Flash message (will be set later)\r\n\r\n`read_and_clear_messages(request)` - reads messages and sets them to be cleared\r\n\r\nProblem: the `request` object isn't created at the very top of the stack - it's actually created within each view. So maybe I need to move its creation up to the top of the routing stuff so that the code that returns the response can see it?\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636916107", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636916107, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkxNjEwNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:14:30Z", "updated_at": "2020-06-01T15:15:52Z", "author_association": "OWNER", "body": "Alternative: `datasette.add_message(message)` and `datasette.read_and_clear_messages()` - these would need some kind of dark magic to ensure that the message was associated with the current request flowing through the system though, since that `datasette` object is shared my multiple concurrent requests.\r\n\r\nMaybe use a request correlation ID that gets added to the scope? This is all getting a bit messy.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636920304", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636920304, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkyMDMwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:22:15Z", "updated_at": "2020-06-01T15:22:15Z", "author_association": "OWNER", "body": "Here's how the Django stuff works: https://github.com/django/django/blob/master/django/contrib/messages/storage/base.py\r\n\r\nNotably the messages are mostly dealt with on the request object, with a piece of middleware that reads from the request and modifies the response (to set or clear cookies) right at the end: https://github.com/django/django/blob/master/django/contrib/messages/middleware.py", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636922104", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636922104, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkyMjEwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:25:39Z", "updated_at": "2020-06-01T15:25:39Z", "author_association": "OWNER", "body": "What if I use a mutable key on `scope` to track messages for the duration of the request? Is that an OK thing to do?\r\n\r\nASGI spec says this: https://asgi.readthedocs.io/en/latest/specs/main.html#middleware\r\n\r\n> ### Middleware\r\n> \r\n> It is possible to have ASGI \"middleware\" - code that plays the role of both server and application, taking in a scope and the send/receive awaitables, potentially modifying them, and then calling an inner application.\r\n> \r\n> When middleware is modifying the scope, it should make a copy of the scope object before mutating it and passing it to the inner application, as changes may leak upstream otherwise. In particular, you should not assume that the copy of the scope you pass down to the application is the one that it ends up using, as there may be other middleware in the way; thus, do not keep a reference to it and try to mutate it outside of the initial ASGI constructor callable that receives `scope`. Your one and only chance to add to it is before you hand control to the child application.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636925354", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636925354, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkyNTM1NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:32:02Z", "updated_at": "2020-06-01T15:32:02Z", "author_association": "OWNER", "body": "If `scope` had an immutable correlation ID I could use that with a dict somewhere mapping `correlation_id` to a messages object.\r\n\r\nThe problem then is how do I know to clean up the memory used by that dictionary when the request flows out of the system? I guess the code that updates the cookies in the response could do that.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636934016", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636934016, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjkzNDAxNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T15:49:26Z", "updated_at": "2020-06-01T15:49:26Z", "author_association": "OWNER", "body": "Flask and Django both support \"types\" of message - info, warning etc. I think I should do the same.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636959774", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636959774, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjk1OTc3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T16:15:33Z", "updated_at": "2020-06-01T16:15:33Z", "author_association": "OWNER", "body": "It would be neat if this was driven by a method on `datasette` just because that's already the object passed to plugins as a documented API.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/791#issuecomment-636973355", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/791", "id": 636973355, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjk3MzM1NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T16:33:33Z", "updated_at": "2020-06-01T16:33:33Z", "author_association": "OWNER", "body": "A fun thing about this tutorial is that it can start with a classic, basic todo list - and then start growing all kinds of outlandish features to help demonstrate various Datasette plugins and approaches.\r\n\r\nYour TODOs on a map. URLs in TODOs that have been unfurled. Tag your TODOs and browse them with facets. Vega graphs showing your progress. Etc etc etc.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628572716, "label": "Tutorial: building a something-interesting with writable canned queries"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-636978065", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 636978065, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjk3ODA2NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T16:42:59Z", "updated_at": "2020-06-01T17:44:12Z", "author_association": "OWNER", "body": "`datasette.add_message(request, message, type=datasette.INFO)`\r\n\r\nThen later:\r\n\r\n`datasette.write_messages_to_response(request, response)`\r\nWrites the messages as cookies in the response.\r\n\r\n`datasette.fetch_and_clear_messages(request, response)`\r\nTo display messages and clears them from the response.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-637009509", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 637009509, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNzAwOTUwOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T17:44:55Z", "updated_at": "2020-06-01T17:46:18Z", "author_association": "OWNER", "body": "Problem with `datasette.fetch_and_clear_messages(request, response)` is that I want to call it from the template (so we only clear messages if they have been displayed) - but by that point in the code the `Response` object has not yet been created, so it can't have cookie set on it to clear the list of messages.\r\n\r\nSolution: call it `datasette.show_messages(request)` and have it update internal state on the `datasette` object such that a later call to `write_messages_to_response(request, response)` knows to clear the cookie.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/790#issuecomment-637066496", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/790", "id": 637066496, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNzA2NjQ5Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-06-01T19:48:20Z", "updated_at": "2020-06-01T19:48:20Z", "author_association": "OWNER", "body": "I'm going to stash these on the `request` object after all, so the memory used for the messages gets automatically cleaned up at the end of the request.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 628499086, "label": "\"flash messages\" mechanism"}, "performed_via_github_app": null}