From c076fb65e07a7957b8a45804dc8d8cb92020f0ec Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sat, 8 Jul 2023 11:00:08 -0700 Subject: [PATCH] Applied sphinx-inline-tabs to remaining examples, refs #1153 --- docs/authentication.rst | 370 +++++++++++++++++++++++++++++++++----- docs/custom_templates.rst | 142 +++++++++++++-- docs/facets.rst | 124 +++++++++++-- docs/full_text_search.rst | 41 ++++- docs/metadata_doc.py | 20 ++- docs/plugins.rst | 153 ++++++++++++++-- docs/sql_queries.rst | 320 ++++++++++++++++++++++++++++----- setup.py | 1 + 8 files changed, 1019 insertions(+), 152 deletions(-) diff --git a/docs/authentication.rst b/docs/authentication.rst index 3878b2c9..8864086f 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -212,23 +212,63 @@ Access to an instance Here's how to restrict access to your entire Datasette instance to just the ``"id": "root"`` user: -.. code-block:: json - - { +.. [[[cog + from metadata_doc import metadata_example + metadata_example(cog, { "title": "My private Datasette instance", "allow": { "id": "root" } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + title: My private Datasette instance + allow: + id: root + + +.. tab:: JSON + + .. code-block:: json + + { + "title": "My private Datasette instance", + "allow": { + "id": "root" + } + } +.. [[[end]]] To deny access to all users, you can use ``"allow": false``: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "title": "My entirely inaccessible instance", - "allow": false - } + "allow": False + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + title: My entirely inaccessible instance + allow: false + + +.. tab:: JSON + + .. code-block:: json + + { + "title": "My entirely inaccessible instance", + "allow": false + } +.. [[[end]]] One reason to do this is if you are using a Datasette plugin - such as `datasette-permissions-sql `__ - to control permissions instead. @@ -239,9 +279,8 @@ Access to specific databases To limit access to a specific ``private.db`` database to just authenticated users, use the ``"allow"`` block like this: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "private": { "allow": { @@ -249,7 +288,33 @@ To limit access to a specific ``private.db`` database to just authenticated user } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + private: + allow: + id: '*' + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "private": { + "allow": { + "id": "*" + } + } + } + } +.. [[[end]]] .. _authentication_permissions_table: @@ -258,9 +323,8 @@ Access to specific tables and views To limit access to the ``users`` table in your ``bakery.db`` database: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "bakery": { "tables": { @@ -272,7 +336,39 @@ To limit access to the ``users`` table in your ``bakery.db`` database: } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + bakery: + tables: + users: + allow: + id: '*' + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "bakery": { + "tables": { + "users": { + "allow": { + "id": "*" + } + } + } + } + } + } +.. [[[end]]] This works for SQL views as well - you can list their names in the ``"tables"`` block above in the same way as regular tables. @@ -290,15 +386,14 @@ Access to specific canned queries To limit access to the ``add_name`` canned query in your ``dogs.db`` database to just the :ref:`root user`: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "dogs": { "queries": { "add_name": { "sql": "INSERT INTO names (name) VALUES (:name)", - "write": true, + "write": True, "allow": { "id": ["root"] } @@ -306,7 +401,46 @@ To limit access to the ``add_name`` canned query in your ``dogs.db`` database to } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + dogs: + queries: + add_name: + sql: INSERT INTO names (name) VALUES (:name) + write: true + allow: + id: + - root + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "dogs": { + "queries": { + "add_name": { + "sql": "INSERT INTO names (name) VALUES (:name)", + "write": true, + "allow": { + "id": [ + "root" + ] + } + } + } + } + } + } +.. [[[end]]] .. _authentication_permissions_execute_sql: @@ -323,27 +457,61 @@ You can alternatively use an ``"allow_sql"`` block to control who is allowed to To prevent any user from executing arbitrary SQL queries, use this: -.. code-block:: json +.. [[[cog + metadata_example(cog, { + "allow_sql": False + }) +.. ]]] - { - "allow_sql": false - } +.. tab:: YAML + + .. code-block:: yaml + + allow_sql: false + + +.. tab:: JSON + + .. code-block:: json + + { + "allow_sql": false + } +.. [[[end]]] To enable just the :ref:`root user` to execute SQL for all databases in your instance, use the following: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "allow_sql": { "id": "root" } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + allow_sql: + id: root + + +.. tab:: JSON + + .. code-block:: json + + { + "allow_sql": { + "id": "root" + } + } +.. [[[end]]] To limit this ability for just one specific database, use this: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "mydatabase": { "allow_sql": { @@ -351,7 +519,33 @@ To limit this ability for just one specific database, use this: } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + mydatabase: + allow_sql: + id: root + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "mydatabase": { + "allow_sql": { + "id": "root" + } + } + } + } +.. [[[end]]] .. _authentication_permissions_other: @@ -362,21 +556,42 @@ For all other permissions, you can use one or more ``"permissions"`` blocks in y To grant access to the :ref:`permissions debug tool ` to all signed in users you can grant ``permissions-debug`` to any actor with an ``id`` matching the wildcard ``*`` by adding this a the root of your metadata: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "permissions": { "debug-menu": { "id": "*" } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + permissions: + debug-menu: + id: '*' + + +.. tab:: JSON + + .. code-block:: json + + { + "permissions": { + "debug-menu": { + "id": "*" + } + } + } +.. [[[end]]] To grant ``create-table`` to the user with ``id`` of ``editor`` for the ``docs`` database: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "docs": { "permissions": { @@ -386,13 +601,41 @@ To grant ``create-table`` to the user with ``id`` of ``editor`` for the ``docs`` } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + docs: + permissions: + create-table: + id: editor + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "docs": { + "permissions": { + "create-table": { + "id": "editor" + } + } + } + } + } +.. [[[end]]] And for ``insert-row`` against the ``reports`` table in that ``docs`` database: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "docs": { "tables": { @@ -406,7 +649,42 @@ And for ``insert-row`` against the ``reports`` table in that ``docs`` database: } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + docs: + tables: + reports: + permissions: + insert-row: + id: editor + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "docs": { + "tables": { + "reports": { + "permissions": { + "insert-row": { + "id": "editor" + } + } + } + } + } + } + } +.. [[[end]]] The :ref:`permissions debug tool ` can be useful for helping test permissions that you have configured in this way. diff --git a/docs/custom_templates.rst b/docs/custom_templates.rst index 97dea2af..f9d0b5f5 100644 --- a/docs/custom_templates.rst +++ b/docs/custom_templates.rst @@ -12,20 +12,45 @@ Custom CSS and JavaScript When you launch Datasette, you can specify a custom metadata file like this:: - datasette mydb.db --metadata metadata.json + datasette mydb.db --metadata metadata.yaml -Your ``metadata.json`` file can include links that look like this: +Your ``metadata.yaml`` file can include links that look like this: -.. code-block:: json - - { +.. [[[cog + from metadata_doc import metadata_example + metadata_example(cog, { "extra_css_urls": [ "https://simonwillison.net/static/css/all.bf8cd891642c.css" ], "extra_js_urls": [ "https://code.jquery.com/jquery-3.2.1.slim.min.js" ] - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + extra_css_urls: + - https://simonwillison.net/static/css/all.bf8cd891642c.css + extra_js_urls: + - https://code.jquery.com/jquery-3.2.1.slim.min.js + + +.. tab:: JSON + + .. code-block:: json + + { + "extra_css_urls": [ + "https://simonwillison.net/static/css/all.bf8cd891642c.css" + ], + "extra_js_urls": [ + "https://code.jquery.com/jquery-3.2.1.slim.min.js" + ] + } +.. [[[end]]] The extra CSS and JavaScript files will be linked in the ```` of every page: @@ -36,9 +61,8 @@ The extra CSS and JavaScript files will be linked in the ```` of every pag You can also specify a SRI (subresource integrity hash) for these assets: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "extra_css_urls": [ { "url": "https://simonwillison.net/static/css/all.bf8cd891642c.css", @@ -51,7 +75,40 @@ You can also specify a SRI (subresource integrity hash) for these assets: "sri": "sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=" } ] - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + extra_css_urls: + - url: https://simonwillison.net/static/css/all.bf8cd891642c.css + sri: sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI + extra_js_urls: + - url: https://code.jquery.com/jquery-3.2.1.slim.min.js + sri: sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g= + + +.. tab:: JSON + + .. code-block:: json + + { + "extra_css_urls": [ + { + "url": "https://simonwillison.net/static/css/all.bf8cd891642c.css", + "sri": "sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI" + } + ], + "extra_js_urls": [ + { + "url": "https://code.jquery.com/jquery-3.2.1.slim.min.js", + "sri": "sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=" + } + ] + } +.. [[[end]]] This will produce: @@ -69,16 +126,39 @@ matches the content served. You can generate hashes using `www.srihash.org `__. This configuration: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "extra_js_urls": [ { "url": "https://example.datasette.io/module.js", - "module": true + "module": True } ] - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + extra_js_urls: + - url: https://example.datasette.io/module.js + module: true + + +.. tab:: JSON + + .. code-block:: json + + { + "extra_js_urls": [ + { + "url": "https://example.datasette.io/module.js", + "module": true + } + ] + } +.. [[[end]]] Will produce this HTML: @@ -188,16 +268,40 @@ The following URLs will now serve the content from those CSS and JS files:: You can reference those files from ``metadata.json`` like so: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "extra_css_urls": [ "/assets/styles.css" ], "extra_js_urls": [ "/assets/app.js" ] - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + extra_css_urls: + - /assets/styles.css + extra_js_urls: + - /assets/app.js + + +.. tab:: JSON + + .. code-block:: json + + { + "extra_css_urls": [ + "/assets/styles.css" + ], + "extra_js_urls": [ + "/assets/app.js" + ] + } +.. [[[end]]] Publishing static assets ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/facets.rst b/docs/facets.rst index 5df4dbb4..dba232bf 100644 --- a/docs/facets.rst +++ b/docs/facets.rst @@ -98,16 +98,16 @@ You can increase this on an individual page by adding ``?_facet_size=100`` to th .. _facets_metadata: -Facets in metadata.json ------------------------ +Facets in metadata +------------------ You can turn facets on by default for specific tables by adding them to a ``"facets"`` key in a Datasette :ref:`metadata` file. Here's an example that turns on faceting by default for the ``qLegalStatus`` column in the ``Street_Tree_List`` table in the ``sf-trees`` database: -.. code-block:: json - - { +.. [[[cog + from metadata_doc import metadata_example + metadata_example(cog, { "databases": { "sf-trees": { "tables": { @@ -117,26 +117,82 @@ Here's an example that turns on faceting by default for the ``qLegalStatus`` col } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + sf-trees: + tables: + Street_Tree_List: + facets: + - qLegalStatus + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "sf-trees": { + "tables": { + "Street_Tree_List": { + "facets": [ + "qLegalStatus" + ] + } + } + } + } + } +.. [[[end]]] Facets defined in this way will always be shown in the interface and returned in the API, regardless of the ``_facet`` arguments passed to the view. You can specify :ref:`array ` or :ref:`date ` facets in metadata using JSON objects with a single key of ``array`` or ``date`` and a value specifying the column, like this: -.. code-block:: json +.. [[[cog + metadata_example(cog, { + "facets": [ + {"array": "tags"}, + {"date": "created"} + ] + }) +.. ]]] - { - "facets": [ - {"array": "tags"}, - {"date": "created"} - ] - } +.. tab:: YAML + + .. code-block:: yaml + + facets: + - array: tags + - date: created + + +.. tab:: JSON + + .. code-block:: json + + { + "facets": [ + { + "array": "tags" + }, + { + "date": "created" + } + ] + } +.. [[[end]]] You can change the default facet size (the number of results shown for each facet) for a table using ``facet_size``: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "sf-trees": { "tables": { @@ -147,7 +203,41 @@ You can change the default facet size (the number of results shown for each face } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + sf-trees: + tables: + Street_Tree_List: + facets: + - qLegalStatus + facet_size: 10 + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "sf-trees": { + "tables": { + "Street_Tree_List": { + "facets": [ + "qLegalStatus" + ], + "facet_size": 10 + } + } + } + } + } +.. [[[end]]] Suggested facets ---------------- diff --git a/docs/full_text_search.rst b/docs/full_text_search.rst index 1162a7f9..c956865b 100644 --- a/docs/full_text_search.rst +++ b/docs/full_text_search.rst @@ -64,9 +64,9 @@ The ``"searchmode": "raw"`` property can be used to default the table to accepti Here is an example which enables full-text search (with SQLite advanced search operators) for a ``display_ads`` view which is defined against the ``ads`` table and hence needs to run FTS against the ``ads_fts`` table, using the ``id`` as the primary key: -.. code-block:: json - - { +.. [[[cog + from metadata_doc import metadata_example + metadata_example(cog, { "databases": { "russian-ads": { "tables": { @@ -78,7 +78,40 @@ Here is an example which enables full-text search (with SQLite advanced search o } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + russian-ads: + tables: + display_ads: + fts_table: ads_fts + fts_pk: id + searchmode: raw + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "russian-ads": { + "tables": { + "display_ads": { + "fts_table": "ads_fts", + "fts_pk": "id", + "searchmode": "raw" + } + } + } + } + } +.. [[[end]]] .. _full_text_search_custom_sql: diff --git a/docs/metadata_doc.py b/docs/metadata_doc.py index 19ddef89..537830ca 100644 --- a/docs/metadata_doc.py +++ b/docs/metadata_doc.py @@ -1,13 +1,25 @@ import json import textwrap -import yaml +from yaml import safe_dump +from ruamel.yaml import round_trip_load -def metadata_example(cog, example): +def metadata_example(cog, data=None, yaml=None): + assert data or yaml, "Must provide data= or yaml=" + assert not (data and yaml), "Cannot use data= and yaml=" + output_yaml = None + if yaml: + # dedent it first + yaml = textwrap.dedent(yaml).strip() + # round_trip_load to preserve key order: + data = round_trip_load(yaml) + output_yaml = yaml + else: + output_yaml = safe_dump(data, sort_keys=False) cog.out("\n.. tab:: YAML\n\n") cog.out(" .. code-block:: yaml\n\n") - cog.out(textwrap.indent(yaml.safe_dump(example, sort_keys=False), " ")) + cog.out(textwrap.indent(output_yaml, " ")) cog.out("\n\n.. tab:: JSON\n\n") cog.out(" .. code-block:: json\n\n") - cog.out(textwrap.indent(json.dumps(example, indent=2), " ")) + cog.out(textwrap.indent(json.dumps(data, indent=2), " ")) cog.out("\n") diff --git a/docs/plugins.rst b/docs/plugins.rst index 5e81e202..979f94dd 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -245,9 +245,9 @@ Plugins can have their own configuration, embedded in a :ref:`metadata` file. Co Here is an example of some plugin configuration for a specific table: -.. code-block:: json - - { +.. [[[cog + from metadata_doc import metadata_example + metadata_example(cog, { "databases": { "sf-trees": { "tables": { @@ -262,7 +262,44 @@ Here is an example of some plugin configuration for a specific table: } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + sf-trees: + tables: + Street_Tree_List: + plugins: + datasette-cluster-map: + latitude_column: lat + longitude_column: lng + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "sf-trees": { + "tables": { + "Street_Tree_List": { + "plugins": { + "datasette-cluster-map": { + "latitude_column": "lat", + "longitude_column": "lng" + } + } + } + } + } + } + } +.. [[[end]]] This tells the ``datasette-cluster-map`` column which latitude and longitude columns should be used for a table called ``Street_Tree_List`` inside a database file called ``sf-trees.db``. @@ -271,13 +308,12 @@ This tells the ``datasette-cluster-map`` column which latitude and longitude col Secret configuration values ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Any values embedded in ``metadata.json`` will be visible to anyone who views the ``/-/metadata`` page of your Datasette instance. Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values. +Any values embedded in ``metadata.yaml`` will be visible to anyone who views the ``/-/metadata`` page of your Datasette instance. Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values. **As environment variables**. If your secret lives in an environment variable that is available to the Datasette process, you can indicate that the configuration value should be read from that environment variable like so: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "plugins": { "datasette-auth-github": { "client_secret": { @@ -285,13 +321,38 @@ Any values embedded in ``metadata.json`` will be visible to anyone who views the } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + plugins: + datasette-auth-github: + client_secret: + $env: GITHUB_CLIENT_SECRET + + +.. tab:: JSON + + .. code-block:: json + + { + "plugins": { + "datasette-auth-github": { + "client_secret": { + "$env": "GITHUB_CLIENT_SECRET" + } + } + } + } +.. [[[end]]] **As values in separate files**. Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "plugins": { "datasette-auth-github": { "client_secret": { @@ -299,7 +360,33 @@ Any values embedded in ``metadata.json`` will be visible to anyone who views the } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + plugins: + datasette-auth-github: + client_secret: + $file: /secrets/client-secret + + +.. tab:: JSON + + .. code-block:: json + + { + "plugins": { + "datasette-auth-github": { + "client_secret": { + "$file": "/secrets/client-secret" + } + } + } + } +.. [[[end]]] If you are publishing your data using the :ref:`datasette publish ` family of commands, you can use the ``--plugin-secret`` option to set these secrets at publish time. For example, using Heroku you might run the following command:: @@ -309,11 +396,10 @@ If you are publishing your data using the :ref:`datasette publish ` --plugin-secret datasette-auth-github client_id your_client_id \ --plugin-secret datasette-auth-github client_secret your_client_secret -This will set the necessary environment variables and add the following to the deployed ``metadata.json``: +This will set the necessary environment variables and add the following to the deployed ``metadata.yaml``: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "plugins": { "datasette-auth-github": { "client_id": { @@ -324,4 +410,35 @@ This will set the necessary environment variables and add the following to the d } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + plugins: + datasette-auth-github: + client_id: + $env: DATASETTE_AUTH_GITHUB_CLIENT_ID + client_secret: + $env: DATASETTE_AUTH_GITHUB_CLIENT_SECRET + + +.. tab:: JSON + + .. code-block:: json + + { + "plugins": { + "datasette-auth-github": { + "client_id": { + "$env": "DATASETTE_AUTH_GITHUB_CLIENT_ID" + }, + "client_secret": { + "$env": "DATASETTE_AUTH_GITHUB_CLIENT_SECRET" + } + } + } + } +.. [[[end]]] diff --git a/docs/sql_queries.rst b/docs/sql_queries.rst index 5f53a6d7..3c2cb228 100644 --- a/docs/sql_queries.rst +++ b/docs/sql_queries.rst @@ -64,11 +64,11 @@ The quickest way to create views is with the SQLite command-line interface:: Canned queries -------------- -As an alternative to adding views to your database, you can define canned queries inside your ``metadata.json`` file. Here's an example: +As an alternative to adding views to your database, you can define canned queries inside your ``metadata.yaml`` file. Here's an example: -.. code-block:: json - - { +.. [[[cog + from metadata_doc import metadata_example + metadata_example(cog, { "databases": { "sf-trees": { "queries": { @@ -78,7 +78,36 @@ As an alternative to adding views to your database, you can define canned querie } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + sf-trees: + queries: + just_species: + sql: select qSpecies from Street_Tree_List + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "sf-trees": { + "queries": { + "just_species": { + "sql": "select qSpecies from Street_Tree_List" + } + } + } + } + } +.. [[[end]]] Then run Datasette like this:: @@ -111,38 +140,58 @@ Here's an example of a canned query with a named parameter: where neighborhood like '%' || :text || '%' order by neighborhood; -In the canned query metadata (here :ref:`metadata_yaml` as ``metadata.yaml``) it looks like this: +In the canned query metadata looks like this: -.. code-block:: yaml +.. [[[cog + metadata_example(cog, yaml=""" databases: fixtures: queries: neighborhood_search: + title: Search neighborhoods sql: |- select neighborhood, facet_cities.name, state from facetable join facet_cities on facetable.city_id = facet_cities.id where neighborhood like '%' || :text || '%' order by neighborhood - title: Search neighborhoods + """) +.. ]]] -Here's the equivalent using JSON (as ``metadata.json``): +.. tab:: YAML -.. code-block:: json + .. code-block:: yaml - { - "databases": { + databases: + fixtures: + queries: + neighborhood_search: + title: Search neighborhoods + sql: |- + select neighborhood, facet_cities.name, state + from facetable + join facet_cities on facetable.city_id = facet_cities.id + where neighborhood like '%' || :text || '%' + order by neighborhood + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { "fixtures": { - "queries": { - "neighborhood_search": { - "sql": "select neighborhood, facet_cities.name, state\nfrom facetable\n join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%'\norder by neighborhood", - "title": "Search neighborhoods" - } + "queries": { + "neighborhood_search": { + "title": "Search neighborhoods", + "sql": "select neighborhood, facet_cities.name, state\nfrom facetable\n join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%'\norder by neighborhood" } + } } + } } - } +.. [[[end]]] Note that we are using SQLite string concatenation here - the ``||`` operator - to add wildcard ``%`` characters to the string provided by the user. @@ -153,12 +202,13 @@ In this example the ``:text`` named parameter is automatically extracted from th You can alternatively provide an explicit list of named parameters using the ``"params"`` key, like this: -.. code-block:: yaml - +.. [[[cog + metadata_example(cog, yaml=""" databases: fixtures: queries: neighborhood_search: + title: Search neighborhoods params: - text sql: |- @@ -167,7 +217,47 @@ You can alternatively provide an explicit list of named parameters using the ``" join facet_cities on facetable.city_id = facet_cities.id where neighborhood like '%' || :text || '%' order by neighborhood - title: Search neighborhoods + """) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + fixtures: + queries: + neighborhood_search: + title: Search neighborhoods + params: + - text + sql: |- + select neighborhood, facet_cities.name, state + from facetable + join facet_cities on facetable.city_id = facet_cities.id + where neighborhood like '%' || :text || '%' + order by neighborhood + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "fixtures": { + "queries": { + "neighborhood_search": { + "title": "Search neighborhoods", + "params": [ + "text" + ], + "sql": "select neighborhood, facet_cities.name, state\nfrom facetable\n join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%'\norder by neighborhood" + } + } + } + } + } +.. [[[end]]] .. _canned_queries_options: @@ -192,21 +282,54 @@ You can set a default fragment hash that will be included in the link to the can This example demonstrates both ``fragment`` and ``hide_sql``: -.. code-block:: json +.. [[[cog + metadata_example(cog, yaml=""" + databases: + fixtures: + queries: + neighborhood_search: + fragment: fragment-goes-here + hide_sql: true + sql: |- + select neighborhood, facet_cities.name, state + from facetable join facet_cities on facetable.city_id = facet_cities.id + where neighborhood like '%' || :text || '%' order by neighborhood; + """) +.. ]]] - { - "databases": { +.. tab:: YAML + + .. code-block:: yaml + + databases: + fixtures: + queries: + neighborhood_search: + fragment: fragment-goes-here + hide_sql: true + sql: |- + select neighborhood, facet_cities.name, state + from facetable join facet_cities on facetable.city_id = facet_cities.id + where neighborhood like '%' || :text || '%' order by neighborhood; + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { "fixtures": { - "queries": { - "neighborhood_search": { - "sql": "select neighborhood, facet_cities.name, state\nfrom facetable join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%' order by neighborhood;", - "fragment": "fragment-goes-here", - "hide_sql": true - } + "queries": { + "neighborhood_search": { + "fragment": "fragment-goes-here", + "hide_sql": true, + "sql": "select neighborhood, facet_cities.name, state\nfrom facetable join facet_cities on facetable.city_id = facet_cities.id\nwhere neighborhood like '%' || :text || '%' order by neighborhood;" } + } } + } } - } +.. [[[end]]] `See here `__ for a demo of this in action. @@ -219,20 +342,50 @@ Canned queries by default are read-only. You can use the ``"write": true`` key t See :ref:`authentication_permissions_query` for details on how to add permission checks to canned queries, using the ``"allow"`` key. -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "mydatabase": { "queries": { "add_name": { "sql": "INSERT INTO names (name) VALUES (:name)", - "write": true + "write": True } } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + mydatabase: + queries: + add_name: + sql: INSERT INTO names (name) VALUES (:name) + write: true + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "mydatabase": { + "queries": { + "add_name": { + "sql": "INSERT INTO names (name) VALUES (:name)", + "write": true + } + } + } + } + } +.. [[[end]]] This configuration will create a page at ``/mydatabase/add_name`` displaying a form with a ``name`` field. Submitting that form will execute the configured ``INSERT`` query. @@ -245,15 +398,14 @@ You can customize how Datasette represents success and errors using the followin For example: -.. code-block:: json - - { +.. [[[cog + metadata_example(cog, { "databases": { "mydatabase": { "queries": { "add_name": { "sql": "INSERT INTO names (name) VALUES (:name)", - "write": true, + "write": True, "on_success_message": "Name inserted", "on_success_redirect": "/mydatabase/names", "on_error_message": "Name insert failed", @@ -262,7 +414,46 @@ For example: } } } - } + }) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + mydatabase: + queries: + add_name: + sql: INSERT INTO names (name) VALUES (:name) + write: true + on_success_message: Name inserted + on_success_redirect: /mydatabase/names + on_error_message: Name insert failed + on_error_redirect: /mydatabase + + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "mydatabase": { + "queries": { + "add_name": { + "sql": "INSERT INTO names (name) VALUES (:name)", + "write": true, + "on_success_message": "Name inserted", + "on_success_redirect": "/mydatabase/names", + "on_error_message": "Name insert failed", + "on_error_redirect": "/mydatabase" + } + } + } + } + } +.. [[[end]]] You can use ``"params"`` to explicitly list the named parameters that should be displayed as form fields - otherwise they will be automatically detected. @@ -300,10 +491,10 @@ Available magic parameters are: ``_random_chars_*`` - e.g. ``_random_chars_128`` A random string of characters of the specified length. -Here's an example configuration (this time using ``metadata.yaml`` since that provides better support for multi-line SQL queries) that adds a message from the authenticated user, storing various pieces of additional metadata using magic parameters: - -.. code-block:: yaml +Here's an example configuration that adds a message from the authenticated user, storing various pieces of additional metadata using magic parameters: +.. [[[cog + metadata_example(cog, yaml=""" databases: mydatabase: queries: @@ -317,6 +508,47 @@ Here's an example configuration (this time using ``metadata.yaml`` since that pr :_actor_id, :message, :_now_datetime_utc ) write: true + """) +.. ]]] + +.. tab:: YAML + + .. code-block:: yaml + + databases: + mydatabase: + queries: + add_message: + allow: + id: "*" + sql: |- + INSERT INTO messages ( + user_id, message, datetime + ) VALUES ( + :_actor_id, :message, :_now_datetime_utc + ) + write: true + +.. tab:: JSON + + .. code-block:: json + + { + "databases": { + "mydatabase": { + "queries": { + "add_message": { + "allow": { + "id": "*" + }, + "sql": "INSERT INTO messages (\n user_id, message, datetime\n) VALUES (\n :_actor_id, :message, :_now_datetime_utc\n)", + "write": true + } + } + } + } + } +.. [[[end]]] The form presented at ``/mydatabase/add_message`` will have just a field for ``message`` - the other parameters will be populated by the magic parameter mechanism. diff --git a/setup.py b/setup.py index be9c7fde..61860f43 100644 --- a/setup.py +++ b/setup.py @@ -76,6 +76,7 @@ setup( "blacken-docs", "sphinx-copybutton", "sphinx-inline-tabs", + "ruamel.yaml", ], "test": [ "pytest>=5.2.2",