How to upload Attachments via API and python requests?

Hi,

I’m running Deck 1.5.5 on NC 22.2.3.
I have a python script that takes information from another site and creates cards automatically. Now I want to also generate PDFs and add them to these cards.

The documentation tells me to post to the endpoint
/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments
and use parameters type and file.

As I said I am using python and the requests package. Creating the cards works like charm, however I am unsure of how to pass the file into the requests.post() call. I’m only just starting out with python.

I have tried both the simpler version and the tuple version of what requests doc says, using the files argument and as the file item in the dict passed as params.
This gives me code 500: No file uploaded or file size exceeds maximum of 512MB, which of course it didn’t.

requests.post('https://<URL>index.php/apps/deck/api/v1.0/boards/<boardId>/stacks/<stackId>/cards/<cardId>/attachments', headers={'OCS-APIRequest':'true', 'Content-Type': 'application/json'}, auth=("***","***"), data={'type':'file'}, files={'file': ('file.pdf', open('file.pdf', 'rb'), 'application/pdf')})

requests.post(https://<URL>/index.php/apps/deck/api/v1.0/boards/<boardId>/stacks/<stackId>/cards/<cardId>/attachments', headers={'OCS-APIRequest':'true', 'Content-Type': 'application/json'}, auth=("***","***"), params={'type':'file', 'file': ('file.pdf', open('file.pdf', 'rb'), 'application/pdf')})

So is 'type':'file' correct and how do I need to package, let’s say, file.pdf in order to pass it as the file parameter?

Thank you,
pmallot

Hey :wave:

unfortunately i can’t give you a direct solution, but in the Deck Android app we are using the following endpoints - maybe you can read some difference to your approach?

https://github.com/stefan-niedermann/nextcloud-deck/blob/master/app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java#L146

Hi Stefan,
thanks for the answer, unfortunately I didn’t really find anything helpful to me.

I have tried getting an existing attachment to see what the API returns. However this didn’t work either.

r=requests.get("https://***/index.php/apps/deck/api/v1.1/boards/18/stacks/55/cards/443/attachments/201", headers={'OCS-APIRequest':'true', 'Content-Type': 'application/json'}, auth=("***","***"))

This returns a code 405 Method Not Allowed and what seems to be an empty binary object: b''

Making the same request to the v1.0 API results in a response 500 Internal Server Error with the Nextcloud logs saying the following:

[deck] Error: Object of class OC\DB\QueryBuilder\QueryBuilder could not be converted to string

GET /index.php/apps/deck/api/v1.0/boards/18/stacks/55/cards/443/attachments/201
from *** by *** at ***
Exception: Object of class OC\DB\QueryBuilder\QueryBuilder could not be converted to string
/var/www/html/lib/private/AppFramework/App.php - line 156:

OC\AppFramework\Http\Dispatcher->dispatch(OCA\Deck\Con ... {}, "display")

/var/www/html/lib/private/Route/Router.php - line 302:

OC\AppFramework\App::main("OCA\\Deck\\ ... r", "display", OC\AppFramew ... {}, { 0: "And 1 ... "})

/var/www/html/lib/base.php - line 1006:

OC\Route\Router->match("/apps/deck/ ... 1")

/var/www/html/index.php - line 36:

OC::handleRequest()

Verursacht durch Error: Object of class OC\DB\QueryBuilder\QueryBuilder could not be converted to string
/var/www/html/custom_apps/deck/lib/Service/AttachmentService.php - line 238:

OCA\Deck\Db\AttachmentMapper->find("201")

/var/www/html/custom_apps/deck/lib/Controller/AttachmentApiController.php - line 62:

OCA\Deck\Service\AttachmentService->display("443", "201", "deck_file")

/var/www/html/lib/private/AppFramework/Http/Dispatcher.php - line 217:

OCA\Deck\Controller\AttachmentApiController->display("443", "201", "deck_file")

/var/www/html/lib/private/AppFramework/Http/Dispatcher.php - line 126:

OC\AppFramework\Http\Dispatcher->executeController(OCA\Deck\Con ... {}, "display")

/var/www/html/lib/private/AppFramework/App.php - line 156:

OC\AppFramework\Http\Dispatcher->dispatch(OCA\Deck\Con ... {}, "display")

/var/www/html/lib/private/Route/Router.php - line 302:

OC\AppFramework\App::main("OCA\\Deck\\ ... r", "display", OC\AppFramew ... {}, { 0: "And 1 ... "})

/var/www/html/lib/base.php - line 1006:

OC\Route\Router->match("/apps/deck/ ... 1")

/var/www/html/index.php - line 36:

OC::handleRequest()

The ids for board, stack, card and id are correct. However I was only able to get a list of attachments when using the v1.1 api. I understand the file storage was changed moving to v1.1 so I assume this is more about the documentation not being updated yet.

Hi,

I’m facing the same issue with my javascript app. Did you find a solution to your problem?

If I do:

        				axios.post(server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments`,
				        {
			    	        type: 'deck_file',
							file: binary
				        },
				        {
				            timeout: 8000,
				            headers: {
		        	     	   'Content-Type': 'application/json',
		            	    	'Authorization': token.value
				            }
				        })

I receive an error No file uploaded or file size exceeds maximum of 32MB (The file is 2MB).

The error is thrown by Deck’s File service because the $_FILES variable wasn’t initialised:

	private function getUploadedFile() {
		$file = $this->request->getUploadedFile('file');
		$error = null;
		$phpFileUploadErrors = [
			UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
			UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
			UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
			UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
			UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
			UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
			UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
			UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
		];

		if (empty($file)) {
			$error = $this->l10n->t('No file uploaded or file size exceeds maximum of %s', [\OCP\Util::humanFileSize(\OCP\Util::uploadLimit())]);
		}

So, I’ve decided to try and set the Content-Type header to multipart/form-data:

        				axios.post(server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments`,
				        {
			    	        type: 'deck_file',
							file: binary
				        },
				        {
				            timeout: 8000,
				            headers: {
		        	     	   'Content-Type': 'multipart/form-data',
		            	    	'Authorization': token.value
				            }
				        })

Which fails because, then, Deck’s Attachment service cannot find the “type” parameter:

	public function create($cardId, $type, $data) {
		if (is_numeric($cardId) === false) {
			throw new BadRequestException('card id must be a number');
		}

		if ($type === false || $type === null) {
			throw new BadRequestException('type must be provided');
		}

		if ($data === false || $data === null) {
			//throw new BadRequestException('data must be provided');
		}

So, I don’t known what to do, and it looks like a chicken and egg problem:

  1. It looks like if I want to have the $_FILES variable initialised, then I have to specify multipart/form-data as ContentType (see PHP: POST method uploads - Manual);
  2. However, it looks like I need to set the ContentType to application/json in order for Deck to be able to read the type parameter (which is also aligned with Deck’s documentation. See REST API - Nextcloud Deck)

Anyone can help?

I could get it working.

I don’t exactly understand how the requests are build but at least I can upload attachments with the following javascript/expo-based code:

			// Selects document
			DocumentPicker.getDocumentAsync({copyToCacheDirectory: false})
			.then(resp => {
				if (resp.type === 'success') {

					// Uploads attachment
					console.log('Uploading attachment')
					const { name, size, uri } = resp
                    FileSystem.uploadAsync(
                        server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments`,
                        uri,
                        {
                            fieldName: 'file',
						    httpMethod: 'POST',
                            uploadType: FileSystem.FileSystemUploadType.MULTIPART,
                            headers: {
                                'Content-Type': 'application/json',
                                'Authorization': token.value
                            },
                            parameters: {
                                type: 'file'
                            }
                        },
				    )
					.then(() => {
						console.log('Attachment uploaded')
					})

Hi StCyr,

unfortunately I didn’t have the time to look into this much further. I’m glad to hear, that you found a way!
If and when I find a way to recreate this in Python I will post it here.

Thanks!

I found a way to upload attachments via API with python3.

def createAttachment(boardId, stackId, cardId, fileType, fileContent, mimetype, fileName):
    url = f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments'
    payload = {'type' : fileType}
    files=[
        ('file',(fileName,fileContent,mimetype))
    ]
    response = requests.post( url, auth=authTo, data=payload, files=files)
    response.raise_for_status()
    return response.json()

Notes:

  • Content-Type is sent as ‘multipart/form-data’ (not ‘application/json’). Although it’s better not to specify it explicitly, so that the boundary can automatically be taken care of.
  • mimetype for the uploaded file was generated as ‘application/octet-stream’ by Postman although I uploaded a PNG file.

Hope, this helps.