|
3 | 3 | from requests import HTTPError |
4 | 4 | import logging |
5 | 5 | import os |
| 6 | +import time |
6 | 7 |
|
7 | 8 | log = logging.getLogger(__name__) |
8 | 9 |
|
@@ -278,25 +279,29 @@ def remove_page(self, page_id, status=None, recursive=False): |
278 | 279 | params['status'] = status |
279 | 280 | return self.delete(url, params=params) |
280 | 281 |
|
281 | | - def create_page(self, space, title, body, parent_id=None, type='page'): |
| 282 | + def create_page(self, space, title, body, parent_id=None, type='page', |
| 283 | + representation='storage'): |
282 | 284 | """ |
283 | 285 | Create page from scratch |
284 | 286 | :param space: |
285 | 287 | :param title: |
286 | 288 | :param body: |
287 | 289 | :param parent_id: |
288 | 290 | :param type: |
| 291 | + :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format |
289 | 292 | :return: |
290 | 293 | """ |
291 | 294 | log.info('Creating {type} "{space}" -> "{title}"'.format(space=space, title=title, type=type)) |
| 295 | + if representation not in ['wiki', 'storage']: |
| 296 | + raise ValueError("Wrong value for representation, it should be either wiki or storage") |
292 | 297 | url = 'rest/api/content/' |
293 | 298 | data = { |
294 | 299 | 'type': type, |
295 | 300 | 'title': title, |
296 | 301 | 'space': {'key': space}, |
297 | | - 'body': {'storage': { |
| 302 | + 'body': {representation: { |
298 | 303 | 'value': body, |
299 | | - 'representation': 'storage'}}} |
| 304 | + 'representation': representation}}} |
300 | 305 | if parent_id: |
301 | 306 | data['ancestors'] = [{'type': type, 'id': parent_id}] |
302 | 307 | return self.post(url, data=data) |
@@ -325,8 +330,8 @@ def add_comment(self, page_id, text): |
325 | 330 | data = {'type': 'comment', |
326 | 331 | 'container': {'id': page_id, 'type': 'page', 'status': 'current'}, |
327 | 332 | 'body': {'storage': {'value': text, 'representation': 'storage'}}} |
328 | | - return self.post('rest/api/content/', data=data) |
329 | | - |
| 333 | + return self.post('rest/api/content/', data=data) |
| 334 | + |
330 | 335 | def attach_file(self, filename, page_id=None, title=None, space=None, comment=None): |
331 | 336 | """ |
332 | 337 | Attach (upload) a file to a page, if it exists it will update the |
@@ -630,6 +635,28 @@ def get_space(self, space_key, expand='description.plain,homepage'): |
630 | 635 | expand=expand) |
631 | 636 | return self.get(url) |
632 | 637 |
|
| 638 | + def create_space(self, space_key, space_name): |
| 639 | + """ |
| 640 | + Create space |
| 641 | + :param space_key: |
| 642 | + :param space_name: |
| 643 | + :return: |
| 644 | + """ |
| 645 | + data = { |
| 646 | + 'key': space_key, |
| 647 | + 'name': space_name |
| 648 | + } |
| 649 | + self.post('rest/api/space', data=data) |
| 650 | + |
| 651 | + def delete_space(self, space_key): |
| 652 | + """ |
| 653 | + Delete space |
| 654 | + :param space_key: |
| 655 | + :return: |
| 656 | + """ |
| 657 | + url = 'rest/api/space/{}'.format(space_key) |
| 658 | + return self.delete(url) |
| 659 | + |
633 | 660 | def get_user_details_by_username(self, username, expand=None): |
634 | 661 | """ |
635 | 662 | Get information about a user through username |
@@ -701,6 +728,9 @@ def get_page_as_pdf(self, page_id): |
701 | 728 | """ |
702 | 729 | headers = self.form_token_headers |
703 | 730 | url = 'spaces/flyingpdf/pdfpageexport.action?pageId={pageId}'.format(pageId=page_id) |
| 731 | + if self.api_version == 'cloud': |
| 732 | + url = self.get_pdf_download_url_for_confluence_cloud(url) |
| 733 | + |
704 | 734 | return self.get(url, headers=headers, not_json_response=True) |
705 | 735 |
|
706 | 736 | def export_page(self, page_id): |
@@ -761,3 +791,50 @@ def health_check(self): |
761 | 791 | response = self.get('rest/supportHealthCheck/1.0/check/') |
762 | 792 | return response |
763 | 793 |
|
| 794 | + def get_pdf_download_url_for_confluence_cloud(self, url): |
| 795 | + """ |
| 796 | + Confluence cloud does not return the PDF document when the PDF |
| 797 | + export is initiated. Instead it starts a process in the background |
| 798 | + and provides a link to download the PDF once the process completes. |
| 799 | + This functions polls the long running task page and returns the |
| 800 | + download url of the PDF. |
| 801 | + :param url: URL to initiate PDF export |
| 802 | + :return: Download url for PDF file |
| 803 | + """ |
| 804 | + download_url = None |
| 805 | + try: |
| 806 | + long_running_task = True |
| 807 | + headers = self.form_token_headers |
| 808 | + log.info('Initiate PDF export from Confluence Cloud') |
| 809 | + response = self.get(url, headers=headers, not_json_response=True) |
| 810 | + response_string = response.decode(encoding='utf-8', errors='strict') |
| 811 | + task_id = response_string.split('name="ajs-taskId" content="')[1].split('">')[0] |
| 812 | + poll_url = 'runningtaskxml.action?taskId={0}'.format(task_id) |
| 813 | + while long_running_task: |
| 814 | + long_running_task_response = self.get(poll_url, headers=headers, not_json_response=True) |
| 815 | + long_running_task_response_parts = long_running_task_response.decode(encoding='utf-8', |
| 816 | + errors='strict').split('\n') |
| 817 | + percentage_complete = long_running_task_response_parts[6].strip() |
| 818 | + is_successful = long_running_task_response_parts[7].strip() |
| 819 | + is_complete = long_running_task_response_parts[8].strip() |
| 820 | + log.info('Sleep for 5s.') |
| 821 | + time.sleep(5) |
| 822 | + log.info('Check if export task has completed.') |
| 823 | + if is_complete == '<isComplete>true</isComplete>': |
| 824 | + if is_successful == '<isSuccessful>true</isSuccessful>': |
| 825 | + log.info(percentage_complete) |
| 826 | + log.info('Downloading content...') |
| 827 | + log.debug('Extract taskId and download PDF.') |
| 828 | + current_status = long_running_task_response_parts[3] |
| 829 | + download_url = current_status.split('href="/wiki/')[1].split('"')[0] |
| 830 | + long_running_task = False |
| 831 | + elif is_successful == '<isSuccessful>false</isSuccessful>': |
| 832 | + log.error('PDF conversion not successful.') |
| 833 | + return None |
| 834 | + else: |
| 835 | + log.info(percentage_complete) |
| 836 | + except IndexError as e: |
| 837 | + log.error(e) |
| 838 | + return None |
| 839 | + |
| 840 | + return download_url |
0 commit comments