mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-10 23:50:12 +00:00
Merge branch '255-add-user-file-upload-docs-to-the-generated-api-docs' into 'develop'
Resolve "Add user file upload docs to the generated API docs" Closes #255 See merge request bramw/baserow!962
This commit is contained in:
commit
31ed9ec584
11 changed files with 522 additions and 8 deletions
backend
src/baserow
tests/baserow/api/user_files
web-frontend
locales
modules/database
components/docs
locales
pages
|
@ -8,6 +8,7 @@ from rest_framework.views import APIView
|
|||
|
||||
from baserow.api.decorators import map_exceptions, validate_body
|
||||
from baserow.api.schemas import get_error_schema
|
||||
from baserow.contrib.database.api.tokens.authentications import TokenAuthentication
|
||||
from baserow.core.user_files.exceptions import (
|
||||
FileSizeTooLargeError,
|
||||
FileURLCouldNotBeReached,
|
||||
|
@ -28,6 +29,7 @@ from .serializers import UserFileSerializer, UserFileUploadViaURLRequestSerializ
|
|||
class UploadFileView(APIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
parser_classes = (MultiPartParser,)
|
||||
authentication_classes = APIView.authentication_classes + [TokenAuthentication]
|
||||
|
||||
@extend_schema(
|
||||
tags=["User files"],
|
||||
|
@ -63,6 +65,7 @@ class UploadFileView(APIView):
|
|||
|
||||
class UploadViaURLView(APIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
authentication_classes = APIView.authentication_classes + [TokenAuthentication]
|
||||
|
||||
@extend_schema(
|
||||
tags=["User files"],
|
||||
|
|
|
@ -343,9 +343,8 @@ def setup_interesting_test_table(
|
|||
)
|
||||
|
||||
# multiple collaborators
|
||||
getattr(row, f"field_{name_to_field_id['multiple_collaborators']}").set(
|
||||
[user2.id, user3.id]
|
||||
)
|
||||
getattr(row, f"field_{name_to_field_id['multiple_collaborators']}").add(user2.id)
|
||||
getattr(row, f"field_{name_to_field_id['multiple_collaborators']}").add(user3.id)
|
||||
|
||||
context = {"user2": user2, "user3": user3}
|
||||
|
||||
|
|
|
@ -15,11 +15,12 @@ from rest_framework.status import (
|
|||
HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
)
|
||||
|
||||
from baserow.contrib.database.tokens.handler import TokenHandler
|
||||
from baserow.core.models import UserFile
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_upload_file(api_client, data_fixture, tmpdir):
|
||||
def test_upload_file_with_jwt_auth(api_client, data_fixture, tmpdir):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
|
@ -144,9 +145,137 @@ def test_upload_file(api_client, data_fixture, tmpdir):
|
|||
assert thumbnail.width == 21
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_upload_file_with_token_auth(api_client, data_fixture, tmpdir):
|
||||
user, jwt_token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
group = data_fixture.create_group(user=user)
|
||||
token = TokenHandler().create_token(user, group, "uploadFile")
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_INVALID_FILE"
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
data={"file": ""},
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_INVALID_FILE"
|
||||
|
||||
old_limit = settings.BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB
|
||||
settings.BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB = 6
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
data={"file": SimpleUploadedFile("test.txt", b"Hello World")},
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
settings.BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB = old_limit
|
||||
assert response.status_code == HTTP_413_REQUEST_ENTITY_TOO_LARGE
|
||||
assert response.json()["error"] == "ERROR_FILE_SIZE_TOO_LARGE"
|
||||
assert response.json()["detail"] == (
|
||||
"The provided file is too large. Max 0MB is allowed."
|
||||
)
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
data={"file": "not a file"},
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_INVALID_FILE"
|
||||
|
||||
storage = FileSystemStorage(location=str(tmpdir), base_url="http://localhost")
|
||||
|
||||
with patch("baserow.core.user_files.handler.default_storage", new=storage):
|
||||
with freeze_time("2020-01-01 12:00"):
|
||||
file = SimpleUploadedFile("test.txt", b"Hello World")
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
data={"file": file},
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["size"] == 11
|
||||
assert response_json["mime_type"] == "text/plain"
|
||||
assert response_json["is_image"] is False
|
||||
assert response_json["image_width"] is None
|
||||
assert response_json["image_height"] is None
|
||||
assert response_json["uploaded_at"] == "2020-01-01T12:00:00Z"
|
||||
assert response_json["thumbnails"] is None
|
||||
assert response_json["original_name"] == "test.txt"
|
||||
assert "localhost:8000" in response_json["url"]
|
||||
|
||||
user_file = UserFile.objects.all().last()
|
||||
assert user_file.name == response_json["name"]
|
||||
assert response_json["url"].endswith(response_json["name"])
|
||||
file_path = tmpdir.join("user_files", user_file.name)
|
||||
assert file_path.isfile()
|
||||
|
||||
with patch("baserow.core.user_files.handler.default_storage", new=storage):
|
||||
file = SimpleUploadedFile("test.txt", b"Hello World")
|
||||
response_2 = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
data={"file": file},
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
|
||||
# The old file should be provided.
|
||||
assert response_2.json()["name"] == response_json["name"]
|
||||
assert response_json["original_name"] == "test.txt"
|
||||
|
||||
image = Image.new("RGB", (100, 140), color="red")
|
||||
file = SimpleUploadedFile("test.png", b"")
|
||||
image.save(file, format="PNG")
|
||||
file.seek(0)
|
||||
|
||||
with patch("baserow.core.user_files.handler.default_storage", new=storage):
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_file"),
|
||||
data={"file": file},
|
||||
format="multipart",
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["mime_type"] == "image/png"
|
||||
assert response_json["is_image"] is True
|
||||
assert response_json["image_width"] == 100
|
||||
assert response_json["image_height"] == 140
|
||||
assert len(response_json["thumbnails"]) == 1
|
||||
assert "localhost:8000" in response_json["thumbnails"]["tiny"]["url"]
|
||||
assert "tiny" in response_json["thumbnails"]["tiny"]["url"]
|
||||
assert response_json["thumbnails"]["tiny"]["width"] == 21
|
||||
assert response_json["thumbnails"]["tiny"]["height"] == 21
|
||||
assert response_json["original_name"] == "test.png"
|
||||
|
||||
user_file = UserFile.objects.all().last()
|
||||
file_path = tmpdir.join("user_files", user_file.name)
|
||||
assert file_path.isfile()
|
||||
file_path = tmpdir.join("thumbnails", "tiny", user_file.name)
|
||||
assert file_path.isfile()
|
||||
thumbnail = Image.open(file_path.open("rb"))
|
||||
assert thumbnail.height == 21
|
||||
assert thumbnail.width == 21
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||
def test_upload_file_via_url(api_client, data_fixture, tmpdir):
|
||||
def test_upload_file_via_url_with_jwt_auth(api_client, data_fixture, tmpdir):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
|
@ -251,6 +380,115 @@ def test_upload_file_via_url(api_client, data_fixture, tmpdir):
|
|||
assert file_path.isfile()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@httpretty.activate(verbose=True, allow_net_connect=False)
|
||||
def test_upload_file_via_url_with_token_auth(api_client, data_fixture, tmpdir):
|
||||
user, jwt_token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
group = data_fixture.create_group(user=user)
|
||||
token = TokenHandler().create_token(user, group, "uploadFileViaUrl")
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_REQUEST_BODY_VALIDATION"
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={"url": "NOT_A_URL"},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_REQUEST_BODY_VALIDATION"
|
||||
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
"https://baserow.io/test2.txt",
|
||||
status=404,
|
||||
)
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={"url": "https://baserow.io/test2.txt"},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_FILE_URL_COULD_NOT_BE_REACHED"
|
||||
|
||||
# Only the http and https protocol are allowed.
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={"url": "ftp://baserow.io/test2.txt"},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_INVALID_FILE_URL"
|
||||
|
||||
old_limit = settings.BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB
|
||||
settings.BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB = 6
|
||||
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
"http://localhost/test.txt",
|
||||
body="Hello World",
|
||||
status=200,
|
||||
content_type="text/plain",
|
||||
)
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={"url": "http://localhost/test.txt"},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_413_REQUEST_ENTITY_TOO_LARGE
|
||||
assert response.json()["error"] == "ERROR_FILE_SIZE_TOO_LARGE"
|
||||
|
||||
# If the content length is not specified then when streaming down the file we will
|
||||
# check the file size.
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
"http://localhost/test2.txt",
|
||||
body="Hello World",
|
||||
forcing_headers={"Content-Length": None},
|
||||
status=200,
|
||||
content_type="text/plain",
|
||||
)
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={"url": "http://localhost/test2.txt"},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
assert response.status_code == HTTP_413_REQUEST_ENTITY_TOO_LARGE
|
||||
assert response.json()["error"] == "ERROR_FILE_SIZE_TOO_LARGE"
|
||||
|
||||
settings.BASEROW_FILE_UPLOAD_SIZE_LIMIT_MB = old_limit
|
||||
|
||||
storage = FileSystemStorage(location=str(tmpdir), base_url="http://localhost")
|
||||
|
||||
with patch("baserow.core.user_files.handler.default_storage", new=storage):
|
||||
response = api_client.post(
|
||||
reverse("api:user_files:upload_via_url"),
|
||||
data={"url": "http://localhost/test.txt"},
|
||||
HTTP_AUTHORIZATION=f"Token {token.key}",
|
||||
)
|
||||
response_json = response.json()
|
||||
|
||||
assert response.status_code == HTTP_200_OK, response_json
|
||||
assert response_json["size"] == 11
|
||||
assert response_json["mime_type"] == "text/plain"
|
||||
assert response_json["is_image"] is False
|
||||
assert response_json["image_width"] is None
|
||||
assert response_json["image_height"] is None
|
||||
assert response_json["thumbnails"] is None
|
||||
assert response_json["original_name"] == "test.txt"
|
||||
assert "localhost:8000" in response_json["url"]
|
||||
user_file = UserFile.objects.all().last()
|
||||
file_path = tmpdir.join("user_files", user_file.name)
|
||||
assert file_path.isfile()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_upload_file_via_url_within_private_network(api_client, data_fixture, tmpdir):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
|
|
|
@ -28,6 +28,7 @@ For example:
|
|||
* Fields can now be duplicated with their cell values also. [#964](https://gitlab.com/bramw/baserow/-/issues/964)
|
||||
* Add a tooltip to applications and tables in the left sidebar to show the full name. [#986](https://gitlab.com/bramw/baserow/-/issues/986)
|
||||
* Allow not creating a reversed relationship with the link row field. [#1063](https://gitlab.com/bramw/baserow/-/issues/1063)
|
||||
* Add API token authentication support to multipart and via-URL file uploads. [#255](https://gitlab.com/bramw/baserow/-/issues/255)
|
||||
|
||||
### Bug Fixes
|
||||
* Resolve circular dependency in `FieldWithFiltersAndSortsSerializer` [#1113](https://gitlab.com/bramw/baserow/-/issues/1113)
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
"createdOnReadOnly": "The created on field is a read only field.",
|
||||
"url": "Accepts a string that must be a URL.",
|
||||
"email": "Accepts a string that must be an email address.",
|
||||
"file": "Accepts an array of objects containing at least the name of the user file.",
|
||||
"file": "Accepts an array of objects containing at least the name of the user file. You can use the \"File uploads\" endpoints to upload the file. The response of those calls can be provided directly as object here. The endpoints can be found in the left sidebar.",
|
||||
"singleSelect": "Accepts an integer representing the chosen select option id or null if none is selected.",
|
||||
"multipleSelect": "Accepts an array of integers each representing the chosen select option id.",
|
||||
"phoneNumber": "Accepts a phone number which has a maximum length of 100 characters consisting solely of digits, spaces and the following characters: Nx,._+*()#=;/- .",
|
||||
|
@ -269,12 +269,17 @@
|
|||
"moveRow": "Move row",
|
||||
"deleteRow": "Delete row",
|
||||
"deleteRows": "Delete rows",
|
||||
"uploadFile": "Upload file",
|
||||
"uploadFileDescription": "The `file` multipart containing the file contents.",
|
||||
"uploadFileViaUrl": "Upload file via URL",
|
||||
"uploadFileViaUrlDescription": "Uploads a file to Baserow by downloading it from the provided URL.",
|
||||
"queryParameters": "Query parameters",
|
||||
"pathParameters": "Path parameters",
|
||||
"requestBodySchema": "Request body schema",
|
||||
"userFieldNamesDescription": "When any value is provided for the `user_field_names` GET param then field names returned by this endpoint will be the actual names of the fields.\n\n If the `user_field_names` GET param is not provided, then all returned field names will be `field_` followed by the id of the field. For example `field_1` refers to the field with an id of `1`.",
|
||||
"singleRow": "Single",
|
||||
"batchRows": "Batch"
|
||||
"batchRows": "Batch",
|
||||
"fileUploads": "File uploads"
|
||||
},
|
||||
"exporterType": {
|
||||
"csv": "Export to CSV"
|
||||
|
|
|
@ -165,6 +165,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
fileRequest: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
request: {
|
||||
type: [Object, Boolean],
|
||||
required: false,
|
||||
|
@ -226,6 +231,11 @@ export default {
|
|||
example += '\n-H "Content-Type: application/json" \\'
|
||||
}
|
||||
|
||||
if (this.fileRequest !== '') {
|
||||
index++
|
||||
example += ` \\\n-F file=@${this.fileRequest}`
|
||||
}
|
||||
|
||||
example += `\n${this.url}`
|
||||
|
||||
if (this.request !== false) {
|
||||
|
@ -250,6 +260,12 @@ export default {
|
|||
example += `${this.url} HTTP`
|
||||
example += '\nAuthorization: Token YOUR_API_KEY'
|
||||
|
||||
if (this.fileRequest !== '') {
|
||||
example += '\nContent-Length: YOUR_CONTENT_LENGTH'
|
||||
example +=
|
||||
'\nContent-Type: multipart/form-data; boundary=------------------------YOUR_BOUNDARY'
|
||||
}
|
||||
|
||||
if (this.request !== false) {
|
||||
index += 2
|
||||
example += '\nContent-Type: application/json'
|
||||
|
@ -263,7 +279,14 @@ export default {
|
|||
},
|
||||
getJavaScriptExample() {
|
||||
let index = 5
|
||||
let example = 'axios({'
|
||||
let example = ''
|
||||
if (this.fileRequest !== '') {
|
||||
example += 'const formData = new FormData()'
|
||||
example += `\nformData.append('file', '${this.fileRequest}')`
|
||||
example += "\naxios.post('/fileupload', formData, {"
|
||||
} else {
|
||||
example = 'axios({'
|
||||
}
|
||||
|
||||
if (this.type !== '') {
|
||||
index++
|
||||
|
@ -274,6 +297,11 @@ export default {
|
|||
example += '\n headers: {'
|
||||
example += '\n Authorization: "Token YOUR_API_KEY"'
|
||||
|
||||
if (this.fileRequest !== '') {
|
||||
index++
|
||||
example += ',\n "Content-Type": "multipart/form-data"'
|
||||
}
|
||||
|
||||
if (this.request !== false) {
|
||||
index++
|
||||
example += ',\n "Content-Type": "application/json"'
|
||||
|
@ -309,6 +337,11 @@ export default {
|
|||
|
||||
example += '\n }'
|
||||
|
||||
if (this.fileRequest !== '') {
|
||||
index++
|
||||
example += `\n files={'file': open('${this.fileRequest}', 'rb')}`
|
||||
}
|
||||
|
||||
if (this.request !== false) {
|
||||
index++
|
||||
const data = this.format(this.request).split('\n').join('\n ')
|
||||
|
|
|
@ -125,6 +125,42 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="api-docs__nav-link"
|
||||
@click.prevent="navigate('section-upload-file')"
|
||||
>{{ $t('apiDocs.fileUploads') }}</a
|
||||
>
|
||||
<ul
|
||||
class="api-docs__nav-sub"
|
||||
:class="{
|
||||
open:
|
||||
navActive === 'section-upload-file' ||
|
||||
navActive === 'section-upload-file-via-url',
|
||||
}"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
class="api-docs__nav-link"
|
||||
:class="{
|
||||
active: navActive === 'section-upload-file',
|
||||
}"
|
||||
@click.prevent="navigate('section-upload-file')"
|
||||
>{{ $t('apiDocs.uploadFile') }}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="api-docs__nav-link"
|
||||
:class="{
|
||||
active: navActive === 'section-upload-file-via-url',
|
||||
}"
|
||||
@click.prevent="navigate('section-upload-file-via-url')"
|
||||
>{{ $t('apiDocs.uploadFileViaUrl') }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="api-docs__nav-link"
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="api-docs__item">
|
||||
<div class="api-docs__left">
|
||||
<div class="api-docs__heading-wrapper">
|
||||
<h3 :id="'section-upload-file'" class="api-docs__heading-3">
|
||||
<span>
|
||||
{{ $t('apiDocs.uploadFile') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="api-docs__endpoint-type"></div>
|
||||
</div>
|
||||
|
||||
<MarkdownIt
|
||||
tag="p"
|
||||
class="api-docs__content"
|
||||
:content="
|
||||
$t('apiDocsUploadFile.description', {
|
||||
PUBLIC_BACKEND_URL: `${$env.PUBLIC_BACKEND_URL}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
||||
<h4 class="api-docs__heading-4">{{ $t('apiDocs.requestBodySchema') }}</h4>
|
||||
<ul class="api-docs__parameters">
|
||||
<APIDocsParameter name="file" :optional="false" type="multipart">
|
||||
<MarkdownIt
|
||||
class="api-docs__content"
|
||||
:content="$t('apiDocs.uploadFileDescription')"
|
||||
/>
|
||||
</APIDocsParameter>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="api-docs__right">
|
||||
<APIDocsExample
|
||||
:value="value"
|
||||
type="POST"
|
||||
:url="getUploadFileListUrl()"
|
||||
:file-request="getUploadFileExample()"
|
||||
:response="getUploadFileResponse()"
|
||||
:include-user-fields-checkbox="false"
|
||||
@input="$emit('input', $event)"
|
||||
></APIDocsExample>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import APIDocsExample from '@baserow/modules/database/components/docs/APIDocsExample'
|
||||
import APIDocsParameter from '@baserow/modules/database/components/docs/APIDocsParameter'
|
||||
|
||||
export default {
|
||||
name: 'APIDocsUploadFile',
|
||||
components: {
|
||||
APIDocsParameter,
|
||||
APIDocsExample,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
getUploadFileListUrl: { type: Function, required: true },
|
||||
getUploadFileExample: { type: Function, required: true },
|
||||
getUploadFileResponse: { type: Function, required: true },
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="api-docs__item">
|
||||
<div class="api-docs__left">
|
||||
<div class="api-docs__heading-wrapper">
|
||||
<h3 :id="'section-upload-file-via-url'" class="api-docs__heading-3">
|
||||
<span>
|
||||
{{ $t('apiDocs.uploadFileViaUrl') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="api-docs__endpoint-type"></div>
|
||||
</div>
|
||||
|
||||
<MarkdownIt
|
||||
tag="p"
|
||||
class="api-docs__content"
|
||||
:content="
|
||||
$t('apiDocsUploadFileViaURL.description', {
|
||||
PUBLIC_BACKEND_URL: `${$env.PUBLIC_BACKEND_URL}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
||||
<h4 class="api-docs__heading-4">{{ $t('apiDocs.requestBodySchema') }}</h4>
|
||||
<ul class="api-docs__parameters">
|
||||
<APIDocsParameter name="url" :optional="false" type="string">
|
||||
<MarkdownIt
|
||||
class="api-docs__content"
|
||||
:content="$t('apiDocs.uploadFileViaUrlDescription')"
|
||||
/>
|
||||
</APIDocsParameter>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="api-docs__right">
|
||||
<APIDocsExample
|
||||
:value="value"
|
||||
type="POST"
|
||||
:url="getUploadFileViaUrlListUrl()"
|
||||
:response="getUploadFileResponse()"
|
||||
:request="getUploadFileViaUrlRequestExample()"
|
||||
:include-user-fields-checkbox="false"
|
||||
@input="$emit('input', $event)"
|
||||
></APIDocsExample>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import APIDocsExample from '@baserow/modules/database/components/docs/APIDocsExample'
|
||||
import APIDocsParameter from '@baserow/modules/database/components/docs/APIDocsParameter'
|
||||
|
||||
export default {
|
||||
name: 'APIDocsUploadFileViaURL',
|
||||
components: {
|
||||
APIDocsParameter,
|
||||
APIDocsExample,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
getUploadFileViaUrlListUrl: { type: Function, required: true },
|
||||
getUploadFileViaUrlRequestExample: { type: Function, required: true },
|
||||
getUploadFileResponse: { type: Function, required: true },
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -180,6 +180,14 @@
|
|||
"rowId": "Moves the row related to the value.",
|
||||
"before": "Moves the row related to the given `row_id` before the row related to the provided value. If not provided, then the row will be moved to the end."
|
||||
},
|
||||
"apiDocsUploadFile": {
|
||||
"description": "Uploads a file to Baserow by uploading the file contents directly. A `file` multipart is expected containing the file contents. The response can then be used to [upload a file to a row]({PUBLIC_BACKEND_URL}/api/redoc/#tag/Database-table-rows/operation/update_database_table_row).",
|
||||
"file": "The `file` multipart containing the file contents."
|
||||
},
|
||||
"apiDocsUploadFileViaURL": {
|
||||
"description": "Uploads a file to Baserow by downloading it from the provided URL. The response can then be used to [upload a file to a row]({PUBLIC_BACKEND_URL}/api/redoc/#tag/Database-table-rows/operation/update_database_table_row).",
|
||||
"url": "The URL you would like Baserow to download and upload on your behalf."
|
||||
},
|
||||
"apiDocsTableUpdateRow": {
|
||||
"description": "Updates an existing {name} row.",
|
||||
"rowId": "The unique identifier of the row that needs to be updated.",
|
||||
|
|
|
@ -113,6 +113,20 @@
|
|||
:get-batch-delete-request-example="getBatchDeleteRequestExample"
|
||||
/>
|
||||
</div>
|
||||
<APIDocsUploadFile
|
||||
v-model="exampleData"
|
||||
:get-upload-file-list-url="getUploadFileListUrl"
|
||||
:get-upload-file-example="getUploadFileExample"
|
||||
:get-upload-file-response="getUploadFileResponse"
|
||||
/>
|
||||
<APIDocsUploadFileViaURL
|
||||
v-model="exampleData"
|
||||
:get-upload-file-response="getUploadFileResponse"
|
||||
:get-upload-file-via-url-list-url="getUploadFileViaUrlListUrl"
|
||||
:get-upload-file-via-url-request-example="
|
||||
getUploadFileViaUrlRequestExample
|
||||
"
|
||||
/>
|
||||
<APIDocsFilters />
|
||||
<APIDocsErrors v-model="exampleData" />
|
||||
</div>
|
||||
|
@ -136,10 +150,15 @@ import APIDocsTableCreateRow from '@baserow/modules/database/components/docs/sec
|
|||
import APIDocsTableUpdateRow from '@baserow/modules/database/components/docs/sections/APIDocsTableUpdateRow'
|
||||
import APIDocsTableMoveRow from '@baserow/modules/database/components/docs/sections/APIDocsTableMoveRow'
|
||||
import APIDocsTableDeleteRow from '@baserow/modules/database/components/docs/sections/APIDocsTableDeleteRow'
|
||||
import APIDocsUploadFile from '@baserow/modules/database/components/docs/sections/APIDocsUploadFile'
|
||||
import APIDocsUploadFileViaURL from '@baserow/modules/database/components/docs/sections/APIDocsUploadFileViaURL'
|
||||
import APIDocsFilters from '@baserow/modules/database/components/docs/sections/APIDocsFilters'
|
||||
import APIDocsErrors from '@baserow/modules/database/components/docs/sections/APIDocsErrors'
|
||||
import APIDocsMenu from '@baserow/modules/database/components/docs/sections/APIDocsMenu.vue'
|
||||
|
||||
// Re-use the FileFieldType docs response example.
|
||||
import { FileFieldType } from '../fieldTypes'
|
||||
|
||||
export default {
|
||||
name: 'APIDocsDatabase',
|
||||
components: {
|
||||
|
@ -154,6 +173,8 @@ export default {
|
|||
APIDocsTableUpdateRow,
|
||||
APIDocsTableMoveRow,
|
||||
APIDocsTableDeleteRow,
|
||||
APIDocsUploadFile,
|
||||
APIDocsUploadFileViaURL,
|
||||
APIDocsFilters,
|
||||
APIDocsErrors,
|
||||
APIDocsMenu,
|
||||
|
@ -372,6 +393,40 @@ export default {
|
|||
getDeleteListURL(table) {
|
||||
return `${this.$env.PUBLIC_BACKEND_URL}/api/database/rows/table/${table.id}/batch-delete/`
|
||||
},
|
||||
/**
|
||||
* Generates the 'upload file' file example.
|
||||
*/
|
||||
getUploadFileExample() {
|
||||
return 'photo.png'
|
||||
},
|
||||
/**
|
||||
* Generates the 'upload file' and 'upload via URL' file example response.
|
||||
*/
|
||||
getUploadFileResponse() {
|
||||
return this.$registry
|
||||
.get('field', FileFieldType.getType())
|
||||
.getDocsResponseExample()[0]
|
||||
},
|
||||
/**
|
||||
* Generates the 'upload file' URI.
|
||||
*/
|
||||
getUploadFileListUrl() {
|
||||
return this.$env.PUBLIC_BACKEND_URL + '/api/user-files/upload-file/'
|
||||
},
|
||||
/**
|
||||
* Generates the 'upload file' request example.
|
||||
*/
|
||||
getUploadFileViaUrlRequestExample() {
|
||||
return {
|
||||
url: 'https://baserow.io/assets/photo.png',
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Generates the 'upload file via URL' URI.
|
||||
*/
|
||||
getUploadFileViaUrlListUrl() {
|
||||
return this.$env.PUBLIC_BACKEND_URL + '/api/user-files/upload-via-url/'
|
||||
},
|
||||
getItemURL(table, addUserFieldParam) {
|
||||
return (
|
||||
this.getListURL(table) +
|
||||
|
|
Loading…
Add table
Reference in a new issue