mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Refactor to use new resolve_database/table/row methods, refs #1896
This commit is contained in:
parent
c588a89f26
commit
ee64130fa8
8 changed files with 254 additions and 133 deletions
|
|
@ -62,13 +62,19 @@ from .utils import (
|
||||||
parse_metadata,
|
parse_metadata,
|
||||||
resolve_env_secrets,
|
resolve_env_secrets,
|
||||||
resolve_routes,
|
resolve_routes,
|
||||||
|
tilde_decode,
|
||||||
to_css_class,
|
to_css_class,
|
||||||
|
urlsafe_components,
|
||||||
|
row_sql_params_pks,
|
||||||
)
|
)
|
||||||
from .utils.asgi import (
|
from .utils.asgi import (
|
||||||
AsgiLifespan,
|
AsgiLifespan,
|
||||||
Base400,
|
Base400,
|
||||||
Forbidden,
|
Forbidden,
|
||||||
NotFound,
|
NotFound,
|
||||||
|
DatabaseNotFound,
|
||||||
|
TableNotFound,
|
||||||
|
RowNotFound,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
asgi_static,
|
asgi_static,
|
||||||
|
|
@ -198,6 +204,12 @@ async def favicon(request, send):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ResolvedTable = collections.namedtuple("ResolvedTable", ("db", "table", "is_view"))
|
||||||
|
ResolvedRow = collections.namedtuple(
|
||||||
|
"ResolvedRow", ("db", "table", "sql", "params", "pks", "pk_values", "row")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Datasette:
|
class Datasette:
|
||||||
# Message constants:
|
# Message constants:
|
||||||
INFO = 1
|
INFO = 1
|
||||||
|
|
@ -1292,6 +1304,41 @@ class Datasette:
|
||||||
for pattern, view in routes
|
for pattern, view in routes
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async def resolve_database(self, request):
|
||||||
|
database_route = tilde_decode(request.url_vars["database"])
|
||||||
|
try:
|
||||||
|
return self.get_database(route=database_route)
|
||||||
|
except KeyError:
|
||||||
|
raise DatabaseNotFound(
|
||||||
|
"Database not found: {}".format(database_route), database_route
|
||||||
|
)
|
||||||
|
|
||||||
|
async def resolve_table(self, request):
|
||||||
|
db = await self.resolve_database(request)
|
||||||
|
table_name = tilde_decode(request.url_vars["table"])
|
||||||
|
# Table must exist
|
||||||
|
is_view = False
|
||||||
|
table_exists = await db.table_exists(table_name)
|
||||||
|
if not table_exists:
|
||||||
|
is_view = await db.view_exists(table_name)
|
||||||
|
if not (table_exists or is_view):
|
||||||
|
raise TableNotFound(
|
||||||
|
"Table not found: {}".format(table_name), db.name, table_name
|
||||||
|
)
|
||||||
|
return ResolvedTable(db, table_name, is_view)
|
||||||
|
|
||||||
|
async def resolve_row(self, request):
|
||||||
|
db, table_name, _ = await self.resolve_table(request)
|
||||||
|
pk_values = urlsafe_components(request.url_vars["pks"])
|
||||||
|
sql, params, pks = await row_sql_params_pks(db, table_name, pk_values)
|
||||||
|
results = await db.execute(sql, params, truncate=True)
|
||||||
|
row = results.first()
|
||||||
|
if row is None:
|
||||||
|
raise RowNotFound(
|
||||||
|
"Row not found: {}".format(pk_values), db.name, table_name, pk_values
|
||||||
|
)
|
||||||
|
return ResolvedRow(db, table_name, sql, params, pks, pk_values, results.first())
|
||||||
|
|
||||||
def app(self):
|
def app(self):
|
||||||
"""Returns an ASGI app function that serves the whole of Datasette"""
|
"""Returns an ASGI app function that serves the whole of Datasette"""
|
||||||
routes = self._routes()
|
routes = self._routes()
|
||||||
|
|
|
||||||
|
|
@ -1193,3 +1193,18 @@ def truncate_url(url, length):
|
||||||
rest, ext = bits
|
rest, ext = bits
|
||||||
return rest[: length - 1 - len(ext)] + "…." + ext
|
return rest[: length - 1 - len(ext)] + "…." + ext
|
||||||
return url[: length - 1] + "…"
|
return url[: length - 1] + "…"
|
||||||
|
|
||||||
|
|
||||||
|
async def row_sql_params_pks(db, table, pk_values):
|
||||||
|
pks = await db.primary_keys(table)
|
||||||
|
use_rowid = not pks
|
||||||
|
select = "*"
|
||||||
|
if use_rowid:
|
||||||
|
select = "rowid, *"
|
||||||
|
pks = ["rowid"]
|
||||||
|
wheres = [f'"{pk}"=:p{i}' for i, pk in enumerate(pks)]
|
||||||
|
sql = f"select {select} from {escape_sqlite(table)} where {' AND '.join(wheres)}"
|
||||||
|
params = {}
|
||||||
|
for i, pk_value in enumerate(pk_values):
|
||||||
|
params[f"p{i}"] = pk_value
|
||||||
|
return sql, params, pks
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,27 @@ class NotFound(Base400):
|
||||||
status = 404
|
status = 404
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseNotFound(NotFound):
|
||||||
|
def __init__(self, message, database_name):
|
||||||
|
super().__init__(message)
|
||||||
|
self.database_name = database_name
|
||||||
|
|
||||||
|
|
||||||
|
class TableNotFound(NotFound):
|
||||||
|
def __init__(self, message, database_name, table):
|
||||||
|
super().__init__(message)
|
||||||
|
self.database_name = database_name
|
||||||
|
self.table = table
|
||||||
|
|
||||||
|
|
||||||
|
class RowNotFound(NotFound):
|
||||||
|
def __init__(self, message, database_name, table, pk_values):
|
||||||
|
super().__init__(message)
|
||||||
|
self.database_name = database_name
|
||||||
|
self.table_name = table
|
||||||
|
self.pk_values = pk_values
|
||||||
|
|
||||||
|
|
||||||
class Forbidden(Base400):
|
class Forbidden(Base400):
|
||||||
status = 403
|
status = 403
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ from datasette.utils import (
|
||||||
InvalidSql,
|
InvalidSql,
|
||||||
LimitedWriter,
|
LimitedWriter,
|
||||||
call_with_supported_arguments,
|
call_with_supported_arguments,
|
||||||
tilde_decode,
|
|
||||||
path_from_row_pks,
|
path_from_row_pks,
|
||||||
path_with_added_args,
|
path_with_added_args,
|
||||||
path_with_removed_args,
|
path_with_removed_args,
|
||||||
|
|
@ -346,13 +345,9 @@ class DataView(BaseView):
|
||||||
return AsgiStream(stream_fn, headers=headers, content_type=content_type)
|
return AsgiStream(stream_fn, headers=headers, content_type=content_type)
|
||||||
|
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
db = await self.ds.resolve_database(request)
|
||||||
|
|
||||||
try:
|
|
||||||
db = self.ds.get_database(route=database_route)
|
|
||||||
except KeyError:
|
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
|
||||||
database = db.name
|
database = db.name
|
||||||
|
database_route = db.route
|
||||||
|
|
||||||
_format = request.url_vars["format"]
|
_format = request.url_vars["format"]
|
||||||
data_kwargs = {}
|
data_kwargs = {}
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,7 @@ class DatabaseView(DataView):
|
||||||
name = "database"
|
name = "database"
|
||||||
|
|
||||||
async def data(self, request, default_labels=False, _size=None):
|
async def data(self, request, default_labels=False, _size=None):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
db = await self.ds.resolve_database(request)
|
||||||
try:
|
|
||||||
db = self.ds.get_database(route=database_route)
|
|
||||||
except KeyError:
|
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
|
||||||
database = db.name
|
database = db.name
|
||||||
|
|
||||||
visible, private = await self.ds.check_visibility(
|
visible, private = await self.ds.check_visibility(
|
||||||
|
|
@ -228,11 +224,7 @@ class QueryView(DataView):
|
||||||
named_parameters=None,
|
named_parameters=None,
|
||||||
write=False,
|
write=False,
|
||||||
):
|
):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
db = await self.ds.resolve_database(request)
|
||||||
try:
|
|
||||||
db = self.ds.get_database(route=database_route)
|
|
||||||
except KeyError:
|
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
|
||||||
database = db.name
|
database = db.name
|
||||||
params = {key: request.args.get(key) for key in request.args}
|
params = {key: request.args.get(key) for key in request.args}
|
||||||
if "sql" in params:
|
if "sql" in params:
|
||||||
|
|
@ -582,11 +574,7 @@ class TableCreateView(BaseView):
|
||||||
self.ds = datasette
|
self.ds = datasette
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
db = await self.ds.resolve_database(request)
|
||||||
try:
|
|
||||||
db = self.ds.get_database(route=database_route)
|
|
||||||
except KeyError:
|
|
||||||
return _error(["Database not found: {}".format(database_route)], 404)
|
|
||||||
database_name = db.name
|
database_name = db.name
|
||||||
|
|
||||||
# Must have create-table permission
|
# Must have create-table permission
|
||||||
|
|
@ -727,11 +715,7 @@ class TableCreateView(BaseView):
|
||||||
self.ds = datasette
|
self.ds = datasette
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
db = await self.ds.resolve_database(request)
|
||||||
try:
|
|
||||||
db = self.ds.get_database(route=database_route)
|
|
||||||
except KeyError:
|
|
||||||
return _error(["Database not found: {}".format(database_route)], 404)
|
|
||||||
database_name = db.name
|
database_name = db.name
|
||||||
|
|
||||||
# Must have create-table permission
|
# Must have create-table permission
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,21 @@ from datasette.utils import (
|
||||||
urlsafe_components,
|
urlsafe_components,
|
||||||
to_css_class,
|
to_css_class,
|
||||||
escape_sqlite,
|
escape_sqlite,
|
||||||
|
row_sql_params_pks,
|
||||||
)
|
)
|
||||||
|
import json
|
||||||
import sqlite_utils
|
import sqlite_utils
|
||||||
from .table import _sql_params_pks, display_columns_and_rows
|
from .table import display_columns_and_rows
|
||||||
|
|
||||||
|
|
||||||
class RowView(DataView):
|
class RowView(DataView):
|
||||||
name = "row"
|
name = "row"
|
||||||
|
|
||||||
async def data(self, request, default_labels=False):
|
async def data(self, request, default_labels=False):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
resolved = await self.ds.resolve_row(request)
|
||||||
table = tilde_decode(request.url_vars["table"])
|
database = resolved.db.name
|
||||||
try:
|
table = resolved.table
|
||||||
db = self.ds.get_database(route=database_route)
|
pk_values = resolved.pk_values
|
||||||
except KeyError:
|
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
|
||||||
database = db.name
|
|
||||||
|
|
||||||
# Ensure user has permission to view this row
|
# Ensure user has permission to view this row
|
||||||
visible, private = await self.ds.check_visibility(
|
visible, private = await self.ds.check_visibility(
|
||||||
|
|
@ -35,14 +34,9 @@ class RowView(DataView):
|
||||||
if not visible:
|
if not visible:
|
||||||
raise Forbidden("You do not have permission to view this table")
|
raise Forbidden("You do not have permission to view this table")
|
||||||
|
|
||||||
pk_values = urlsafe_components(request.url_vars["pks"])
|
results = await resolved.db.execute(
|
||||||
try:
|
resolved.sql, resolved.params, truncate=True
|
||||||
db = self.ds.get_database(route=database_route)
|
)
|
||||||
except KeyError:
|
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
|
||||||
database = db.name
|
|
||||||
sql, params, pks = await _sql_params_pks(db, table, pk_values)
|
|
||||||
results = await db.execute(sql, params, truncate=True)
|
|
||||||
columns = [r[0] for r in results.description]
|
columns = [r[0] for r in results.description]
|
||||||
rows = list(results.rows)
|
rows = list(results.rows)
|
||||||
if not rows:
|
if not rows:
|
||||||
|
|
@ -83,7 +77,7 @@ class RowView(DataView):
|
||||||
"table": table,
|
"table": table,
|
||||||
"rows": rows,
|
"rows": rows,
|
||||||
"columns": columns,
|
"columns": columns,
|
||||||
"primary_keys": pks,
|
"primary_keys": resolved.pks,
|
||||||
"primary_key_values": pk_values,
|
"primary_key_values": pk_values,
|
||||||
"units": self.ds.table_metadata(database, table).get("units", {}),
|
"units": self.ds.table_metadata(database, table).get("units", {}),
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +143,11 @@ class RowView(DataView):
|
||||||
return foreign_key_tables
|
return foreign_key_tables
|
||||||
|
|
||||||
|
|
||||||
|
class RowError(Exception):
|
||||||
|
def __init__(self, error):
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
|
||||||
class RowDeleteView(BaseView):
|
class RowDeleteView(BaseView):
|
||||||
name = "row-delete"
|
name = "row-delete"
|
||||||
|
|
||||||
|
|
@ -156,24 +155,20 @@ class RowDeleteView(BaseView):
|
||||||
self.ds = datasette
|
self.ds = datasette
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
from datasette.app import DatabaseNotFound, TableNotFound, RowNotFound
|
||||||
table = tilde_decode(request.url_vars["table"])
|
|
||||||
try:
|
try:
|
||||||
db = self.ds.get_database(route=database_route)
|
resolved = await self.ds.resolve_row(request)
|
||||||
except KeyError:
|
except DatabaseNotFound as e:
|
||||||
return _error(["Database not found: {}".format(database_route)], 404)
|
return _error(["Database not found: {}".format(e.database_name)], 404)
|
||||||
|
except TableNotFound as e:
|
||||||
|
return _error(["Table not found: {}".format(e.table)], 404)
|
||||||
|
except RowNotFound as e:
|
||||||
|
return _error(["Record not found: {}".format(e.pk_values)], 404)
|
||||||
|
db = resolved.db
|
||||||
database_name = db.name
|
database_name = db.name
|
||||||
if not await db.table_exists(table):
|
table = resolved.table
|
||||||
return _error(["Table not found: {}".format(table)], 404)
|
pk_values = resolved.pk_values
|
||||||
|
|
||||||
pk_values = urlsafe_components(request.url_vars["pks"])
|
|
||||||
|
|
||||||
sql, params, pks = await _sql_params_pks(db, table, pk_values)
|
|
||||||
results = await db.execute(sql, params, truncate=True)
|
|
||||||
rows = list(results.rows)
|
|
||||||
if not rows:
|
|
||||||
return _error([f"Record not found: {pk_values}"], 404)
|
|
||||||
|
|
||||||
# Ensure user has permission to delete this row
|
# Ensure user has permission to delete this row
|
||||||
if not await self.ds.permission_allowed(
|
if not await self.ds.permission_allowed(
|
||||||
|
|
|
||||||
|
|
@ -93,36 +93,33 @@ class TableView(DataView):
|
||||||
return expandables
|
return expandables
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
from datasette.app import TableNotFound
|
||||||
try:
|
|
||||||
db = self.ds.get_database(route=database_route)
|
|
||||||
except KeyError:
|
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
|
||||||
database_name = db.name
|
|
||||||
table_name = tilde_decode(request.url_vars["table"])
|
|
||||||
# Handle POST to a canned query
|
|
||||||
canned_query = await self.ds.get_canned_query(
|
|
||||||
database_name, table_name, request.actor
|
|
||||||
)
|
|
||||||
if canned_query:
|
|
||||||
return await QueryView(self.ds).data(
|
|
||||||
request,
|
|
||||||
canned_query["sql"],
|
|
||||||
metadata=canned_query,
|
|
||||||
editable=False,
|
|
||||||
canned_query=table_name,
|
|
||||||
named_parameters=canned_query.get("params"),
|
|
||||||
write=bool(canned_query.get("write")),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Handle POST to a table
|
|
||||||
return await self.table_post(request, database_name, table_name)
|
|
||||||
|
|
||||||
async def table_post(self, request, database_name, table_name):
|
try:
|
||||||
# Table must exist (may handle table creation in the future)
|
resolved = await self.ds.resolve_table(request)
|
||||||
db = self.ds.get_database(database_name)
|
except TableNotFound as e:
|
||||||
if not await db.table_exists(table_name):
|
# Was this actually a canned query?
|
||||||
raise NotFound("Table not found: {}".format(table_name))
|
canned_query = await self.ds.get_canned_query(
|
||||||
|
e.database_name, e.table, request.actor
|
||||||
|
)
|
||||||
|
if canned_query:
|
||||||
|
# Handle POST to a canned query
|
||||||
|
return await QueryView(self.ds).data(
|
||||||
|
request,
|
||||||
|
canned_query["sql"],
|
||||||
|
metadata=canned_query,
|
||||||
|
editable=False,
|
||||||
|
canned_query=e.table,
|
||||||
|
named_parameters=canned_query.get("params"),
|
||||||
|
write=bool(canned_query.get("write")),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle POST to a table
|
||||||
|
return await self.table_post(
|
||||||
|
request, resolved.db, resolved.db.name, resolved.table
|
||||||
|
)
|
||||||
|
|
||||||
|
async def table_post(self, request, db, database_name, table_name):
|
||||||
# Must have insert-row permission
|
# Must have insert-row permission
|
||||||
if not await self.ds.permission_allowed(
|
if not await self.ds.permission_allowed(
|
||||||
request.actor, "insert-row", resource=(database_name, table_name)
|
request.actor, "insert-row", resource=(database_name, table_name)
|
||||||
|
|
@ -221,12 +218,31 @@ class TableView(DataView):
|
||||||
_next=None,
|
_next=None,
|
||||||
_size=None,
|
_size=None,
|
||||||
):
|
):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
from datasette.app import TableNotFound
|
||||||
table_name = tilde_decode(request.url_vars["table"])
|
|
||||||
try:
|
try:
|
||||||
db = self.ds.get_database(route=database_route)
|
resolved = await self.ds.resolve_table(request)
|
||||||
except KeyError:
|
except TableNotFound as e:
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
# Was this actually a canned query?
|
||||||
|
canned_query = await self.ds.get_canned_query(
|
||||||
|
e.database_name, e.table, request.actor
|
||||||
|
)
|
||||||
|
# If this is a canned query, not a table, then dispatch to QueryView instead
|
||||||
|
if canned_query:
|
||||||
|
return await QueryView(self.ds).data(
|
||||||
|
request,
|
||||||
|
canned_query["sql"],
|
||||||
|
metadata=canned_query,
|
||||||
|
editable=False,
|
||||||
|
canned_query=e.table,
|
||||||
|
named_parameters=canned_query.get("params"),
|
||||||
|
write=bool(canned_query.get("write")),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
table_name = resolved.table
|
||||||
|
db = resolved.db
|
||||||
database_name = db.name
|
database_name = db.name
|
||||||
|
|
||||||
# For performance profiling purposes, ?_noparallel=1 turns off asyncio.gather
|
# For performance profiling purposes, ?_noparallel=1 turns off asyncio.gather
|
||||||
|
|
@ -243,21 +259,6 @@ class TableView(DataView):
|
||||||
_gather_sequential if request.args.get("_noparallel") else _gather_parallel
|
_gather_sequential if request.args.get("_noparallel") else _gather_parallel
|
||||||
)
|
)
|
||||||
|
|
||||||
# If this is a canned query, not a table, then dispatch to QueryView instead
|
|
||||||
canned_query = await self.ds.get_canned_query(
|
|
||||||
database_name, table_name, request.actor
|
|
||||||
)
|
|
||||||
if canned_query:
|
|
||||||
return await QueryView(self.ds).data(
|
|
||||||
request,
|
|
||||||
canned_query["sql"],
|
|
||||||
metadata=canned_query,
|
|
||||||
editable=False,
|
|
||||||
canned_query=table_name,
|
|
||||||
named_parameters=canned_query.get("params"),
|
|
||||||
write=bool(canned_query.get("write")),
|
|
||||||
)
|
|
||||||
|
|
||||||
is_view, table_exists = map(
|
is_view, table_exists = map(
|
||||||
bool,
|
bool,
|
||||||
await gather(
|
await gather(
|
||||||
|
|
@ -874,21 +875,6 @@ class TableView(DataView):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _sql_params_pks(db, table, pk_values):
|
|
||||||
pks = await db.primary_keys(table)
|
|
||||||
use_rowid = not pks
|
|
||||||
select = "*"
|
|
||||||
if use_rowid:
|
|
||||||
select = "rowid, *"
|
|
||||||
pks = ["rowid"]
|
|
||||||
wheres = [f'"{pk}"=:p{i}' for i, pk in enumerate(pks)]
|
|
||||||
sql = f"select {select} from {escape_sqlite(table)} where {' AND '.join(wheres)}"
|
|
||||||
params = {}
|
|
||||||
for i, pk_value in enumerate(pk_values):
|
|
||||||
params[f"p{i}"] = pk_value
|
|
||||||
return sql, params, pks
|
|
||||||
|
|
||||||
|
|
||||||
async def display_columns_and_rows(
|
async def display_columns_and_rows(
|
||||||
datasette,
|
datasette,
|
||||||
database_name,
|
database_name,
|
||||||
|
|
@ -1161,13 +1147,13 @@ class TableInsertView(BaseView):
|
||||||
return rows, errors, extras
|
return rows, errors, extras
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
|
||||||
try:
|
try:
|
||||||
db = self.ds.get_database(route=database_route)
|
resolved = await self.ds.resolve_table(request)
|
||||||
except KeyError:
|
except NotFound as e:
|
||||||
return _error(["Database not found: {}".format(database_route)], 404)
|
return _error([e.args[0]], 404)
|
||||||
|
db = resolved.db
|
||||||
database_name = db.name
|
database_name = db.name
|
||||||
table_name = tilde_decode(request.url_vars["table"])
|
table_name = resolved.table
|
||||||
|
|
||||||
# Table must exist (may handle table creation in the future)
|
# Table must exist (may handle table creation in the future)
|
||||||
db = self.ds.get_database(database_name)
|
db = self.ds.get_database(database_name)
|
||||||
|
|
@ -1221,13 +1207,13 @@ class TableDropView(BaseView):
|
||||||
self.ds = datasette
|
self.ds = datasette
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
database_route = tilde_decode(request.url_vars["database"])
|
|
||||||
try:
|
try:
|
||||||
db = self.ds.get_database(route=database_route)
|
resolved = await self.ds.resolve_table(request)
|
||||||
except KeyError:
|
except NotFound as e:
|
||||||
return _error(["Database not found: {}".format(database_route)], 404)
|
return _error([e.args[0]], 404)
|
||||||
|
db = resolved.db
|
||||||
database_name = db.name
|
database_name = db.name
|
||||||
table_name = tilde_decode(request.url_vars["table"])
|
table_name = resolved.table
|
||||||
# Table must exist
|
# Table must exist
|
||||||
db = self.ds.get_database(database_name)
|
db = self.ds.get_database(database_name)
|
||||||
if not await db.table_exists(table_name):
|
if not await db.table_exists(table_name):
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,84 @@ For example:
|
||||||
|
|
||||||
downloads_are_allowed = datasette.setting("allow_download")
|
downloads_are_allowed = datasette.setting("allow_download")
|
||||||
|
|
||||||
|
.. _datasette_resolve_database:
|
||||||
|
|
||||||
|
.resolve_database(request)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
``request`` - :ref:`internals_request`
|
||||||
|
A request object
|
||||||
|
|
||||||
|
If you are implementing your own custom views, you may need to resolve the database that the user is requesting based on a URL path. If the regular expression for your route declares a ``database`` named group, you can use this method to resolve the database object.
|
||||||
|
|
||||||
|
This returns a :ref:`Database <internals_database>` instance.
|
||||||
|
|
||||||
|
If the database cannot be found, it raises a ``datasette.utils.asgi.DatabaseNotFound`` exception - which is a subclass of ``datasette.utils.asgi.NotFound`` with a ``.database_name`` attribute set to the name of the database that was requested.
|
||||||
|
|
||||||
|
.. _datasette_resolve_table:
|
||||||
|
|
||||||
|
.resolve_table(request)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
``request`` - :ref:`internals_request`
|
||||||
|
A request object
|
||||||
|
|
||||||
|
This assumes that the regular expression for your route declares both a ``database`` and a ``table`` named group.
|
||||||
|
|
||||||
|
It returns a ``ResolvedTable`` named tuple instance with the following fields:
|
||||||
|
|
||||||
|
``db`` - :ref:`Database <internals_database>`
|
||||||
|
The database object
|
||||||
|
|
||||||
|
``table`` - string
|
||||||
|
The name of the table (or view)
|
||||||
|
|
||||||
|
``is_view`` - boolean
|
||||||
|
``True`` if this is a view, ``False`` if it is a table
|
||||||
|
|
||||||
|
If the database or table cannot be found it raises a ``datasette.utils.asgi.DatabaseNotFound`` exception.
|
||||||
|
|
||||||
|
If the table does not exist it raises a ``datasette.utils.asgi.TableNotFound`` exception - a subclass of ``datasette.utils.asgi.NotFound`` with ``.database_name`` and ``.table`` attributes.
|
||||||
|
|
||||||
|
.. _datasette_resolve_row:
|
||||||
|
|
||||||
|
.resolve_row(request)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
``request`` - :ref:`internals_request`
|
||||||
|
A request object
|
||||||
|
|
||||||
|
This method assumes your route declares named groups for ``database``, ``table`` and ``pks``.
|
||||||
|
|
||||||
|
It returns a ``ResolvedRow`` named tuple instance with the following fields:
|
||||||
|
|
||||||
|
``db`` - :ref:`Database <internals_database>`
|
||||||
|
The database object
|
||||||
|
|
||||||
|
``table`` - string
|
||||||
|
The name of the table
|
||||||
|
|
||||||
|
``sql`` - string
|
||||||
|
SQL snippet that can be used in a ``WHERE`` clause to select the row
|
||||||
|
|
||||||
|
``params`` - dict
|
||||||
|
Parameters that should be passed to the SQL query
|
||||||
|
|
||||||
|
``pks`` - list
|
||||||
|
List of primary key column names
|
||||||
|
|
||||||
|
``pk_values`` - list
|
||||||
|
List of primary key values decoded from the URL
|
||||||
|
|
||||||
|
``row`` - ``sqlite3.Row``
|
||||||
|
The row itself
|
||||||
|
|
||||||
|
If the database or table cannot be found it raises a ``datasette.utils.asgi.DatabaseNotFound`` exception.
|
||||||
|
|
||||||
|
If the table does not exist it raises a ``datasette.utils.asgi.TableNotFound`` exception.
|
||||||
|
|
||||||
|
If the row cannot be found it raises a ``datasette.utils.asgi.RowNotFound`` exception. This has ``.database_name``, ``.table`` and ``.pk_values`` attributes, extracted from the request path.
|
||||||
|
|
||||||
.. _internals_datasette_client:
|
.. _internals_datasette_client:
|
||||||
|
|
||||||
datasette.client
|
datasette.client
|
||||||
|
|
@ -770,7 +848,7 @@ The ``Results`` object also has the following properties and methods:
|
||||||
``.columns`` - list of strings
|
``.columns`` - list of strings
|
||||||
A list of column names returned by the query.
|
A list of column names returned by the query.
|
||||||
|
|
||||||
``.rows`` - list of sqlite3.Row
|
``.rows`` - list of ``sqlite3.Row``
|
||||||
This property provides direct access to the list of rows returned by the database. You can access specific rows by index using ``results.rows[0]``.
|
This property provides direct access to the list of rows returned by the database. You can access specific rows by index using ``results.rows[0]``.
|
||||||
|
|
||||||
``.first()`` - row or None
|
``.first()`` - row or None
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue