mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
datasette.urls methods will not apply base_url prefix twice, refs #1026
This commit is contained in:
parent
c1d386ef67
commit
7a67bc7a56
4 changed files with 70 additions and 17 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
from .utils import path_with_format, HASH_LENGTH
|
from .utils import path_with_format, HASH_LENGTH, PrefixedUrlString
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7,12 +7,13 @@ class Urls:
|
||||||
self.ds = ds
|
self.ds = ds
|
||||||
|
|
||||||
def path(self, path, format=None):
|
def path(self, path, format=None):
|
||||||
if path.startswith("/"):
|
if not isinstance(path, PrefixedUrlString):
|
||||||
path = path[1:]
|
if path.startswith("/"):
|
||||||
path = self.ds.config("base_url") + path
|
path = path[1:]
|
||||||
|
path = self.ds.config("base_url") + path
|
||||||
if format is not None:
|
if format is not None:
|
||||||
path = path_with_format(path=path, format=format)
|
path = path_with_format(path=path, format=format)
|
||||||
return path
|
return PrefixedUrlString(path)
|
||||||
|
|
||||||
def instance(self, format=None):
|
def instance(self, format=None):
|
||||||
return self.path("", format=format)
|
return self.path("", format=format)
|
||||||
|
|
@ -40,19 +41,19 @@ class Urls:
|
||||||
path = "{}/{}".format(self.database(database), urllib.parse.quote_plus(table))
|
path = "{}/{}".format(self.database(database), urllib.parse.quote_plus(table))
|
||||||
if format is not None:
|
if format is not None:
|
||||||
path = path_with_format(path=path, format=format)
|
path = path_with_format(path=path, format=format)
|
||||||
return path
|
return PrefixedUrlString(path)
|
||||||
|
|
||||||
def query(self, database, query, format=None):
|
def query(self, database, query, format=None):
|
||||||
path = "{}/{}".format(self.database(database), urllib.parse.quote_plus(query))
|
path = "{}/{}".format(self.database(database), urllib.parse.quote_plus(query))
|
||||||
if format is not None:
|
if format is not None:
|
||||||
path = path_with_format(path=path, format=format)
|
path = path_with_format(path=path, format=format)
|
||||||
return path
|
return PrefixedUrlString(path)
|
||||||
|
|
||||||
def row(self, database, table, row_path, format=None):
|
def row(self, database, table, row_path, format=None):
|
||||||
path = "{}/{}".format(self.table(database, table), row_path)
|
path = "{}/{}".format(self.table(database, table), row_path)
|
||||||
if format is not None:
|
if format is not None:
|
||||||
path = path_with_format(path=path, format=format)
|
path = path_with_format(path=path, format=format)
|
||||||
return path
|
return PrefixedUrlString(path)
|
||||||
|
|
||||||
def row_blob(self, database, table, row_path, column):
|
def row_blob(self, database, table, row_path, column):
|
||||||
return self.table(database, table) + "/{}.blob?_blob_column={}".format(
|
return self.table(database, table) + "/{}.blob?_blob_column={}".format(
|
||||||
|
|
|
||||||
|
|
@ -1010,3 +1010,26 @@ async def initial_path_for_datasette(datasette):
|
||||||
else:
|
else:
|
||||||
path = datasette.urls.instance()
|
path = datasette.urls.instance()
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixedUrlString(str):
|
||||||
|
def __add__(self, other):
|
||||||
|
return type(self)(super().__add__(other))
|
||||||
|
|
||||||
|
def __getattribute__(self, name):
|
||||||
|
if name in dir(str):
|
||||||
|
|
||||||
|
def method(self, *args, **kwargs):
|
||||||
|
value = getattr(super(), name)(*args, **kwargs)
|
||||||
|
if isinstance(value, str):
|
||||||
|
return type(self)(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
return [type(self)(i) for i in value]
|
||||||
|
elif isinstance(value, tuple):
|
||||||
|
return tuple(type(self)(i) for i in value)
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
return method.__get__(self)
|
||||||
|
else:
|
||||||
|
return super().__getattribute__(name)
|
||||||
|
|
|
||||||
|
|
@ -443,6 +443,8 @@ These functions can be accessed via the ``{{ urls }}`` object in Datasette templ
|
||||||
|
|
||||||
Use the ``format="json"`` (or ``"csv"`` or other formats supported by plugins) arguments to get back URLs to the JSON representation. This is usually the path with ``.json`` added on the end, but it may use ``?_format=json`` in cases where the path already includes ``.json``, for example a URL to a table named ``table.json``.
|
Use the ``format="json"`` (or ``"csv"`` or other formats supported by plugins) arguments to get back URLs to the JSON representation. This is usually the path with ``.json`` added on the end, but it may use ``?_format=json`` in cases where the path already includes ``.json``, for example a URL to a table named ``table.json``.
|
||||||
|
|
||||||
|
These methods each return a ``datasette.utils.PrefixedUrlString`` object, which is a subclass of the Python ``str`` type. This allows the logic that considers the ``base_url`` setting to detect if that prefix has already been applied to the path.
|
||||||
|
|
||||||
.. _internals_database:
|
.. _internals_database:
|
||||||
|
|
||||||
Database class
|
Database class
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from datasette.app import Datasette
|
from datasette.app import Datasette
|
||||||
|
from datasette.utils import PrefixedUrlString
|
||||||
from .fixtures import app_client_with_hash
|
from .fixtures import app_client_with_hash
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -20,7 +21,17 @@ def ds():
|
||||||
)
|
)
|
||||||
def test_path(ds, base_url, path, expected):
|
def test_path(ds, base_url, path, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.path(path) == expected
|
actual = ds.urls.path(path)
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_applied_twice_does_not_double_prefix(ds):
|
||||||
|
ds._config["base_url"] = "/prefix/"
|
||||||
|
path = ds.urls.path("/")
|
||||||
|
assert path == "/prefix/"
|
||||||
|
path = ds.urls.path(path)
|
||||||
|
assert path == "/prefix/"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -32,7 +43,9 @@ def test_path(ds, base_url, path, expected):
|
||||||
)
|
)
|
||||||
def test_instance(ds, base_url, expected):
|
def test_instance(ds, base_url, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.instance() == expected
|
actual = ds.urls.instance()
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -44,7 +57,9 @@ def test_instance(ds, base_url, expected):
|
||||||
)
|
)
|
||||||
def test_static(ds, base_url, file, expected):
|
def test_static(ds, base_url, file, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.static(file) == expected
|
actual = ds.urls.static(file)
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -66,7 +81,9 @@ def test_static(ds, base_url, file, expected):
|
||||||
)
|
)
|
||||||
def test_static_plugins(ds, base_url, plugin, file, expected):
|
def test_static_plugins(ds, base_url, plugin, file, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.static_plugins(plugin, file) == expected
|
actual = ds.urls.static_plugins(plugin, file)
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -78,7 +95,9 @@ def test_static_plugins(ds, base_url, plugin, file, expected):
|
||||||
)
|
)
|
||||||
def test_logout(ds, base_url, expected):
|
def test_logout(ds, base_url, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.logout() == expected
|
actual = ds.urls.logout()
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -91,7 +110,9 @@ def test_logout(ds, base_url, expected):
|
||||||
)
|
)
|
||||||
def test_database(ds, base_url, format, expected):
|
def test_database(ds, base_url, format, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.database(":memory:", format=format) == expected
|
actual = ds.urls.database(":memory:", format=format)
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -105,8 +126,12 @@ def test_database(ds, base_url, format, expected):
|
||||||
)
|
)
|
||||||
def test_table_and_query(ds, base_url, name, format, expected):
|
def test_table_and_query(ds, base_url, name, format, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.table(":memory:", name, format=format) == expected
|
actual1 = ds.urls.table(":memory:", name, format=format)
|
||||||
assert ds.urls.query(":memory:", name, format=format) == expected
|
assert actual1 == expected
|
||||||
|
assert isinstance(actual1, PrefixedUrlString)
|
||||||
|
actual2 = ds.urls.query(":memory:", name, format=format)
|
||||||
|
assert actual2 == expected
|
||||||
|
assert isinstance(actual2, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -119,7 +144,9 @@ def test_table_and_query(ds, base_url, name, format, expected):
|
||||||
)
|
)
|
||||||
def test_row(ds, base_url, format, expected):
|
def test_row(ds, base_url, format, expected):
|
||||||
ds._config["base_url"] = base_url
|
ds._config["base_url"] = base_url
|
||||||
assert ds.urls.row(":memory:", "facetable", "1", format=format) == expected
|
actual = ds.urls.row(":memory:", "facetable", "1", format=format)
|
||||||
|
assert actual == expected
|
||||||
|
assert isinstance(actual, PrefixedUrlString)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("base_url", ["/", "/prefix/"])
|
@pytest.mark.parametrize("base_url", ["/", "/prefix/"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue