├── .gitignore ├── README.md ├── kanboard ├── __init__.py ├── attachment.py ├── category.py ├── column.py ├── comment.py ├── kanboard.py ├── link.py ├── project.py ├── remote_obj.py ├── subtask.py ├── swimlane.py ├── task.py └── user.py ├── setup.py ├── test.py └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 60 | 61 | *.iml 62 | 63 | ## Directory-based project format: 64 | .idea/ 65 | # if you remove the above rule, at least ignore the following: 66 | 67 | # User-specific stuff: 68 | # .idea/workspace.xml 69 | # .idea/tasks.xml 70 | # .idea/dictionaries 71 | 72 | # Sensitive or high-churn files: 73 | # .idea/dataSources.ids 74 | # .idea/dataSources.xml 75 | # .idea/sqlDataSources.xml 76 | # .idea/dynamic.xml 77 | # .idea/uiDesigner.xml 78 | 79 | # Gradle: 80 | # .idea/gradle.xml 81 | # .idea/libraries 82 | 83 | # Mongo Explorer plugin: 84 | # .idea/mongoSettings.xml 85 | 86 | ## File-based project format: 87 | *.ipr 88 | *.iws 89 | 90 | ## Plugin-specific files: 91 | 92 | # IntelliJ 93 | /out/ 94 | 95 | # mpeltonen/sbt-idea plugin 96 | .idea_modules/ 97 | 98 | # JIRA plugin 99 | atlassian-ide-plugin.xml 100 | 101 | # Crashlytics plugin (for Android Studio and IntelliJ) 102 | com_crashlytics_export_strings.xml 103 | crashlytics.properties 104 | crashlytics-build.properties 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kanboard-py 2 | =========== 3 | 4 | Kanboard python API client 5 | It wraps internal JSON-RPC protocol with classes and simplifies client applications creation. 6 | 7 | Dependencies 8 | ------------ 9 | - json 10 | - requests 11 | 12 | Tested on 13 | --------- 14 | - python 2.7.9 15 | 16 | Code samples 17 | ------------ 18 | 19 | Create task on specified project column 20 | ```python 21 | import kanboard 22 | from kanboard.task import Task 23 | 24 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 25 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 26 | 27 | project = board.get_project_by_name('Test') 28 | column = project.get_column_by_name('Work in progress') 29 | task = column.create_task('Test title', description='test descr', color=Task.COLOR_ORANGE) 30 | ``` 31 | 32 | Iterate over all project tasks 33 | ```python 34 | import kanboard 35 | from kanboard.task import Task 36 | 37 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 38 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 39 | 40 | project = board.get_project_by_name('Test') 41 | tasks = project.get_all_tasks() 42 | for task in tasks: 43 | print task.title 44 | ``` 45 | 46 | Close all tasks in column 47 | ```python 48 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 49 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 50 | 51 | project = board.get_project_by_name('Test') 52 | column = project.get_column_by_name('Done') 53 | tasks = column.get_tasks() 54 | for task in tasks: 55 | task.close() 56 | ``` 57 | 58 | Show comments of red tasks in column 59 | ```python 60 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 61 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 62 | 63 | project = board.get_project_by_name('Test') 64 | column = project.get_column_by_name('Work in progress') 65 | tasks = column.get_tasks() 66 | for task in tasks: 67 | if task.color == Task.COLOR_RED: 68 | for comment in task.get_all_comments(): 69 | print comment.comment 70 | ``` 71 | 72 | Close all tasks at 'Done' column 73 | ```python 74 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 75 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 76 | 77 | project = board.get_project_by_name('Test') 78 | column = project.get_column_by_name('Done') 79 | tasks = column.get_tasks() 80 | for task in tasks: 81 | task.close() 82 | ``` 83 | 84 | Update category and color of tasks 85 | ```python 86 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 87 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 88 | 89 | project = board.get_project_by_name('Test') 90 | 91 | host_category = project.get_category_by_name('host') 92 | for task in project.get_tasks(): 93 | task.update(category=host_category, color=Task.COLOR_BLUE) 94 | ``` 95 | 96 | get tasks for swimlane 97 | ```python 98 | test_project = board.get_project_by_name('Test') 99 | swimlane = test_project.get_swimlane_by_name('Custom swimlane') 100 | columns = swimlane.get_columns() 101 | for column in columns: 102 | print column 103 | for task in column.get_tasks(): 104 | print u'\t' + unicode(task) 105 | ``` 106 | 107 | create task on swimlane 108 | ```python 109 | test_project = board.get_project_by_name('Test') 110 | first_swimlane = test_project.get_swimlane_by_name('First Swimlane') 111 | first_swimlane.create_task('Sample title') 112 | ``` 113 | 114 | create task on swimlane and specified column 115 | ```python 116 | test_project = board.get_project_by_name('Test') 117 | first_swimlane = test_project.get_swimlane_by_name('First Swimlane') 118 | done_column = first_swimlane.get_column_by_name('Done') 119 | done_column.create_task('test creation for swimlane') 120 | ``` 121 | 122 | create subtask 123 | ```python 124 | test_project = board.get_project_by_name('Test') 125 | task = test_project.get_tasks()[0] 126 | subtask = task.create_subtask('test subtask') 127 | ``` 128 | 129 | close all subtask 130 | ```python 131 | test_project = board.get_project_by_name('Test') 132 | task = test_project.get_tasks()[0] 133 | for subtask in task.get_all_subtasks(): 134 | subtask.update_status(Subtask.STATUS_DONE) 135 | ``` 136 | 137 | add attachment to task 138 | ```python 139 | test_project = board.get_project_by_name('Test') 140 | task = test_project.get_tasks()[0] 141 | task.create_file('test.txt', False, '/tmp/test.txt') 142 | ``` 143 | 144 | get all attachments 145 | ```python 146 | test_project = board.get_project_by_name('Test') 147 | task = test_project.get_tasks()[0] 148 | task.get_all_files() 149 | ``` 150 | 151 | get attachment content 152 | ```python 153 | test_project = board.get_project_by_name('Test') 154 | task = test_project.get_tasks()[0] 155 | attachment = task.get_all_files()[0].get_content() 156 | ``` 157 | 158 | create new link with opposite one 159 | ```python 160 | board = kanboard.Kanboard('http://localhost:8080/jsonrpc.php', 161 | '347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0') 162 | link = board.create_link('manage', 'managed by') 163 | ``` 164 | 165 | get link by label and show opposite link 166 | ```python 167 | link = board.get_link_by_label('manage') 168 | print link.opposite() 169 | ``` 170 | 171 | get all links 172 | ```python 173 | links = board.get_all_links() 174 | ``` 175 | 176 | update link label 177 | ```python 178 | link = board.get_link_by_id(10) 179 | link.update('prepared before') 180 | ``` 181 | -------------------------------------------------------------------------------- /kanboard/__init__.py: -------------------------------------------------------------------------------- 1 | from kanboard import Kanboard 2 | -------------------------------------------------------------------------------- /kanboard/attachment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import base64 3 | 4 | __author__ = 'freekoder' 5 | 6 | from remote_obj import RemoteObject 7 | 8 | 9 | class Attachment(RemoteObject): 10 | 11 | def __init__(self, task, props): 12 | self.task = task 13 | self.id = int(props['id']) 14 | self.name = props['name'] 15 | self.path = props['path'] 16 | self.is_image = True if props['is_image'] else False 17 | self.date = props['date'] 18 | self.user = task.project.board.get_user_by_id(int(props['user_id'])) 19 | self.size = int(props['size']) 20 | super(Attachment, self).__init__(task.url, task.token) 21 | 22 | def remove(self): 23 | (status, result) = self._send_template_request('removeFile', {'file_id': self.id}) 24 | if status and result: 25 | return result 26 | else: 27 | return False 28 | 29 | def get_content(self): 30 | (status, result) = self._send_template_request('downloadFile', {'file_id': self.id}) 31 | if status and result: 32 | return base64.decodestring(result) 33 | else: 34 | return None 35 | 36 | def __unicode__(self): 37 | return u'Attachment{name: ' + self.name + u', size:' + unicode(self.size) + u'}' 38 | 39 | def __str__(self): 40 | return unicode(self).encode('utf-8') -------------------------------------------------------------------------------- /kanboard/category.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | class Category(RemoteObject): 8 | 9 | def __init__(self, project, props): 10 | self.id = int(props['id']) 11 | self.name = props['name'] 12 | self.project = project 13 | super(Category, self).__init__(project.url, project.token) 14 | 15 | def update(self, name): 16 | (status, result) = self._send_template_request('updateCategory', {'id': self.id, 'name': name}) 17 | if status and result: 18 | return result 19 | else: 20 | return False 21 | 22 | def remove(self): 23 | (status, result) = self._send_template_request('removeCategory', {'category_id': self.id}) 24 | if status and result: 25 | return result 26 | else: 27 | return False 28 | 29 | def __unicode__(self): 30 | return u'Category{#' + unicode(self.id) + u', name: ' + self.name + u'}' 31 | 32 | def __str__(self): 33 | return unicode(self).encode('utf-8') 34 | -------------------------------------------------------------------------------- /kanboard/column.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | class Column(RemoteObject): 8 | 9 | def __init__(self, project, props): 10 | self.project = project 11 | self.id = int(props['id']) 12 | self.title = props['title'] 13 | self.position = int(props['position']) 14 | self.task_limit = int(props['task_limit']) 15 | super(Column, self).__init__(project.url, project.token) 16 | 17 | def update(self, title, task_limit=None, description=None): 18 | props = {'column_id': self.id, 'title': title} 19 | if task_limit: 20 | props['task_limit'] = task_limit 21 | if description: 22 | props['description'] = description 23 | (status, result) = self._send_template_request('updateColumn', props) 24 | if status and result: 25 | return result 26 | else: 27 | return False 28 | 29 | def remove(self): 30 | (status, result) = self._send_template_request('removeColumn', {'column_id': self.id}) 31 | if status and result: 32 | return result 33 | else: 34 | return False 35 | 36 | def move_up(self): 37 | (status, result) = self._send_template_request('moveColumnUp', {'project_id': self.project.id, 38 | 'column_id': self.id}) 39 | if status and result: 40 | return result 41 | else: 42 | return False 43 | 44 | def move_down(self): 45 | (status, result) = self._send_template_request('moveColumnDown', {'project_id': self.project.id, 46 | 'column_id': self.id}) 47 | if status and result: 48 | return result 49 | else: 50 | return False 51 | 52 | def get_tasks(self): 53 | all_tasks = self.project.get_opened_tasks() 54 | tasks = [] 55 | for task in all_tasks: 56 | if task.column.id == self.id: 57 | tasks.append(task) 58 | return tasks 59 | 60 | def create_task(self, title, color='', description='', 61 | owner=None, creator=None, score=0, 62 | date_due=None, category=None, swimlane=None): 63 | params = {'title': title, 'project_id': self.project.id, 'column_id': self.id} 64 | if color: 65 | params.update({'color_id': color}) 66 | if owner: 67 | params.update({'owner_id': owner.id}) 68 | if creator: 69 | params.update({'creator_id': creator.id}) 70 | if score: 71 | params.update({'score': score}) 72 | if description: 73 | params.update({'description': description}) 74 | if date_due: 75 | params.update({'date_due': date_due}) 76 | if category: 77 | params.update({'category_id': category.id}) 78 | if swimlane: 79 | params.update({'swimlane_id': swimlane.id}) 80 | (status, result) = self._send_template_request('createTask', params) 81 | if status and result: 82 | return self.project.get_task_by_id(result) 83 | else: 84 | return None 85 | 86 | def __unicode__(self): 87 | return u'Column{#' + unicode(self.id) + u', title: ' + self.title + \ 88 | u', position: ' + unicode(self.position) + u'}' 89 | 90 | def __str__(self): 91 | return unicode(self).encode('utf-8') -------------------------------------------------------------------------------- /kanboard/comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | class Comment(RemoteObject): 8 | 9 | def __init__(self, task, props): 10 | self.id = int(props['id']) 11 | self.date = props['date'] 12 | self.task = task 13 | self.user_id = props['user_id'] 14 | # self.user = TODO: implement 15 | self.comment = props['comment'] 16 | self.username = props['username'] 17 | self.name = props['name'] 18 | super(Comment, self).__init__(task.url, task.token) 19 | 20 | # TODO: implement 21 | def update(self, content): 22 | pass 23 | 24 | # TODO: implement 25 | def remove(self): 26 | pass 27 | 28 | def __unicode__(self): 29 | return u'Comment{#' + unicode(self.id) + u'}' 30 | 31 | def __str__(self): 32 | return unicode(self).encode('utf-8') -------------------------------------------------------------------------------- /kanboard/kanboard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'freekoder' 4 | 5 | from link import Link 6 | from user import User 7 | from column import Column 8 | from remote_obj import RemoteObject 9 | import project 10 | 11 | 12 | class Kanboard(RemoteObject): 13 | 14 | def __init__(self, url, token): 15 | super(Kanboard, self).__init__(url, token) 16 | 17 | # TODO: rewrite with _send_template_request 18 | def get_version(self): 19 | rid = self._get_request_id() 20 | params = self._create_request_params('getVersion', rid) 21 | return self._send_request_with_assert(params, rid) 22 | 23 | # TODO: rewrite with _send_template_request 24 | def get_timezone(self): 25 | rid = self._get_request_id() 26 | params = self._create_request_params('getTimezone', rid) 27 | return self._send_request_with_assert(params, rid) 28 | 29 | # TODO: rewrite with _send_template_request 30 | def create_project(self, project_name, description=''): 31 | if type(project_name) is str: 32 | project_name = project_name.decode('utf-8') 33 | if type(description) is str: 34 | description = description.decode('utf-8') 35 | rid = self._get_request_id() 36 | params = self._create_request_params('createProject', rid, {'name': project_name, 'description': description}) 37 | project_id = self._send_request_with_assert(params, rid) 38 | if project_id: 39 | print project_id 40 | return self.get_project_by_id(project_id) 41 | else: 42 | print 'Can not create project with name ' + '"' + project_name + '"' 43 | return None 44 | 45 | def get_project_by_id(self, project_id): 46 | (status, result) = self._send_template_request('getProjectById', {'project_id': project_id}) 47 | if status and result: 48 | return project.Project(self, result) 49 | else: 50 | return None 51 | 52 | # TODO: rewrite with _send_template_request 53 | def get_project_by_name(self, name): 54 | rid = self._get_request_id() 55 | if type(name) is str: 56 | name = name.decode('utf-8') 57 | params = self._create_request_params('getProjectByName', rid, {'name': name}) 58 | project_props = self._send_request_with_assert(params, rid) 59 | if project_props: 60 | return project.Project(self, project_props) 61 | else: 62 | print 'No project with name: ' + name 63 | return None 64 | 65 | # TODO: rewrite with _send_template_request 66 | def get_all_projects(self): 67 | rid = self._get_request_id() 68 | params = self._create_request_params('getAllProjects', rid) 69 | projects_props = self._send_request_with_assert(params, rid) 70 | projects = [] 71 | if projects_props: 72 | for prop in projects_props: 73 | projects.append(project.Project(self, prop)) 74 | return projects 75 | 76 | # TODO: think about method remove from kanboard class 77 | def get_column_by_id(self, id): 78 | (status, result) = self._send_template_request('getColumn', {'column_id': id}) 79 | if status and result: 80 | return Column(self, result) 81 | else: 82 | return None 83 | 84 | # TODO: implement (think about method remove from kanboard class) 85 | def get_task_by_id(self, task_id): 86 | pass 87 | 88 | def create_user(self, username, password, name=None, email=None, is_admin=None, default_project=None): 89 | props = {'username': username, 'password': password} 90 | if name: 91 | props['name'] = name if type(name) is unicode else name.decode('utf-8') 92 | if email: 93 | props['email'] = email 94 | if is_admin: 95 | props['is_admin'] = 1 if is_admin else 0 96 | if default_project: 97 | props['default_project_id'] = default_project.id 98 | (status, result) = self._send_template_request('createUser', props) 99 | if status and result: 100 | return self.get_user_by_id(result) 101 | else: 102 | return None 103 | 104 | # TODO: implement 105 | def create_ldap_user(self, username=None, email=None, is_admin=None, default_project=None): 106 | pass 107 | 108 | def get_user_by_id(self, user_id): 109 | (status, result) = self._send_template_request('getUser', {'user_id': user_id}) 110 | if status and result: 111 | return User(self, result) 112 | else: 113 | return None 114 | 115 | def get_user_by_username(self, username): 116 | users = self.get_all_users() 117 | for user in users: 118 | if user.username == username: 119 | return user 120 | return None 121 | 122 | def get_all_users(self): 123 | (status, result) = self._send_template_request('getAllUsers') 124 | if status and result: 125 | users = [] 126 | for user_info in result: 127 | users.append(User(self, user_info)) 128 | return users 129 | else: 130 | return [] 131 | 132 | # TODO: implement 133 | def get_overdue_tasks(self): 134 | pass 135 | 136 | def get_all_links(self): 137 | (status, result) = self._send_template_request('getAllLinks') 138 | if status and result: 139 | links = [] 140 | for link_info in result: 141 | links.append(Link(self, link_info)) 142 | return links 143 | return [] 144 | 145 | def get_link_by_label(self, label): 146 | (status, result) = self._send_template_request('getLinkByLabel', {'label': label}) 147 | if status and result: 148 | return Link(self, result) 149 | else: 150 | return None 151 | 152 | def get_link_by_id(self, link_id): 153 | (status, result) = self._send_template_request('getLinkById', {'link_id': link_id}) 154 | if status and result: 155 | return Link(self, result) 156 | else: 157 | return None 158 | 159 | def create_link(self, label, opposite_label=None): 160 | (status, result) = self._send_template_request('createLink', {'label': label, 'opposite_label': opposite_label}) 161 | if status and result: 162 | return self.get_link_by_id(result) 163 | else: 164 | return None -------------------------------------------------------------------------------- /kanboard/link.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | class Link(RemoteObject): 8 | 9 | def __init__(self, board, props): 10 | self.board = board 11 | self.id = int(props['id']) 12 | self.label = props['label'] 13 | self.opposite_id = props['opposite_id'] 14 | super(Link, self).__init__(board.url, board.token) 15 | 16 | def opposite(self): 17 | return self.board.get_link_by_id(self.opposite_id) 18 | 19 | def update(self, label): 20 | (status, result) = self._send_template_request('updateLink', {'link_id': self.id, 21 | 'opposite_link_id': self.opposite_id, 22 | 'label': label}) 23 | if status and result: 24 | self.label = label 25 | return True 26 | else: 27 | return False 28 | 29 | def remove(self): 30 | (status, result) = self._send_template_request('removeLink', {'link_id': self.id}) 31 | if status and result: 32 | return True 33 | else: 34 | return False 35 | 36 | def __unicode__(self): 37 | return u'Link{#' + unicode(self.id) + u', label: ' + self.label + u'}' 38 | 39 | def __str__(self): 40 | return unicode(self).encode('utf-8') -------------------------------------------------------------------------------- /kanboard/project.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | from column import Column 6 | from task import Task 7 | from category import Category 8 | from swimlane import Swimlane 9 | 10 | 11 | class Project(RemoteObject): 12 | 13 | def __init__(self, board, props): 14 | self.board = board 15 | self.id = int(props['id']) 16 | self.name = props['name'] 17 | self.is_active = True if props['is_active'] == u'1' else False 18 | self.token = props['token'] 19 | self.last_modified = props['last_modified'] 20 | self.is_public = True if props['is_public'] == u'1' else False 21 | self.description = props['description'] 22 | super(Project, self).__init__(board.url, board.token) 23 | 24 | def update_name(self, name): 25 | rid = self._get_request_id() 26 | params = self._create_request_params('updateProject', rid, {'id': self.id, 'name': name}) 27 | success = self._send_request_with_assert(params, rid) 28 | if success: 29 | self.name = name 30 | return True 31 | else: 32 | return False 33 | 34 | def update_description(self, description): 35 | rid = self._get_request_id() 36 | params = self._create_request_params('updateProject', rid, {'id': self.id, 37 | 'name': self.name, 38 | 'description': description}) 39 | success = self._send_request_with_assert(params, rid) 40 | if success: 41 | self.description = description 42 | return True 43 | else: 44 | return False 45 | 46 | def update_token(self, token): 47 | rid = self._get_request_id() 48 | params = self._create_request_params('updateProject', rid, {'id': self.id, 49 | 'name': self.name, 50 | 'token': token}) 51 | success = self._send_request_with_assert(params, rid) 52 | if success: 53 | self.token = token 54 | return True 55 | else: 56 | return False 57 | 58 | def remove(self): 59 | rid = self._get_request_id() 60 | params = self._create_request_params('removeProject', rid, {'project_id': self.id}) 61 | return self._send_request_with_assert(params, rid) 62 | 63 | def enable(self, enable): 64 | rid = self._get_request_id() 65 | params = self._create_request_params('enableProject', rid, {'project_id': self.id}) 66 | if not enable: 67 | params = self._create_request_params('disableProject', rid, {'project_id': self.id}) 68 | success = self._send_request_with_assert(params, rid) 69 | if success: 70 | self.is_active = enable 71 | return True 72 | else: 73 | return False 74 | 75 | def set_public(self, public): 76 | rid = self._get_request_id() 77 | params = self._create_request_params('enableProjectPublicAccess', rid, {'project_id': self.id}) 78 | if not public: 79 | params = self._create_request_params('disableProjectPublicAccess', rid, {'project_id': self.id}) 80 | success = self._send_request_with_assert(params, rid) 81 | if success: 82 | self.is_public = public 83 | return True 84 | else: 85 | return False 86 | 87 | # TODO: wrap activity into event class 88 | # TODO: handle limit, start, end params 89 | def get_activity(self, limit=0, start=0, end=0): 90 | rid = self._get_request_id() 91 | params = self._create_request_params('getProjectActivity', rid, {'project_ids': [self.id]}) 92 | activity = self._send_request_with_assert(params, rid) 93 | return activity 94 | 95 | # TODO: convert id/username map to user list 96 | def get_members(self): 97 | rid = self._get_request_id() 98 | params = self._create_request_params('getMembers', rid, {'project_id': self.id}) 99 | members = self._send_request_with_assert(params, rid) 100 | return members 101 | 102 | # TODO: implement 103 | def revoke_user(self, user): 104 | pass 105 | 106 | # TODO: implement 107 | def allow_user(self, user): 108 | pass 109 | 110 | # TODO: implement 111 | def get_board_info(self): 112 | pass 113 | 114 | def get_columns(self): 115 | (status, result) = self._send_template_request('getColumns', {'project_id': self.id}) 116 | if status: 117 | columns = [] 118 | for column_info in result: 119 | columns.append(Column(self, column_info)) 120 | return columns 121 | else: 122 | return [] 123 | 124 | def get_column_by_id(self, id): 125 | (status, result) = self._send_template_request('getColumn', {'column_id': id}) 126 | if status: 127 | return Column(self, result) 128 | else: 129 | return None 130 | 131 | def get_column_by_name(self, name): 132 | if type(name) is str: 133 | name = name.decode('utf-8') 134 | columns = self.get_columns() 135 | for column in columns: 136 | if column.title == name: 137 | return column 138 | return None 139 | 140 | # TODO: implement 141 | def move_column_up(self, column): 142 | pass 143 | 144 | # TODO: implement 145 | def move_column_down(self, column): 146 | pass 147 | 148 | # TODO: implement 149 | def add_column(self, title, task_limit=0, description=''): 150 | pass 151 | 152 | # TODO: implement 153 | def get_all_swimlanes(self): 154 | pass 155 | 156 | def get_swimlanes(self): 157 | (status, result) = self._send_template_request('getAllSwimlanes', {'project_id': self.id}) 158 | if status and result: 159 | swimlanes = [] 160 | for info in result: 161 | swimlanes.append(Swimlane(self, info)) 162 | return swimlanes 163 | else: 164 | return [] 165 | 166 | def get_swimlane_by_id(self, swimlane_id): 167 | swimlanes = self.get_swimlanes() 168 | for swimlane in swimlanes: 169 | if swimlane.id == swimlane_id: 170 | return swimlane 171 | return None 172 | 173 | def get_swimlane_by_name(self, name): 174 | swimlanes = self.get_swimlanes() 175 | for swimlane in swimlanes: 176 | if swimlane.name == name: 177 | return swimlane 178 | return None 179 | # (status, result) = self._send_template_request('getSwimlane', {'project_id': self.id, 'name': name}) 180 | # if status and result and len(result) > 0: 181 | # return Swimlane(self, result[0]) 182 | # else: 183 | # return None 184 | 185 | # TODO: implement 186 | def add_swimlane(self, name): 187 | pass 188 | 189 | # TODO: implement 190 | def get_available_actions(self): 191 | pass 192 | 193 | # TODO: implement 194 | def get_available_events(self): 195 | pass 196 | 197 | # TODO: implement 198 | def get_compatible_events(self): 199 | pass 200 | 201 | # TODO: implement 202 | def get_actions(self): 203 | pass 204 | 205 | # TODO: implement 206 | def create_action(self, event_name, action_name, params): 207 | pass 208 | 209 | # TODO: implement, move to action class? 210 | def remove_action(self, action_id): 211 | pass 212 | 213 | # TODO: implement recurrence fields 214 | def create_task(self, title, color='', column=None, description='', 215 | owner=None, creator=None, score=0, 216 | date_due=None, category=None, swimlane=None): 217 | params = {'title': title, 'project_id': self.id} 218 | if color: 219 | params.update({'color_id': color}) 220 | if column: 221 | params.update({'column_id': column.id}) 222 | if owner: 223 | params.update({'owner_id': owner.id}) 224 | if creator: 225 | params.update({'creator_id': creator.id}) 226 | if score: 227 | params.update({'score': score}) 228 | if description: 229 | params.update({'description': description}) 230 | if date_due: 231 | params.update({'date_due': date_due}) 232 | if category: 233 | params.update({'category_id': category.id}) 234 | if swimlane: 235 | params.update({'swimlane_id': swimlane.id}) 236 | (status, result) = self._send_template_request('createTask', params) 237 | if status and result: 238 | return self.get_task_by_id(result) 239 | else: 240 | return None 241 | 242 | def get_task_by_id(self, task_id): 243 | (status, result) = self._send_template_request('getTask', {'task_id': task_id}) 244 | if status and result: 245 | return Task(self, result) 246 | else: 247 | return None 248 | 249 | def get_tasks(self, status=Task.OPENED): 250 | (success, result) = self._send_template_request('getAllTasks', {'project_id': self.id, 'status_id': status}) 251 | if success and result: 252 | tasks = [] 253 | for task_info in result: 254 | tasks.append(Task(self, task_info)) 255 | return tasks 256 | else: 257 | return [] 258 | 259 | def get_opened_tasks(self): 260 | return self.get_tasks(status=Task.OPENED) 261 | 262 | def get_closed_tasks(self): 263 | return self.get_tasks(status=Task.CLOSED) 264 | 265 | # TODO: implement 266 | def get_overdue_tasks(self): 267 | pass 268 | 269 | # TODO: implement 270 | def create_category(self, name): 271 | pass 272 | 273 | def get_category_by_id(self, category_id): 274 | (status, result) = self._send_template_request('getCategory', {'category_id': category_id}) 275 | if status and result: 276 | return Category(self, result) 277 | else: 278 | return None 279 | 280 | def get_category_by_name(self, name): 281 | categories = self.get_all_categories() 282 | for category in categories: 283 | if category.name == name: 284 | return category 285 | return None 286 | 287 | def get_all_categories(self): 288 | (status, result) = self._send_template_request('getAllCategories', {'project_id': self.id}) 289 | if status and result: 290 | categories = [] 291 | for info in result: 292 | categories.append(Category(self, info)) 293 | return categories 294 | else: 295 | return [] 296 | 297 | def __unicode__(self): 298 | return u'Project{#' + unicode(self.id) + u', name: ' + self.name + u', active: ' + \ 299 | unicode(self.is_active) + u', public: ' + unicode(self.is_public) + u'}' 300 | 301 | def __str__(self): 302 | return unicode(self).encode('utf-8') 303 | -------------------------------------------------------------------------------- /kanboard/remote_obj.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | import json 5 | import requests 6 | 7 | 8 | class RemoteObject(object): 9 | 10 | _request_id = 0 11 | token = None 12 | rpc_username = 'jsonrpc' 13 | headers = {'content-type': 'application/json'} 14 | 15 | def __init__(self, url, token): 16 | self.url = url 17 | self.token = token 18 | 19 | def _get_request_id(self): 20 | self._request_id += 1 21 | return self._request_id 22 | 23 | def _create_request_params(self, methon_name, rid, params=None): 24 | request_params = { 25 | 'id': rid, 26 | 'jsonrpc': '2.0', 27 | 'method': methon_name, 28 | } 29 | if params: 30 | request_params['params'] = params 31 | return request_params 32 | 33 | def _send_request_with_assert(self, params, rid): 34 | response = requests.post(self.url, data=json.dumps(params), headers=self.headers, 35 | auth=(self.rpc_username, self.token)) 36 | assert response.ok 37 | assert response.json()['id'] == rid 38 | return response.json()['result'] 39 | 40 | # TODO: handle error 41 | def _send_template_request(self, method_name, params=None): 42 | rid = self._get_request_id() 43 | request = self._create_request_params(method_name, rid, params) 44 | response = requests.post(self.url, data=json.dumps(request), headers=self.headers, 45 | auth=(self.rpc_username, self.token)) 46 | if response.ok and response.json()['id'] == rid and ('result' in response.json()): 47 | return True, response.json()['result'] 48 | else: 49 | return False, 'Error' -------------------------------------------------------------------------------- /kanboard/subtask.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | def status_to_text(status): 8 | if status == Subtask.STATUS_OPENED: 9 | return 'OPENED' 10 | elif status == Subtask.STATUS_PROGRESS: 11 | return 'PROGRESS' 12 | elif status == Subtask.STATUS_DONE: 13 | return 'DONE' 14 | 15 | 16 | class Subtask(RemoteObject): 17 | 18 | STATUS_OPENED = 0 19 | STATUS_PROGRESS = 1 20 | STATUS_DONE = 2 21 | 22 | def __init__(self, task, props): 23 | self.task = task 24 | self.id = int(props['id']) 25 | self.title = props['title'] 26 | self.status = int(props['status']) 27 | self.time_estimated = props['time_estimated'] 28 | self.time_spent = props['time_spent'] 29 | self.user = task.project.board.get_user_by_id(int(props['user_id'])) if props['user_id'] else None 30 | super(Subtask, self).__init__(task.url, task.token) 31 | 32 | def update(self, title=None, user=None, time_estimated=None, time_spent=None, subtask_status=None): 33 | props = {'id': self.id, 'task_id': self.task.id} 34 | if title: 35 | props['title'] = title 36 | if user: 37 | props['user_id'] = user.id 38 | if time_estimated: 39 | props['time_estimated'] = time_estimated 40 | if time_spent: 41 | props['time_spent'] = time_spent 42 | if subtask_status: 43 | props['status'] = subtask_status 44 | (status, result) = self._send_template_request('updateSubtask', props) 45 | if status and result: 46 | if title: 47 | self.title = title 48 | if user: 49 | self.user = user 50 | if time_estimated: 51 | self.time_estimated = time_estimated 52 | if time_spent: 53 | self.time_spent = time_spent 54 | if subtask_status: 55 | self.status = subtask_status 56 | return result 57 | else: 58 | return False 59 | 60 | def update_status(self, subtask_status): 61 | return self.update(subtask_status=subtask_status) 62 | 63 | def remove(self): 64 | (status, result) = self._send_template_request('removeSubtask', {'subtask_id': self.id}) 65 | if status and result: 66 | return result 67 | else: 68 | return False 69 | 70 | def status_to_text(self): 71 | return unicode(self.status) 72 | 73 | def __unicode__(self): 74 | return u'Subtask{#' + unicode(self.id) + u', title: ' + self.title + u', status: ' + status_to_text(self.status) + u'}' 75 | 76 | def __str__(self): 77 | return unicode(self).encode('utf-8') -------------------------------------------------------------------------------- /kanboard/swimlane.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | class Swimlane(RemoteObject): 8 | 9 | def __init__(self, project, props): 10 | self.id = int(props['id']) 11 | self.name = props['name'] 12 | self.is_active = True 13 | if 'is_active' in props: 14 | self.is_active = True if props['is_active'] == '1' else False 15 | if 'position' in props: 16 | self.position = int(props['position']) 17 | self.project = project 18 | super(Swimlane, self).__init__(project.url, project.token) 19 | 20 | # TODO: implement 21 | def move_up(self): 22 | pass 23 | 24 | # TODO: implement 25 | def move_down(self): 26 | pass 27 | 28 | # TODO: implement 29 | def update(self): 30 | pass 31 | 32 | # TODO: implement 33 | def remove(self): 34 | pass 35 | 36 | # TODO: implement 37 | def enable(self, value): 38 | pass 39 | 40 | def get_columns(self): 41 | wrappers = [] 42 | columns = self.project.get_columns() 43 | for column in columns: 44 | wrappers.append(ColumnWrapper(self, column)) 45 | return wrappers 46 | 47 | def get_column_by_id(self, id): 48 | column = self.project.get_column_by_id(id) 49 | return ColumnWrapper(self, column) 50 | 51 | def get_column_by_name(self, name): 52 | column = self.project.get_column_by_name(name) 53 | return ColumnWrapper(self, column) 54 | 55 | def create_task(self, title, color='', column=None, description='', 56 | owner=None, creator=None, score=0, 57 | date_due=None, category=None): 58 | return self.project.create_task(title, color, column, description, 59 | owner, creator, score, 60 | date_due, category, swimlane=self) 61 | 62 | def __unicode__(self): 63 | return u'Swimlane{#' + unicode(self.id) + u', name: ' + self.name + u'}' 64 | 65 | def __str__(self): 66 | return unicode(self).encode('utf-8') 67 | 68 | 69 | class ColumnWrapper: 70 | 71 | def __init__(self, swimlane, column): 72 | self.swimlane = swimlane 73 | self.column = column 74 | 75 | def get_tasks(self): 76 | tasks = [] 77 | for task in self.column.get_tasks(): 78 | if task.swimlane.id == self.swimlane.id: 79 | tasks.append(task) 80 | return tasks 81 | 82 | def create_task(self, title, color='', description='', 83 | owner=None, creator=None, score=0, 84 | date_due=None, category=None): 85 | return self.column.create_task(title, color, description, owner, creator, 86 | score, date_due, category, swimlane=self.swimlane) 87 | 88 | def __unicode__(self): 89 | return u'Swimlane' + unicode(self.column) 90 | 91 | def __str__(self): 92 | return 'Swimlane' + str(self.column) 93 | -------------------------------------------------------------------------------- /kanboard/task.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import base64 3 | 4 | __author__ = 'freekoder' 5 | 6 | from comment import Comment 7 | from remote_obj import RemoteObject 8 | from subtask import Subtask 9 | from attachment import Attachment 10 | 11 | # TODO: create aliases for update 12 | 13 | 14 | class Task(RemoteObject): 15 | 16 | OPENED = 1 17 | CLOSED = 0 18 | 19 | COLOR_YELLOW = 'yellow' 20 | COLOR_BLUE = 'blue' 21 | COLOR_RED = 'red' 22 | COLOR_GREEN = 'green' 23 | COLOR_PURPLE = 'purple' 24 | COLOR_ORANGE = 'orange' 25 | COLOR_GRAY = 'gray' 26 | 27 | def __init__(self, project, props): 28 | self.project = project 29 | self.id = int(props['id']) 30 | self.title = props['title'] 31 | self.description = props['description'] 32 | self.date_creation = props['date_creation'] 33 | self.color = props['color_id'] 34 | self.column = project.get_column_by_id(int(props['column_id'])) 35 | self.owner = project.board.get_user_by_id(int(props['owner_id'])) 36 | self.position = int(props['position']) 37 | self.is_active = True if props['is_active'] == '1' else False 38 | self.date_completed = props['date_completed'] 39 | self.score = props['score'] 40 | self.date_due = props['date_due'] 41 | self.category = project.get_category_by_id(int(props['category_id'])) 42 | self.creator = project.board.get_user_by_id(int(props['creator_id'])) 43 | self.date_modification = props['date_modification'] 44 | self.swimlane = project.get_swimlane_by_id(int(props['swimlane_id'])) 45 | self.date_completed = props['date_completed'] 46 | self.reference = props['reference'] 47 | self.date_modification = props['date_modification'] 48 | self.date_started = props['date_started'] 49 | self.time_spent = props['time_spent'] 50 | self.time_estimated = props['time_estimated'] 51 | self.date_moved = props['date_moved'] 52 | self.recurrence_status = props['recurrence_status'] if 'recurrence_status' in props else None 53 | self.recurrence_trigger = props['recurrence_trigger'] if 'recurrence_trigger' in props else None 54 | self.recurrence_factor = props['recurrence_factor'] if 'recurrence_factor' in props else None 55 | self.recurrence_timeframe = props['recurrence_timeframe'] if 'recurrence_timeframe' in props else None 56 | self.recurrence_basedate = props['recurrence_basedate'] if 'recurrence_basedate' in props else None 57 | self.recurrence_parent = props['recurrence_parent'] if 'recurrence_parent' in props else None 58 | self.recurrence_child = props['recurrence_child'] if 'recurrence_child' in props else None 59 | 60 | super(Task, self).__init__(project.url, project.token) 61 | 62 | # TODO: implement recurrence fields 63 | # TODO: category, owner or swimlane could be None 64 | def update(self, title=None, color=None, column=None, 65 | description=None, owner=None, creator=None, 66 | score=None, date_due=None, category=None, swimlane=None): 67 | props = {'id': self.id, 'project_id': self.project.id} 68 | if title: 69 | props['title'] = title 70 | if color: 71 | props['color_id'] = color 72 | if column: 73 | props['column_id'] = column.id 74 | if description: 75 | props['description'] = description 76 | if owner: 77 | props['owner_id'] = owner.id 78 | if creator: 79 | props['creator_id'] = creator.id 80 | if score: 81 | props['score'] = score 82 | if date_due: 83 | props['date_due'] = date_due 84 | if category: 85 | props['category_id'] = category.id 86 | if swimlane: 87 | props['swimlane_id'] = swimlane.id 88 | 89 | (status, result) = self._send_template_request('updateTask', props) 90 | if status and result: 91 | return result 92 | else: 93 | return False 94 | 95 | def open(self): 96 | (status, result) = self._send_template_request('openTask', {'task_id': self.id}) 97 | if status and result: 98 | return True 99 | else: 100 | return False 101 | 102 | def close(self): 103 | (status, result) = self._send_template_request('closeTask', {'task_id': self.id}) 104 | if status and result: 105 | return True 106 | else: 107 | return False 108 | 109 | def remove(self): 110 | (status, result) = self._send_template_request('removeTask', {'task_id': self.id}) 111 | if status and result: 112 | return True 113 | else: 114 | return False 115 | 116 | def move(self, column, position=1): 117 | (status, result) = self._send_template_request('moveTaskPosition', {'project_id': self.project.id, 118 | 'task_id': self.id, 119 | 'column_id': column.id, 120 | 'position': position}) 121 | if status and result: 122 | return result 123 | else: 124 | return False 125 | 126 | def create_comment(self, user, content): 127 | (status, result) = self._send_template_request('createComment', {'task_id': self.id, 128 | 'user_id': user.id, 129 | 'content': content}) 130 | if status and result: 131 | return self.get_comment(result) 132 | else: 133 | return False 134 | 135 | def get_comment(self, comment_id): 136 | (status, result) = self._send_template_request('getComment', {'comment_id': comment_id}) 137 | if status and result: 138 | return Comment(self, result) 139 | else: 140 | return None 141 | 142 | def get_all_comments(self): 143 | (status, result) = self._send_template_request('getAllComments', {'task_id': self.id}) 144 | if status and result: 145 | comments = [] 146 | for comment_info in result: 147 | comments.append(Comment(self, comment_info)) 148 | return comments 149 | else: 150 | return [] 151 | 152 | def create_subtask(self, title, user=None, time_estimated=None, time_spent=None, task_status=None): 153 | props = {'task_id': self.id, 'title': title} 154 | if user: 155 | props['user_id'] = user.id 156 | if time_estimated: 157 | props['time_estimated'] = time_estimated 158 | if time_spent: 159 | props['time_spent'] = time_spent 160 | if task_status: 161 | props['status'] = task_status 162 | (status, result) = self._send_template_request('createSubtask', props) 163 | if status and result: 164 | return self.get_subtask_by_id(result) 165 | else: 166 | return None 167 | 168 | def get_subtask_by_id(self, subtask_id): 169 | (status, result) = self._send_template_request('getSubtask', {'subtask_id': subtask_id}) 170 | if status and result: 171 | return Subtask(self, result) 172 | else: 173 | return None 174 | 175 | def get_all_subtasks(self): 176 | (status, result) = self._send_template_request('getAllSubtasks', {'task_id': self.id}) 177 | if status and result: 178 | subtasks = [] 179 | for info in result: 180 | subtasks.append(Subtask(self, info)) 181 | return subtasks 182 | else: 183 | return [] 184 | 185 | def _file_get_contents_base64(self, filename): 186 | with open(filename) as f: 187 | file_contents = f.read() 188 | if file_contents: 189 | return base64.b64encode(file_contents) 190 | else: 191 | return None 192 | 193 | def create_file(self, filename, is_image, local_file): 194 | file_blob = self._file_get_contents_base64(local_file) 195 | is_image = '1' if is_image else '0' 196 | if file_blob: 197 | (status, result) = self._send_template_request('createFile', {'project_id': self.project.id, 198 | 'task_id': self.id, 199 | 'filename': filename, 200 | 'is_image': is_image, 201 | 'blob': file_blob}) 202 | if status and result: 203 | return result 204 | else: 205 | return False 206 | else: 207 | return False 208 | 209 | def get_all_files(self): 210 | (status, result) = self._send_template_request('getAllFiles', {'task_id': self.id}) 211 | if status and result: 212 | files = [] 213 | for file_info in result: 214 | files.append(Attachment(self, file_info)) 215 | return files 216 | else: 217 | return False 218 | 219 | def __unicode__(self): 220 | return u'Task{#' + unicode(self.id) + u', title: ' + self.title + \ 221 | u', active: ' + unicode(self.is_active) + u'}' 222 | 223 | def __str__(self): 224 | return unicode(self).encode('utf-8') 225 | -------------------------------------------------------------------------------- /kanboard/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'freekoder' 3 | 4 | from remote_obj import RemoteObject 5 | 6 | 7 | class User(RemoteObject): 8 | def __init__(self, board, props): 9 | self.id = int(props['id']) 10 | self.username = props['username'] 11 | self.password = props['password'] if 'password' in props else '' 12 | self.is_admin = True if props['is_admin'] == '1' else False 13 | self.is_ldap_user = True if props['is_ldap_user'] == '1' else False 14 | self.name = props['name'] 15 | self.email = props['email'] 16 | self.google_id = props['google_id'] 17 | self.github_id = props['github_id'] 18 | self.notifications_enabled = True if props['notifications_enabled'] == '1' else False 19 | super(User, self).__init__(board.url, board.token) 20 | 21 | def update(self, username=None, name=None, email=None, is_admin=None, default_project=None): 22 | props = {'id': self.id} 23 | if username: 24 | props['username'] = username 25 | if name: 26 | props['name'] = name 27 | if email: 28 | props['email'] = email 29 | if isinstance(is_admin, bool): 30 | props['is_admin'] = '1' if is_admin else '0' 31 | if default_project: 32 | props['default_project_id'] = default_project.id 33 | (status, result) = self._send_template_request('updateUser', props) 34 | if status and result: 35 | return result 36 | else: 37 | return False 38 | 39 | def remove(self): 40 | (status, result) = self._send_template_request('removeUser', {'user_id': self.id}) 41 | if status and result: 42 | return result 43 | else: 44 | return False 45 | 46 | def __unicode__(self): 47 | return u'User{#' + str(self.id) + u', username: ' + self.username + u'}' 48 | 49 | def __str__(self): 50 | return unicode(self).encode('utf-8') -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='kanboard', 4 | version='0.1', 5 | description='kanboard API python client', 6 | author='Alexander Rudakov', 7 | author_email='freekoder@gmail.com', 8 | url='https://github.com/freekoder/kanboard-py', 9 | license="BSD-derived (http://www.repoze.org/LICENSE.txt)", 10 | classifiers=[ 11 | "Development Status :: 3 - Alpha", 12 | "Intended Audience :: Developers", 13 | "Programming Language :: Python", 14 | "Programming Language :: Python :: 2.6", 15 | "Programming Language :: Python :: 2.7", 16 | ], 17 | keywords='kanboard API', 18 | install_requires=['requests'], 19 | packages=find_packages(exclude=['contrib', 'docs', 'tests*', 'examples']) 20 | ) 21 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import kanboard 5 | from kanboard.subtask import Subtask 6 | 7 | 8 | def main(): 9 | board = kanboard.Kanboard('http://localhost:8000/jsonrpc.php', 10 | 'f3ef774d36cb3eee20cd5ee0c9905dfca36dae4c661546dd86c15297a2e2') 11 | 12 | # board.create_link('distrupt') 13 | # 14 | 15 | # link = board.get_link_by_id(14) 16 | # link.update('disepticon') 17 | # links = board.get_all_links() 18 | # for link in links: 19 | # print str(link) + ' => ' + str(link.opposite()) 20 | 21 | users = board.get_all_users() 22 | for user in users: 23 | print user 24 | 25 | project = board.get_project_by_name('Test') 26 | swimlanes = project.get_swimlanes() 27 | for swimlane in swimlanes: 28 | print swimlane.name 29 | 30 | # user = board.create_user('Масимо23', '123456', name='test', email='test5@email.com') 31 | # if user: 32 | # print user 33 | # else: 34 | # print 'Can not create user' 35 | # users = board.get_all_users() 36 | # for user in users: 37 | # print user 38 | # print 'Email: ' + str(user.email) 39 | # print 'Admin: ' + str(user.is_admin) 40 | # print 'Def Project: ' + str(user.default_project) 41 | 42 | # admin_user = board.get_user_by_username('simple') 43 | # print 'User: ' + str(admin_user) 44 | # print admin_user.update(email='done@test.com') 45 | # test_project = board.get_project_by_name('Test') 46 | # tasks = test_project.get_tasks() 47 | # for task in tasks: 48 | # success = task.create_file('test.txt', False, '/tmp/test.txt') 49 | # files = task.get_all_files() 50 | # for attach in files: 51 | # print attach.remove() 52 | # if '.txt' in attach.name: 53 | # print attach.get_content() 54 | # print success 55 | # if task.get_all_files(): 56 | # for attachment in task.get_all_files(): 57 | # print attachment 58 | # columns = test_project.get_columns() 59 | # # columns[2].move_down() 60 | # for column in columns: 61 | # column.update(column.title, description='test description', task_limit=5) 62 | # task = test_project.get_tasks()[0] 63 | # print task 64 | # subtask = task.create_subtask('test subtask', user=admin_user) 65 | # print subtask 66 | # 67 | # for subtask in task.get_all_subtasks(): 68 | # subtask.update('OK OB') 69 | # subtask.update_status(Subtask.STATUS_DONE) 70 | # print subtask 71 | # subtask.remove() 72 | # backlog = test_project.get_column_by_name('Work in progress') 73 | # done = test_project.get_column_by_name('Done') 74 | # print backlog 75 | # print done 76 | # for task in backlog.get_tasks(): 77 | # print task.move(done) 78 | 79 | # swimlane = test_project.get_swimlane_by_name('Default swimlane') 80 | # columns = swimlane.get_columns() 81 | # for column in columns: 82 | # print column 83 | # tasks = column.get_tasks() 84 | # for task in tasks: 85 | # print u'\t' + unicode(task) 86 | # 87 | # first_swimlane = test_project.get_swimlane_by_name('First Swimlane') 88 | # done_column = first_swimlane.get_column_by_name('Done') 89 | # done_column.create_task('test creation for swimlane') 90 | # backlog = test_project.get_column_by_name('Backlog') 91 | # tasks = backlog.get_tasks() 92 | # for task in tasks: 93 | # print task.swimlane 94 | # swimlane = test_project.get_swimlane_by_name('First Swimlane') 95 | # test_project.create_task('test from script2', owner=admin_user, swimlane=swimlane) 96 | # # tasks = test_project.get_tasks() 97 | # # for task in tasks: 98 | # # print task.swimlane 99 | # 100 | # swimlanes = test_project.get_all_swimlanes() 101 | # for swimlane in swimlanes: 102 | # print swimlane 103 | 104 | # swimlane = test_project.get_swimlane_by_name('First Swimlane') 105 | # print swimlane 106 | # for category in test_project.get_all_categories(): 107 | # print category 108 | # 109 | # tasks = test_project.get_tasks() 110 | # # for task in tasks: 111 | # # print task.category 112 | # 113 | # closed_tasks = test_project.get_closed_tasks() 114 | # for task in closed_tasks: 115 | # task.open() 116 | # 117 | # 118 | # 119 | # host_category = test_project.get_category_by_name('host') 120 | # for task in tasks: 121 | # task.update(category=host_category, color=Task.COLOR_BLUE, description='Test description') 122 | # task.update(title='Hi kanboard') 123 | # task.create_comment(admin_user, 'Test comment from admin') 124 | 125 | 126 | # done_task = test_project.get_column_by_name('Done').get_tasks() 127 | # for task in done_task: 128 | # task.remove() 129 | 130 | # project = board.get_project_by_name('Test') 131 | # column = project.get_column_by_name('Done') 132 | # tasks = column.get_tasks() 133 | # for task in tasks: 134 | # task.close() 135 | # task = column.create_task('Create from column', description='created with script', color=Task.COLOR_ORANGE) 136 | 137 | 138 | # task = project.create_task('Created task', description='Test description', color=Task.COLOR_GREEN, column=column) 139 | # print task 140 | # print project 141 | # tasks = project.get_closed_tasks() 142 | # for task in tasks: 143 | # print task.color 144 | # column = project.get_column_by_name(u'Backlog') 145 | # print column 146 | # if column: 147 | # tasks = column.get_tasks() 148 | # for task in tasks: 149 | # print task.get_all_comments() 150 | # user = board.create_user('test_def', '123456', default_project=project) 151 | # print user 152 | # users = board.get_all_users() 153 | # for user in users: 154 | # print user 155 | 156 | 157 | if __name__ == '__main__': 158 | main() -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | curl \ 4 | -u 'jsonrpc:347a020cb5ce709441aa42b2d5652fbb8b02e477104d1d9789f7b2d40df0' \ 5 | -d '{ 6 | "jsonrpc": "2.0", 7 | "method": "updateUser", 8 | "id": 322123657, 9 | "params": { 10 | "id": 2, 11 | "is_admin": 1, 12 | "email": "test@test.com" 13 | } 14 | }' \ 15 | http://localhost:8080/jsonrpc.php 16 | --------------------------------------------------------------------------------