datasette-edit-schema 0.7.1__tar.gz → 0.8a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of datasette-edit-schema might be problematic. Click here for more details.

Files changed (22) hide show
  1. {datasette-edit-schema-0.7.1/datasette_edit_schema.egg-info → datasette-edit-schema-0.8a1}/PKG-INFO +47 -6
  2. datasette-edit-schema-0.7.1/PKG-INFO → datasette-edit-schema-0.8a1/README.md +38 -20
  3. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/__init__.py +177 -41
  4. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/templates/edit_schema_create_table.html +6 -3
  5. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/templates/edit_schema_table.html +19 -11
  6. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/utils.py +2 -1
  7. datasette-edit-schema-0.8a1/datasette_edit_schema.egg-info/PKG-INFO +129 -0
  8. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema.egg-info/SOURCES.txt +1 -1
  9. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema.egg-info/requires.txt +1 -1
  10. datasette-edit-schema-0.8a1/pyproject.toml +37 -0
  11. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/tests/test_edit_schema.py +395 -116
  12. datasette-edit-schema-0.7.1/README.md +0 -70
  13. datasette-edit-schema-0.7.1/setup.py +0 -33
  14. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/LICENSE +0 -0
  15. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/static/draggable.1.0.0-beta.11.bundle.js +0 -0
  16. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/static/draggable.1.0.0-beta.11.bundle.min.js +0 -0
  17. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/templates/edit_schema_database.html +0 -0
  18. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema/templates/edit_schema_index.html +0 -0
  19. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema.egg-info/dependency_links.txt +0 -0
  20. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema.egg-info/entry_points.txt +0 -0
  21. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/datasette_edit_schema.egg-info/top_level.txt +0 -0
  22. {datasette-edit-schema-0.7.1 → datasette-edit-schema-0.8a1}/setup.cfg +0 -0
@@ -1,14 +1,19 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datasette-edit-schema
3
- Version: 0.7.1
3
+ Version: 0.8a1
4
4
  Summary: Datasette plugin for modifying table schemas
5
- Home-page: https://github.com/simonw/datasette-edit-schema
6
5
  Author: Simon Willison
7
- License: Apache License, Version 2.0
8
- Requires-Python: >=3.7
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://datasette.io/plugins/datasette-edit-schema
8
+ Project-URL: Changelog, https://github.com/simonw/datasette-edit-schema/releases
9
+ Project-URL: Issues, https://github.com/simonw/datasette-edit-schema/issues
10
+ Project-URL: CI, https://github.com/simonw/datasette-edit-schema/actions
11
+ Classifier: Framework :: Datasette
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Requires-Python: >=3.8
9
14
  Description-Content-Type: text/markdown
10
15
  License-File: LICENSE
11
- Requires-Dist: datasette>=0.63
16
+ Requires-Dist: datasette>=1.0a13
12
17
  Requires-Dist: sqlite-utils>=3.35
13
18
  Provides-Extra: test
14
19
  Requires-Dist: pytest; extra == "test"
@@ -25,6 +30,8 @@ Requires-Dist: html5lib; extra == "test"
25
30
 
26
31
  Datasette plugin for modifying table schemas
27
32
 
33
+ > :warning: The latest alpha release depends on Datasette 1.09a. Use [version 0.7.1](https://github.com/simonw/datasette-edit-schema/blob/0.7.1/README.md) with older releases of Datasette.
34
+
28
35
  ## Features
29
36
 
30
37
  * Add new columns to a table
@@ -54,7 +61,9 @@ By default only [the root actor](https://datasette.readthedocs.io/en/stable/auth
54
61
 
55
62
  ## Permissions
56
63
 
57
- The `edit-schema` permission governs access. You can use permission plugins such as [datasette-permissions-sql](https://github.com/simonw/datasette-permissions-sql) to grant additional access to the write interface.
64
+ The `edit-schema` permission provides access to all functionality.
65
+
66
+ You can use permission plugins such as [datasette-permissions-sql](https://github.com/simonw/datasette-permissions-sql) to grant additional access to the write interface.
58
67
 
59
68
  These permission checks will call the `permission_allowed()` plugin hook with three arguments:
60
69
 
@@ -62,6 +71,38 @@ These permission checks will call the `permission_allowed()` plugin hook with th
62
71
  - `actor` will be the currently authenticated actor - usually a dictionary
63
72
  - `resource` will be the string name of the database
64
73
 
74
+ You can instead use more finely-grained permissions from the default Datasette permissions collection:
75
+
76
+ - `create-table` allows users to create a new table. The `resource` will be the name of the database.
77
+ - `drop-table` allows users to drop a table. The `resource` will be a tuple of `(database_name, table_name)`.
78
+ - `alter-table` allows users to alter a table. The `resource` will be a tuple of `(database_name, table_name)`.
79
+
80
+ To rename a table a user must have both `drop-table` permission for that table and `create-table` permission for that database.
81
+
82
+ For example, to configure Datasette to allow the user with ID `pelican` to create, alter and drop tables in the `marketing` database and to alter just the `notes` table in the `sales` database, you could use the following configuration:
83
+
84
+ ```yaml
85
+ databases:
86
+ marketing:
87
+ permissions:
88
+ create-table:
89
+ id: pelican
90
+ drop-table:
91
+ id: pelican
92
+ alter-table:
93
+ id: pelican
94
+ sales:
95
+ tables:
96
+ notes:
97
+ permissions:
98
+ alter-table:
99
+ id: pelican
100
+ ```
101
+
102
+ ## Events
103
+
104
+ This plugin fires `create-table`, `alter-table` and `drop-table` events when tables are modified, using the [Datasette Events](https://docs.datasette.io/en/latest/events.html) system introduced in [Datasette 1.0a8](https://docs.datasette.io/en/latest/changelog.html#a8-2024-02-07).
105
+
65
106
  ## Screenshot
66
107
 
67
108
  ![datasette-edit-schema interface](https://raw.githubusercontent.com/simonw/datasette-edit-schema/main/datasette-edit-schema.png)
@@ -1,21 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: datasette-edit-schema
3
- Version: 0.7.1
4
- Summary: Datasette plugin for modifying table schemas
5
- Home-page: https://github.com/simonw/datasette-edit-schema
6
- Author: Simon Willison
7
- License: Apache License, Version 2.0
8
- Requires-Python: >=3.7
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Requires-Dist: datasette>=0.63
12
- Requires-Dist: sqlite-utils>=3.35
13
- Provides-Extra: test
14
- Requires-Dist: pytest; extra == "test"
15
- Requires-Dist: pytest-asyncio; extra == "test"
16
- Requires-Dist: beautifulsoup4; extra == "test"
17
- Requires-Dist: html5lib; extra == "test"
18
-
19
1
  # datasette-edit-schema
20
2
 
21
3
  [![PyPI](https://img.shields.io/pypi/v/datasette-edit-schema.svg)](https://pypi.org/project/datasette-edit-schema/)
@@ -25,6 +7,8 @@ Requires-Dist: html5lib; extra == "test"
25
7
 
26
8
  Datasette plugin for modifying table schemas
27
9
 
10
+ > :warning: The latest alpha release depends on Datasette 1.09a. Use [version 0.7.1](https://github.com/simonw/datasette-edit-schema/blob/0.7.1/README.md) with older releases of Datasette.
11
+
28
12
  ## Features
29
13
 
30
14
  * Add new columns to a table
@@ -54,7 +38,9 @@ By default only [the root actor](https://datasette.readthedocs.io/en/stable/auth
54
38
 
55
39
  ## Permissions
56
40
 
57
- The `edit-schema` permission governs access. You can use permission plugins such as [datasette-permissions-sql](https://github.com/simonw/datasette-permissions-sql) to grant additional access to the write interface.
41
+ The `edit-schema` permission provides access to all functionality.
42
+
43
+ You can use permission plugins such as [datasette-permissions-sql](https://github.com/simonw/datasette-permissions-sql) to grant additional access to the write interface.
58
44
 
59
45
  These permission checks will call the `permission_allowed()` plugin hook with three arguments:
60
46
 
@@ -62,6 +48,38 @@ These permission checks will call the `permission_allowed()` plugin hook with th
62
48
  - `actor` will be the currently authenticated actor - usually a dictionary
63
49
  - `resource` will be the string name of the database
64
50
 
51
+ You can instead use more finely-grained permissions from the default Datasette permissions collection:
52
+
53
+ - `create-table` allows users to create a new table. The `resource` will be the name of the database.
54
+ - `drop-table` allows users to drop a table. The `resource` will be a tuple of `(database_name, table_name)`.
55
+ - `alter-table` allows users to alter a table. The `resource` will be a tuple of `(database_name, table_name)`.
56
+
57
+ To rename a table a user must have both `drop-table` permission for that table and `create-table` permission for that database.
58
+
59
+ For example, to configure Datasette to allow the user with ID `pelican` to create, alter and drop tables in the `marketing` database and to alter just the `notes` table in the `sales` database, you could use the following configuration:
60
+
61
+ ```yaml
62
+ databases:
63
+ marketing:
64
+ permissions:
65
+ create-table:
66
+ id: pelican
67
+ drop-table:
68
+ id: pelican
69
+ alter-table:
70
+ id: pelican
71
+ sales:
72
+ tables:
73
+ notes:
74
+ permissions:
75
+ alter-table:
76
+ id: pelican
77
+ ```
78
+
79
+ ## Events
80
+
81
+ This plugin fires `create-table`, `alter-table` and `drop-table` events when tables are modified, using the [Datasette Events](https://docs.datasette.io/en/latest/events.html) system introduced in [Datasette 1.0a8](https://docs.datasette.io/en/latest/changelog.html#a8-2024-02-07).
82
+
65
83
  ## Screenshot
66
84
 
67
85
  ![datasette-edit-schema interface](https://raw.githubusercontent.com/simonw/datasette-edit-schema/main/datasette-edit-schema.png)
@@ -85,4 +103,4 @@ pip install -e '.[test]'
85
103
  To run the tests:
86
104
  ```bash
87
105
  pytest
88
- ```
106
+ ```
@@ -1,4 +1,5 @@
1
1
  from datasette import hookimpl
2
+ from datasette.events import CreateTableEvent, AlterTableEvent, DropTableEvent
2
3
  from datasette.utils.asgi import Response, NotFound, Forbidden
3
4
  from datasette.utils import sqlite3
4
5
  from urllib.parse import quote_plus, unquote_plus
@@ -11,6 +12,11 @@ from .utils import (
11
12
  potential_primary_keys,
12
13
  )
13
14
 
15
+ try:
16
+ from datasette import events
17
+ except ImportError: # Pre Datasette 1.0a8
18
+ events = None
19
+
14
20
  # Don't attempt to detect foreign keys on tables larger than this:
15
21
  FOREIGN_KEY_DETECTION_LIMIT = 10_000
16
22
 
@@ -29,9 +35,7 @@ def permission_allowed(actor, action, resource):
29
35
  @hookimpl
30
36
  def table_actions(datasette, actor, database, table):
31
37
  async def inner():
32
- if not await datasette.permission_allowed(
33
- actor, "edit-schema", resource=database, default=False
34
- ):
38
+ if not await can_alter_table(datasette, actor, database, table):
35
39
  return []
36
40
  return [
37
41
  {
@@ -39,18 +43,63 @@ def table_actions(datasette, actor, database, table):
39
43
  "/-/edit-schema/{}/{}".format(database, quote_plus(table))
40
44
  ),
41
45
  "label": "Edit table schema",
46
+ "description": "Rename the table, add and remove columns...",
42
47
  }
43
48
  ]
44
49
 
45
50
  return inner
46
51
 
47
52
 
53
+ async def can_create_table(datasette, actor, database):
54
+ if await datasette.permission_allowed(
55
+ actor, "edit-schema", resource=database, default=False
56
+ ):
57
+ return True
58
+ # Or maybe they have create-table
59
+ if await datasette.permission_allowed(
60
+ actor, "create-table", resource=database, default=False
61
+ ):
62
+ return True
63
+ return False
64
+
65
+
66
+ async def can_alter_table(datasette, actor, database, table):
67
+ if await datasette.permission_allowed(
68
+ actor, "edit-schema", resource=database, default=False
69
+ ):
70
+ return True
71
+ if await datasette.permission_allowed(
72
+ actor, "alter-table", resource=(database, table), default=False
73
+ ):
74
+ return True
75
+ return False
76
+
77
+
78
+ async def can_rename_table(datasette, actor, database, table):
79
+ if not await can_drop_table(datasette, actor, database, table):
80
+ return False
81
+ if not await can_create_table(datasette, actor, database):
82
+ return False
83
+ return True
84
+
85
+
86
+ async def can_drop_table(datasette, actor, database, table):
87
+ if await datasette.permission_allowed(
88
+ actor, "edit-schema", resource=database, default=False
89
+ ):
90
+ return True
91
+ # Or maybe they have drop-table
92
+ if await datasette.permission_allowed(
93
+ actor, "drop-table", resource=(database, table), default=False
94
+ ):
95
+ return True
96
+ return False
97
+
98
+
48
99
  @hookimpl
49
100
  def database_actions(datasette, actor, database):
50
101
  async def inner():
51
- if not await datasette.permission_allowed(
52
- actor, "edit-schema", resource=database, default=False
53
- ):
102
+ if not await can_create_table(datasette, actor, database):
54
103
  return []
55
104
  return [
56
105
  {
@@ -58,6 +107,7 @@ def database_actions(datasette, actor, database):
58
107
  "/-/edit-schema/{}/-/create".format(database)
59
108
  ),
60
109
  "label": "Create a table",
110
+ "description": "Define a new table with specified columns",
61
111
  }
62
112
  ]
63
113
 
@@ -173,9 +223,9 @@ async def edit_schema_database(request, datasette):
173
223
 
174
224
 
175
225
  async def edit_schema_create_table(request, datasette):
176
- databases = get_databases(datasette)
177
226
  database_name = request.url_vars["database"]
178
- await check_permissions(datasette, request, database_name)
227
+ if not await can_create_table(datasette, request.actor, database_name):
228
+ raise Forbidden("Permission denied for create-table")
179
229
  try:
180
230
  db = datasette.get_database(database_name)
181
231
  except KeyError:
@@ -210,17 +260,18 @@ async def edit_schema_create_table(request, datasette):
210
260
  def create_the_table(conn):
211
261
  db = sqlite_utils.Database(conn)
212
262
  if not table_name.strip():
213
- return "Table name is required"
263
+ return None, "Table name is required"
214
264
  if db[table_name].exists():
215
- return "Table already exists"
265
+ return None, "Table already exists"
216
266
  try:
217
267
  db[table_name].create(
218
268
  create, pk=primary_key_name, not_null=(primary_key_name,)
219
269
  )
270
+ return db[table_name].schema, None
220
271
  except Exception as e:
221
- return str(e)
272
+ return None, str(e)
222
273
 
223
- error = await db.execute_write_fn(create_the_table, block=True)
274
+ schema, error = await db.execute_write_fn(create_the_table, block=True)
224
275
 
225
276
  if error:
226
277
  datasette.add_message(request, str(error), datasette.ERROR)
@@ -228,6 +279,14 @@ async def edit_schema_create_table(request, datasette):
228
279
  else:
229
280
  datasette.add_message(request, "Table has been created")
230
281
  path = datasette.urls.table(database_name, table_name)
282
+ await datasette.track_event(
283
+ CreateTableEvent(
284
+ actor=request.actor,
285
+ database=database_name,
286
+ table=table_name,
287
+ schema=schema,
288
+ )
289
+ )
231
290
 
232
291
  return Response.redirect(path)
233
292
 
@@ -251,7 +310,10 @@ async def edit_schema_table(request, datasette):
251
310
  table = unquote_plus(request.url_vars["table"])
252
311
  databases = get_databases(datasette)
253
312
  database_name = request.url_vars["database"]
254
- await check_permissions(datasette, request, database_name)
313
+
314
+ if not await can_alter_table(datasette, request.actor, database_name, table):
315
+ raise Forbidden("Permission denied for alter-table")
316
+
255
317
  try:
256
318
  database = [db for db in databases if db.name == database_name][0]
257
319
  except IndexError:
@@ -260,6 +322,29 @@ async def edit_schema_table(request, datasette):
260
322
  raise NotFound("Table not found")
261
323
 
262
324
  if request.method == "POST":
325
+
326
+ def get_schema(conn):
327
+ table_obj = sqlite_utils.Database(conn)[table]
328
+ if not table_obj.exists():
329
+ return None
330
+ return table_obj.schema
331
+
332
+ before_schema = await database.execute_fn(get_schema)
333
+
334
+ async def track_analytics():
335
+ after_schema = await database.execute_fn(get_schema)
336
+ # Don't track drop tables, which happen when after_schema is None
337
+ if after_schema is not None and after_schema != before_schema:
338
+ await datasette.track_event(
339
+ AlterTableEvent(
340
+ actor=request.actor,
341
+ database=database_name,
342
+ table=table,
343
+ before_schema=before_schema,
344
+ after_schema=after_schema,
345
+ )
346
+ )
347
+
263
348
  formdata = await request.post_vars()
264
349
  if formdata.get("action") == "update_columns":
265
350
  types = {}
@@ -315,31 +400,35 @@ async def edit_schema_table(request, datasette):
315
400
  await database.execute_write_fn(transform_the_table, block=True)
316
401
 
317
402
  datasette.add_message(request, "Changes to table have been saved")
318
-
403
+ await track_analytics()
319
404
  return Response.redirect(request.path)
320
405
 
321
406
  if formdata.get("action") == "update_foreign_keys":
322
- return await update_foreign_keys(
407
+ response = await update_foreign_keys(
323
408
  request, datasette, database, table, formdata
324
409
  )
325
410
  elif formdata.get("action") == "update_primary_key":
326
- return await update_primary_key(
411
+ response = await update_primary_key(
327
412
  request, datasette, database, table, formdata
328
413
  )
329
- elif "delete_table" in formdata:
330
- return await delete_table(request, datasette, database, table)
414
+ elif "drop_table" in formdata:
415
+ response = await drop_table(request, datasette, database, table)
331
416
  elif "add_column" in formdata:
332
- return await add_column(request, datasette, database, table, formdata)
417
+ response = await add_column(request, datasette, database, table, formdata)
333
418
  elif "rename_table" in formdata:
334
- return await rename_table(request, datasette, database, table, formdata)
419
+ response = await rename_table(request, datasette, database, table, formdata)
335
420
  elif "add_index" in formdata:
336
421
  column = formdata.get("add_index_column") or ""
337
422
  unique = formdata.get("add_index_unique")
338
- return await add_index(request, datasette, database, table, column, unique)
423
+ response = await add_index(
424
+ request, datasette, database, table, column, unique
425
+ )
339
426
  elif any(key.startswith("drop_index_") for key in formdata.keys()):
340
- return await drop_index(request, datasette, database, table, formdata)
427
+ response = await drop_index(request, datasette, database, table, formdata)
341
428
  else:
342
- return Response.html("Unknown operation", status=400)
429
+ response = Response.html("Unknown operation", status=400)
430
+ await track_analytics()
431
+ return response
343
432
 
344
433
  def get_columns_and_schema_and_fks_and_pks_and_indexes(conn):
345
434
  db = sqlite_utils.Database(conn)
@@ -354,10 +443,11 @@ async def edit_schema_table(request, datasette):
354
443
  textwrap.dedent(
355
444
  """
356
445
  select group_concat(sql, ';
357
- ') from sqlite_master where tbl_name = 'Orders'
446
+ ') from sqlite_master where tbl_name = ?
358
447
  order by type desc
359
448
  """
360
- )
449
+ ),
450
+ [table],
361
451
  ).fetchone()[0]
362
452
  return columns, schema, t.foreign_keys, t.pks, t.indexes
363
453
 
@@ -394,19 +484,20 @@ async def edit_schema_table(request, datasette):
394
484
  (pair[0], pair[1]) for pair in other_primary_keys if pair[2] is str
395
485
  ]
396
486
 
397
- column_foreign_keys = [
487
+ all_columns_to_manage_foreign_keys = [
398
488
  {
399
489
  "name": column["name"],
400
- "foreign_key": foreign_keys_by_column.get(column["name"])[0]
401
- if foreign_keys_by_column.get(column["name"])
402
- else None,
490
+ "foreign_key": (
491
+ foreign_keys_by_column.get(column["name"])[0]
492
+ if foreign_keys_by_column.get(column["name"])
493
+ else None
494
+ ),
403
495
  "suggestions": [],
404
- "options": integer_primary_keys
405
- if column["type"] is int
406
- else string_primary_keys,
496
+ "options": (
497
+ integer_primary_keys if column["type"] is int else string_primary_keys
498
+ ),
407
499
  }
408
500
  for column in columns
409
- if not column["is_pk"]
410
501
  ]
411
502
 
412
503
  # Anything not a float or an existing PK could be the next PK, but
@@ -433,7 +524,7 @@ async def edit_schema_table(request, datasette):
433
524
  other_primary_keys,
434
525
  )
435
526
  )
436
- for info in column_foreign_keys:
527
+ for info in all_columns_to_manage_foreign_keys:
437
528
  info["suggestions"] = potential_fks.get(info["name"], [])
438
529
  # Now do potential primary keys against non-float columns
439
530
  non_float_columns = [
@@ -444,7 +535,7 @@ async def edit_schema_table(request, datasette):
444
535
  )
445
536
 
446
537
  # Add 'options' to those
447
- for info in column_foreign_keys:
538
+ for info in all_columns_to_manage_foreign_keys:
448
539
  options = []
449
540
  seen = set()
450
541
  info["html_options"] = options
@@ -516,29 +607,49 @@ async def edit_schema_table(request, datasette):
516
607
  for value in TYPES.values()
517
608
  ],
518
609
  "foreign_keys": foreign_keys,
519
- "column_foreign_keys": column_foreign_keys,
610
+ "all_columns_to_manage_foreign_keys": all_columns_to_manage_foreign_keys,
520
611
  "potential_pks": potential_pks,
521
612
  "is_rowid_table": bool(pks == ["rowid"]),
522
613
  "current_pk": pks[0] if len(pks) == 1 else None,
523
614
  "existing_indexes": existing_indexes,
524
615
  "non_primary_key_columns": non_primary_key_columns,
616
+ "can_drop_table": await can_drop_table(
617
+ datasette, request.actor, database_name, table
618
+ ),
619
+ "can_rename_table": await can_rename_table(
620
+ datasette, request.actor, database_name, table
621
+ ),
525
622
  },
526
623
  request=request,
527
624
  )
528
625
  )
529
626
 
530
627
 
531
- async def delete_table(request, datasette, database, table):
532
- def do_delete_table(conn):
628
+ async def drop_table(request, datasette, database, table):
629
+ if not await can_drop_table(datasette, request.actor, database.name, table):
630
+ raise Forbidden("Permission denied for drop-table")
631
+
632
+ def do_drop_table(conn):
533
633
  db = sqlite_utils.Database(conn)
534
634
  db[table].disable_fts()
535
635
  db[table].drop()
536
636
  db.vacuum()
537
637
 
538
- await datasette.databases[database.name].execute_write_fn(
539
- do_delete_table, block=True
540
- )
638
+ if hasattr(database, "execute_isolated_fn"):
639
+ await database.execute_isolated_fn(do_drop_table)
640
+ # For the tests
641
+ datasette._datasette_edit_schema_used_execute_isolated_fn = True
642
+ else:
643
+ await database.execute_write_fn(do_drop_table)
644
+
541
645
  datasette.add_message(request, "Table has been deleted")
646
+ await datasette.track_event(
647
+ DropTableEvent(
648
+ actor=request.actor,
649
+ database=database.name,
650
+ table=table,
651
+ )
652
+ )
542
653
  return Response.redirect("/-/edit-schema/" + database.name)
543
654
 
544
655
 
@@ -601,7 +712,19 @@ async def rename_table(request, datasette, database, table, formdata):
601
712
  )
602
713
  return redirect
603
714
 
715
+ # User must have drop-table permission on old table and create-table on new table
716
+ if not await can_rename_table(datasette, request.actor, database.name, table):
717
+ datasette.add_message(
718
+ request,
719
+ "Permission denied to rename table '{}'".format(table),
720
+ datasette.ERROR,
721
+ )
722
+ return redirect
723
+
604
724
  try:
725
+ before_schema = await database.execute_fn(
726
+ lambda conn: sqlite_utils.Database(conn)[table].schema
727
+ )
605
728
  await database.execute_write(
606
729
  """
607
730
  ALTER TABLE [{}] RENAME TO [{}];
@@ -610,9 +733,22 @@ async def rename_table(request, datasette, database, table, formdata):
610
733
  ),
611
734
  block=True,
612
735
  )
736
+ after_schema = await database.execute_fn(
737
+ lambda conn: sqlite_utils.Database(conn)[new_name].schema
738
+ )
613
739
  datasette.add_message(
614
740
  request, "Table renamed to '{}'".format(new_name), datasette.INFO
615
741
  )
742
+ await datasette.track_event(
743
+ AlterTableEvent(
744
+ actor=request.actor,
745
+ database=database.name,
746
+ table=new_name,
747
+ before_schema=before_schema,
748
+ after_schema=after_schema,
749
+ )
750
+ )
751
+
616
752
  except Exception as error:
617
753
  datasette.add_message(
618
754
  request, "Error renaming table: {}".format(str(error)), datasette.ERROR
@@ -22,6 +22,9 @@ select {
22
22
  font-size: 1em;
23
23
  font-family: Helvetica, sans-serif;
24
24
  }
25
+ select.select-smaller {
26
+ width: 90%;
27
+ }
25
28
  html body label {
26
29
  font-weight: bold;
27
30
  display: inline-block;
@@ -81,14 +84,14 @@ html body label {
81
84
  <form action="{{ base_url }}-/edit-schema/{{ database.name|quote_plus }}/-/create" method="post">
82
85
  <p>
83
86
  <label for="table_name">Table name: &nbsp;</label>
84
- <input type="text" required="1" id="table_name" name="table_name" size="20" style="width: 30%">
87
+ <input type="text" required="1" id="table_name" name="table_name" size="20" style="width: 25%">
85
88
  </p>
86
89
  <h2>Columns</h2>
87
90
  <p style="font-size: 0.8em;">If the primary key is an integer it will automatically count up from 1</p>
88
91
  <ul class="editable-columns">
89
92
  <!-- primary key comes first and is not sortable -->
90
93
  <li>
91
- <input style="width: 30%" type="text" size="10" name="primary_key_name" value="id">
94
+ <input style="width: 25%" type="text" size="10" name="primary_key_name" value="id">
92
95
  <label>Type: <select name="primary_key_type">
93
96
  <option value="INTEGER" selected="selected">Integer</option>
94
97
  <option value="TEXT">Text</option>
@@ -99,7 +102,7 @@ html body label {
99
102
  <ul class="sortable-columns editable-columns">
100
103
  {% for column in columns %}
101
104
  <li>
102
- <input style="width: 30%" type="text" size="10" name="column-name.{{ loop.index }}" value="">
105
+ <input style="width: 25%" type="text" size="10" name="column-name.{{ loop.index }}" value="">
103
106
  <label>Type: <select name="column-type.{{ loop.index }}">
104
107
  {% for type in types %}
105
108
  <option value="{{ type.value }}">{{ type.name }}</option>