From 02f40ccf2c838c2ef633d00bf489bfad8caf9a09 Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Tue, 21 Jan 2014 21:05:39 +0100 Subject: [PATCH] add Issue.fields attribute to support custom fields --- modules/redmine/backend.py | 13 +++++++++---- modules/redmine/browser.py | 21 +++++++++++++++++++++ modules/redmine/pages/issues.py | 23 +++++++++++++++++++++++ weboob/capabilities/bugtracker.py | 1 + 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/modules/redmine/backend.py b/modules/redmine/backend.py index f3a5e916..c1f8b029 100644 --- a/modules/redmine/backend.py +++ b/modules/redmine/backend.py @@ -189,6 +189,9 @@ class RedmineBackend(BaseBackend, ICapContent, ICapBugTracker, ICapCollection): issue.body = params['body'] issue.creation = params['created_on'] issue.updated = params['updated_on'] + issue.fields = {} + for key, value in params['fields'].iteritems(): + issue.fields[key] = value issue.attachments = [] for a in params['attachments']: attachment = Attachment(a['id']) @@ -220,12 +223,14 @@ class RedmineBackend(BaseBackend, ICapContent, ICapBugTracker, ICapCollection): def create_issue(self, project): try: with self.browser: - r = self.browser.query_issues(project) + r = self.browser.get_project(project) except BrowserHTTPNotFound: return None issue = Issue(0) - issue.project = self._build_project(r['project']) + issue.project = self._build_project(r) + with self.browser: + issue.fields = self.browser.get_custom_fields(project) return issue def post_issue(self, issue): @@ -237,6 +242,7 @@ class RedmineBackend(BaseBackend, ICapContent, ICapBugTracker, ICapCollection): 'category': issue.category, 'status': issue.status.id if issue.status else None, 'body': issue.body, + 'fields': issue.fields, } with self.browser: @@ -291,7 +297,6 @@ class RedmineBackend(BaseBackend, ICapContent, ICapBugTracker, ICapCollection): return self._build_project(params['project']) def fill_issue(self, issue, fields): - # currently there isn't cases where an Issue is uncompleted. - return issue + return self.get_issue(issue) OBJECTS = {Issue: fill_issue} diff --git a/modules/redmine/browser.py b/modules/redmine/browser.py index b93a34d3..4b03940f 100644 --- a/modules/redmine/browser.py +++ b/modules/redmine/browser.py @@ -22,6 +22,7 @@ from urlparse import urlsplit import urllib import lxml.html +from weboob.capabilities.bugtracker import IssueError from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from .pages.index import LoginPage, IndexPage, MyPage, ProjectsPage @@ -147,6 +148,12 @@ class RedmineBrowser(BaseBrowser): 'iter': self.page.iter_issues(), } + def get_project(self, project): + self.location('/projects/%s/issues/new' % project) + assert self.is_on_page(NewIssuePage) + + return self.page.get_project(project) + def get_issue(self, id): self.location('/issues/%s' % id) @@ -165,12 +172,26 @@ class RedmineBrowser(BaseBrowser): assert self.is_on_page(IssuePage) self.page.fill_form(note=message) + def get_custom_fields(self, project): + self.location('/projects/%s/issues/new' % project) + assert self.is_on_page(NewIssuePage) + + fields = {} + for key, div in self.page.iter_custom_fields(): + fields[key] = div.attrib['value'] + + return fields + def create_issue(self, project, **kwargs): self.location('/projects/%s/issues/new' % project) assert self.is_on_page(NewIssuePage) self.page.fill_form(**kwargs) + error = self.page.get_errors() + if len(error) > 0: + raise IssueError(error) + assert self.is_on_page(IssuePage) return int(self.page.groups[0]) diff --git a/modules/redmine/pages/issues.py b/modules/redmine/pages/issues.py index a44bde4c..f7fe9d86 100644 --- a/modules/redmine/pages/issues.py +++ b/modules/redmine/pages/issues.py @@ -81,6 +81,12 @@ class BaseIssuePage(BasePage): token = tokens[0].attrib['value'] return token + def get_errors(self): + errors = [] + for li in self.document.xpath('//div[@id="errorExplanation"]//li'): + errors.append(li.text.strip()) + return ', '.join(errors) + class IssuesPage(BaseIssuePage): PROJECT_FIELDS = {'members': 'values_assigned_to_id', @@ -139,6 +145,11 @@ class NewIssuePage(BaseIssuePage): 'statuses': 'issue_status_id', } + def iter_custom_fields(self): + for div in self.document.xpath('//form//input[starts-with(@id, "issue_custom_field")]'): + label = self.document.xpath('//label[@for="%s"]' % div.attrib['id'])[0] + yield label.text.strip(), div + def set_title(self, title): self.browser['issue[subject]'] = title.encode('utf-8') @@ -178,6 +189,13 @@ class NewIssuePage(BaseIssuePage): def set_note(self, message): self.browser['notes'] = message.encode('utf-8') + def set_fields(self, fields): + for key, div in self.iter_custom_fields(): + try: + self.browser[div.attrib['name']] = fields[key] + except KeyError: + continue + def fill_form(self, **kwargs): self.browser.select_form(predicate=lambda form: form.attrs.get('id', '') == 'issue-form') for key, value in kwargs.iteritems(): @@ -230,6 +248,11 @@ class IssuePage(NewIssuePage): params['category'] = self._parse_selection('issue_category_id') params['version'] = self._parse_selection('issue_fixed_version_id') + params['fields'] = {} + for key, div in self.iter_custom_fields(): + value = div.attrib['value'] + params['fields'][key] = value + params['attachments'] = [] try: for p in self.parser.select(content, 'div.attachments', 1).findall('p'): diff --git a/weboob/capabilities/bugtracker.py b/weboob/capabilities/bugtracker.py index f93468f2..a555afac 100644 --- a/weboob/capabilities/bugtracker.py +++ b/weboob/capabilities/bugtracker.py @@ -206,6 +206,7 @@ class Issue(CapBaseObject): category = StringField('Name of the category') version = Field('Target version of this issue', Version) status = Field('Status of this issue', Status) + fields = Field('Custom fields (key,value)', dict) class Query(CapBaseObject):