diff --git a/modules/redmine/browser.py b/modules/redmine/browser.py
index 5c4c76d3f62e58457c7b8bb85716b66d5d474caf..01bf010f07caada7376afe292372d94ee5cd5322 100644
--- a/modules/redmine/browser.py
+++ b/modules/redmine/browser.py
@@ -17,13 +17,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
-
import re
import lxml.html
from weboob.capabilities.bugtracker import IssueError
-from weboob.deprecated.browser import Browser, BrowserIncorrectPassword
-from weboob.tools.compat import urlsplit, urlencode, quote
+from weboob.browser import LoginBrowser, URL, need_login
+from weboob.exceptions import BrowserIncorrectPassword
+from weboob.tools.compat import quote
from .pages.index import LoginPage, IndexPage, MyPage, ProjectsPage
from .pages.wiki import WikiPage, WikiEditPage
@@ -34,53 +34,38 @@
__all__ = ['RedmineBrowser']
-# Browser
-class RedmineBrowser(Browser):
- ENCODING = 'utf-8'
- PAGES = {
- 'https?://[^/]+/': IndexPage,
- 'https?://[^/]+/login': LoginPage,
- # compatibility with redmine 0.9
- 'https?://[^/]+/login\?back_url.*': MyPage,
- 'https?://[^/]+/my/page': MyPage,
- 'https?://[^/]+/projects': ProjectsPage,
- 'https?://[^/]+/projects/([\w-]+)/wiki/([^\/]+)/edit(?:\?version=\d+)?': WikiEditPage,
- 'https?://[^/]+/projects/[\w-]+/wiki/[^\/]*': WikiPage,
- 'https?://[^/]+/projects/[\w-]+/issues/new': NewIssuePage,
- 'https?://[^/]+/projects/[\w-]+/issues': IssuesPage,
- 'https?://[^/]+/issues(|/?\?.*)': IssuesPage,
- 'https?://[^/]+/issues/(\d+)': IssuePage,
- 'https?://[^/]+/issues/(\d+)/time_entries/new': IssueLogTimePage,
- 'https?://[^/]+/projects/[\w-]+/time_entries': IssueTimeEntriesPage,
- }
+class RedmineBrowser(LoginBrowser):
+ index = URL(r'/$', IndexPage)
+ login = URL(r'/login$', r'/login\?back_url.*', LoginPage) # second url is related to redmine 0.9
+ mypage = URL(r'/my/page', MyPage)
+ projects_page = URL(r'/projects$', ProjectsPage)
+ wiki_edit = URL(r'/projects/(?P[\w-]+)/wiki/(?P[^\/]+)/edit(?:\?version=\d+)?', WikiEditPage)
+ wiki = URL(r'/projects/[\w-]+/wiki/[^\/]*', WikiPage)
+ new_issue = URL(r'/projects/[\w-]+/issues/new', NewIssuePage)
+ issues = URL(r'/projects/[\w-]+/issues$',
+ r'/issues/?\?.*$',
+ r'/issues$',
+ IssuesPage)
+ issue = URL(r'/issues/(?P\d+)', IssuePage)
+ issues_log_time = URL(r'/issues/(?P\d+)/time_entries/new', IssueLogTimePage)
+ issues_time_entry = URL(r'/projects/[\w-]+/time_entries', IssueTimeEntriesPage)
def __init__(self, url, *args, **kwargs):
+ super(RedmineBrowser, self).__init__(*args, **kwargs)
self._userid = 0
- v = urlsplit(url)
- self.PROTOCOL = v.scheme
- self.DOMAIN = v.netloc
- self.BASEPATH = v.path
- if self.BASEPATH.endswith('/'):
- self.BASEPATH = self.BASEPATH[:-1]
- Browser.__init__(self, *args, **kwargs)
+ self.BASEURL = url
self.projects = {}
- def is_logged(self):
- return self.is_on_page(LoginPage) or self.page and len(self.page.document.getroot().cssselect('a.my-account')) == 1
-
- def login(self):
- assert isinstance(self.username, basestring)
- assert isinstance(self.password, basestring)
-
- if not self.is_on_page(LoginPage):
- self.location('%s/login' % self.BASEPATH, no_login=True)
+ def do_login(self):
+ if not self.login.is_here():
+ self.login.go()
self.page.login(self.username, self.password)
- if self.is_on_page(LoginPage):
+ if self.login.is_here():
raise BrowserIncorrectPassword()
- divs = self.page.document.getroot().cssselect('div#loggedas')
+ divs = self.page.doc.xpath('//div[@id="loggedas"]')
if len(divs) > 0:
parts = divs[0].find('a').attrib['href'].split('/')
self._userid = int(parts[2])
@@ -88,29 +73,30 @@ def login(self):
def get_userid(self):
return self._userid
+ @need_login
def get_wiki_source(self, project, page, version=None):
- url = '%s/projects/%s/wiki/%s/edit' % (self.BASEPATH, project, quote(page.encode('utf-8')))
+ url = self.absurl('projects/%s/wiki/%s/edit' % (project, quote(page)), True)
if version:
url += '?version=%s' % version
self.location(url)
return self.page.get_source()
+ @need_login
def set_wiki_source(self, project, page, data, message):
- self.location('%s/projects/%s/wiki/%s/edit' % (self.BASEPATH, project, quote(page.encode('utf-8'))))
+ self.location(self.absurl('projects/%s/wiki/%s/edit' % (project, quote(page)), True))
self.page.set_source(data, message)
+ @need_login
def get_wiki_preview(self, project, page, data):
- if (not self.is_on_page(WikiEditPage) or self.page.groups[0] != project
- or self.page.groups[1] != page):
- self.location('%s/projects/%s/wiki/%s/edit' % (self.BASEPATH,
- project, quote(page.encode('utf-8'))))
- url = '%s/projects/%s/wiki/%s/preview' % (self.BASEPATH, project, quote(page.encode('utf-8')))
- params = {}
- params['content[text]'] = data.encode('utf-8')
- params['authenticity_token'] = "%s" % self.page.get_authenticity_token()
- preview_html = lxml.html.fragment_fromstring(self.readurl(url,
- urlencode(params)),
- create_parent='div')
+ if (not self.wiki_edit.is_here() or self.page.params['project'] != project
+ or self.page.params['page'] != page):
+ url = self.absurl('projects/%s/wiki/%s/edit' % (project, quote(page)), True)
+ self.location(url)
+ url = self.absurl('projects/%s/wiki/%s/preview' % (project, quote(page)), True)
+ params = self.get_submit()
+ params['content[text]'] = data
+ #params['authenticity_token'] = self.page.get_authenticity_token()
+ preview_html = lxml.html.fragment_fromstring(self.open(url, data=params), create_parent='div')
preview_html.find("fieldset").drop_tag()
preview_html.find("legend").drop_tree()
return lxml.html.tostring(preview_html)
@@ -129,8 +115,9 @@ def get_wiki_preview(self, project, page, data):
}
}
+ @need_login
def query_issues(self, project_name, **kwargs):
- self.location('/projects/%s/issues' % project_name)
+ self.location(self.absurl('projects/%s/issues' % project_name, True))
token = self.page.get_authenticity_token()
method = self.page.get_query_method()
data = ((self.METHODS[method]['project_id'], project_name),
@@ -150,7 +137,7 @@ def query_issues(self, project_name, **kwargs):
(self.METHODS[method]['column'], 'estimated_hours'),
(self.METHODS[method]['column'], 'created_on'),
)
- for key, value in kwargs.iteritems():
+ for key, value in kwargs.items():
if value:
value = self.page.get_value_from_label(self.METHODS[method]['value'] % key, value)
data += ((self.METHODS[method]['value'] % key, value),)
@@ -158,43 +145,48 @@ def query_issues(self, project_name, **kwargs):
data += ((self.METHODS[method]['operator'] % key, '~'),)
if method == 'POST':
- self.location('/issues?set_filter=1&per_page=100', urlencode(data))
+ self.location('/issues?set_filter=1&per_page=100', data=data)
else:
data += (('set_filter', '1'), ('per_page', '100'))
- self.location(self.buildurl('/issues', *data))
+ self.location(self.absurl('issues', True), params=data)
- assert self.is_on_page(IssuesPage)
+ assert self.issues.is_here()
return {'project': self.page.get_project(project_name),
'iter': self.page.iter_issues(),
}
+ @need_login
def get_project(self, project):
- self.location('/projects/%s/issues/new' % project)
- assert self.is_on_page(NewIssuePage)
+ self.location(self.absurl('projects/%s/issues/new' % project, True))
+ assert self.new_issue.is_here()
return self.page.get_project(project)
+ @need_login
def get_issue(self, id):
- self.location('/issues/%s' % id)
+ self.location(self.absurl('issues/%s' % id, True))
- assert self.is_on_page(IssuePage)
+ assert self.issue.is_here()
return self.page.get_params()
+ @need_login
def logtime_issue(self, id, hours, message):
- self.location('/issues/%s/time_entries/new' % id)
+ self.location(self.absurl('issues/%s/time_entries/new' % id, True))
- assert self.is_on_page(IssueLogTimePage)
+ assert self.issues_log_time.is_here()
self.page.logtime(hours.seconds/3600, message)
+ @need_login
def comment_issue(self, id, message):
- self.location('/issues/%s' % id)
+ self.location(self.absurl('issues/%s' % id, True))
- assert self.is_on_page(IssuePage)
+ assert self.issue.is_here()
self.page.fill_form(note=message)
+ @need_login
def get_custom_fields(self, project):
- self.location('/projects/%s/issues/new' % project)
- assert self.is_on_page(NewIssuePage)
+ self.location(self.absurl('projects/%s/issues/new' % project, True))
+ assert self.new_issue.is_here(NewIssuePage)
fields = {}
for key, div in self.page.iter_custom_fields():
@@ -206,52 +198,56 @@ def get_custom_fields(self, project):
return fields
+ @need_login
def create_issue(self, project, **kwargs):
- self.location('/projects/%s/issues/new' % project)
+ self.location(self.absurl('projects/%s/issues/new' % project, True))
- assert self.is_on_page(NewIssuePage)
+ assert self.new_issue.is_here()
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])
+ assert self.issue.is_here()
+ return int(self.page.params['id'])
+ @need_login
def edit_issue(self, id, **kwargs):
- self.location('/issues/%s' % id)
+ self.location(self.absurl('issues/%s' % id, True))
- assert self.is_on_page(IssuePage)
+ assert self.issue.is_here()
self.page.fill_form(**kwargs)
- assert self.is_on_page(IssuePage)
- return int(self.page.groups[0])
+ assert self.issue.is_here()
+ return int(self.page.params['id'])
+ @need_login
def remove_issue(self, id):
- self.location('/issues/%s' % id)
+ self.location(self.absurl('issues/%s' % id, True))
- assert self.is_on_page(IssuePage)
+ assert self.issue.is_here()
token = self.page.get_authenticity_token()
data = (('authenticity_token', token),)
- self.openurl('/issues/%s/destroy' % id, urlencode(data))
+ self.open(self.absurl('issues/%s/destroy' % id, True), data=data)
+ @need_login
def iter_projects(self):
- self.location('/projects')
+ self.location(self.absurl('projects', True))
return self.page.iter_projects()
+ @need_login
def create_category(self, project, name, token):
- data = {'issue_category[name]': name.encode('utf-8')}
+ data = {'issue_category[name]': name}
headers = {'X-CSRF-Token': token,
'X-Prototype-Version': '1.7',
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
}
- request = self.request_class(self.absurl(self.buildurl('%s/projects/%s/issue_categories' % (self.BASEPATH, project), **data)),
- '', headers)
- r = self.readurl(request)
+ url = self.absurl('projects/%s/issue_categories' % project, True)
+ r = self.open(url, data=data, headers=headers).text
# Element.replace("issue_category_id", "\u003Cselect id=\"issue_category_id\" name=\"issue[category_id]\"\u003E\u003Coption\u003E\u003C/option\u003E\u003Coption value=\"28\"\u003Ebnporc\u003C/option\u003E\n\u003Coption value=\"31\"\u003Ebp\u003C/option\u003E\n\u003Coption value=\"30\"\u003Ecrag2r\u003C/option\u003E\n\u003Coption value=\"29\"\u003Ecragr\u003C/option\u003E\n\u003Coption value=\"27\"\u003Ei\u003C/option\u003E\n\u003Coption value=\"32\"\u003Elol\u003C/option\u003E\n\u003Coption value=\"33\" selected=\"selected\"\u003Elouiel\u003C/option\u003E\u003C/select\u003E");
diff --git a/modules/redmine/module.py b/modules/redmine/module.py
index 5bf6fa02160ba77480ce1d1f30922f31fe27abd6..831989c2f5ca6d746b866ba922f15972d2c0e3f6 100644
--- a/modules/redmine/module.py
+++ b/modules/redmine/module.py
@@ -25,6 +25,7 @@
from weboob.capabilities.collection import CapCollection, Collection, CollectionNotFound
from weboob.tools.backend import Module, BackendConfig
from weboob.exceptions import BrowserHTTPNotFound
+from weboob.tools.compat import basestring, unicode
from weboob.tools.value import ValueBackendPassword, Value
from .browser import RedmineBrowser
@@ -68,8 +69,7 @@ def get_content(self, id, revision=None):
return None
version = revision.id if revision else None
- with self.browser:
- data = self.browser.get_wiki_source(project, page, version)
+ data = self.browser.get_wiki_source(project, page, version)
content.content = data
return content
@@ -80,8 +80,7 @@ def push_content(self, content, message=None, minor=False):
except ValueError:
return
- with self.browser:
- return self.browser.set_wiki_source(project, page, content.content, message)
+ return self.browser.set_wiki_source(project, page, content.content, message)
def get_content_preview(self, content):
try:
@@ -89,8 +88,7 @@ def get_content_preview(self, content):
except ValueError:
return
- with self.browser:
- return self.browser.get_wiki_preview(project, page, content.content)
+ return self.browser.get_wiki_preview(project, page, content.content)
############# CapCollection ###################################################
def iter_resources(self, objs, split_path):
@@ -196,8 +194,7 @@ def get_issue(self, issue):
issue = Issue(issue)
try:
- with self.browser:
- params = self.browser.get_issue(id)
+ params = self.browser.get_issue(id)
except BrowserHTTPNotFound:
return None
@@ -243,15 +240,13 @@ def get_issue(self, issue):
def create_issue(self, project):
try:
- with self.browser:
- r = self.browser.get_project(project)
+ r = self.browser.get_project(project)
except BrowserHTTPNotFound:
return None
issue = Issue(0)
issue.project = self._build_project(r)
- with self.browser:
- issue.fields = self.browser.get_custom_fields(project)
+ issue.fields = self.browser.get_custom_fields(project)
return issue
def post_issue(self, issue):
@@ -270,11 +265,10 @@ def post_issue(self, issue):
'fields': issue.fields,
}
- with self.browser:
- if int(issue.id) < 1:
- id = self.browser.create_issue(project, **kwargs)
- else:
- id = self.browser.edit_issue(issue.id, **kwargs)
+ if int(issue.id) < 1:
+ id = self.browser.create_issue(project, **kwargs)
+ else:
+ id = self.browser.edit_issue(issue.id, **kwargs)
if id is None:
return None
@@ -286,11 +280,10 @@ def update_issue(self, issue, update):
if isinstance(issue, Issue):
issue = issue.id
- with self.browser:
- if update.hours:
- return self.browser.logtime_issue(issue, update.hours, update.message)
- else:
- return self.browser.comment_issue(issue, update.message)
+ if update.hours:
+ return self.browser.logtime_issue(issue, update.hours, update.message)
+ else:
+ return self.browser.comment_issue(issue, update.message)
def remove_issue(self, issue):
"""
@@ -299,8 +292,7 @@ def remove_issue(self, issue):
if isinstance(issue, Issue):
issue = issue.id
- with self.browser:
- return self.browser.remove_issue(issue)
+ return self.browser.remove_issue(issue)
def iter_projects(self):
"""
@@ -308,14 +300,12 @@ def iter_projects(self):
@return [iter(Project)] projects
"""
- with self.browser:
- for project in self.browser.iter_projects():
- yield Project(project['id'], project['name'])
+ for project in self.browser.iter_projects():
+ yield Project(project['id'], project['name'])
def get_project(self, id):
try:
- with self.browser:
- params = self.browser.get_project(id)
+ params = self.browser.get_project(id)
except BrowserHTTPNotFound:
return None
diff --git a/modules/redmine/pages/index.py b/modules/redmine/pages/index.py
index 707f59c639be0998a26815b16db5f03855c8f5b3..7dc052f22d468f504c70698ab8bceeabd48f637d 100644
--- a/modules/redmine/pages/index.py
+++ b/modules/redmine/pages/index.py
@@ -18,28 +18,34 @@
# along with weboob. If not, see .
-from weboob.deprecated.browser import Page
+from weboob.browser.pages import HTMLPage
-class LoginPage(Page):
+class BaseHTMLPage(HTMLPage):
+ @property
+ def logged(self):
+ return len(self.doc.xpath('//a[has-class("my-account")]'))
+
+
+class LoginPage(HTMLPage):
def login(self, username, password):
- self.browser.select_form(predicate=lambda f: f.attrs.get('method', '') == 'post')
- self.browser['username'] = username.encode(self.browser.ENCODING)
- self.browser['password'] = password.encode(self.browser.ENCODING)
- self.browser.submit()
+ form = self.get_form(xpath='//form[@method="post"]')
+ form['username'] = username
+ form['password'] = password
+ form.submit()
-class IndexPage(Page):
+class IndexPage(BaseHTMLPage):
pass
-class MyPage(Page):
+class MyPage(BaseHTMLPage):
pass
-class ProjectsPage(Page):
+class ProjectsPage(BaseHTMLPage):
def iter_projects(self):
- for ul in self.parser.select(self.document.getroot(), 'ul.projects'):
+ for ul in self.doc.xpath('//ul[has-class("projects")]'):
for li in ul.findall('li'):
prj = {}
link = li.find('div').find('a')
diff --git a/modules/redmine/pages/issues.py b/modules/redmine/pages/issues.py
index 687a3328415cd628d16c5bc0e3193970c7a97914..e0037429f691be6b5353a82dcd7de41be7038655 100644
--- a/modules/redmine/pages/issues.py
+++ b/modules/redmine/pages/issues.py
@@ -21,16 +21,16 @@
import datetime
import re
-import mechanize
-
from weboob.capabilities.bugtracker import IssueError
-from weboob.deprecated.browser import BrokenPageError, Page
from weboob.tools.date import parse_french_date
from weboob.tools.json import json
from weboob.tools.misc import to_unicode
+from weboob.browser.filters.standard import CleanText
+
+from .index import BaseHTMLPage
-class BaseIssuePage(Page):
+class BaseIssuePage(BaseHTMLPage):
def parse_datetime(self, text):
m = re.match('(\d+)/(\d+)/(\d+) (\d+):(\d+) (\w+)', text)
if m:
@@ -66,11 +66,11 @@ def parse_datetime(self, text):
def iter_choices(self, name):
try:
- select = self.parser.select(self.document.getroot(), 'select#%s' % name, 1)
- except BrokenPageError:
+ select, = self.doc.xpath('//select[@id=$id]', id=name)
+ except ValueError:
try:
- select = self.parser.select(self.document.getroot(), 'select#%s_1' % name, 1)
- except BrokenPageError:
+ select, = self.doc.xpath('//select[@id="%s_1"]' % name)
+ except ValueError:
return
for option in select.findall('option'):
if option.attrib['value'].isdigit():
@@ -79,14 +79,14 @@ def iter_choices(self, name):
def get_project(self, project_name):
project = {}
project['name'] = project_name
- for field, elid in self.PROJECT_FIELDS.iteritems():
+ for field, elid in self.PROJECT_FIELDS.items():
project[field] = list(self.iter_choices(elid))
return project
def get_authenticity_token(self):
- tokens = self.parser.select(self.document.getroot(), 'input[name=authenticity_token]')
+ tokens = self.doc.xpath('//input[@name="authenticity_token"]')
if len(tokens) == 0:
- tokens = self.document.xpath('//meta[@name="csrf-token"]')
+ tokens = self.doc.xpath('//meta[@name="csrf-token"]')
if len(tokens) == 0:
raise IssueError("You don't have rights to remove this issue.")
@@ -98,12 +98,12 @@ def get_authenticity_token(self):
def get_errors(self):
errors = []
- for li in self.document.xpath('//div[@id="errorExplanation"]//li'):
+ for li in self.doc.xpath('//div[@id="errorExplanation"]//li'):
errors.append(li.text.strip())
return ', '.join(errors)
def get_value_from_label(self, name, label):
- for option in self.document.xpath('//select[@name="%s"]/option' % name):
+ for option in self.doc.xpath('//select[@name=$name]/option', name=name):
if option.text.strip().lower() == label.lower():
return option.attrib['value']
return label
@@ -121,7 +121,7 @@ def get_from_js(self, pattern, end, is_list=False):
find a pattern in any javascript text
"""
value = None
- for script in self.document.xpath('//script'):
+ for script in self.doc.xpath('//script'):
txt = script.text
if txt is None:
continue
@@ -175,12 +175,12 @@ def get_values(key):
return project
def get_query_method(self):
- return self.document.xpath('//form[@id="query_form"]')[0].attrib['method'].upper()
+ return self.doc.xpath('//form[@id="query_form"]')[0].attrib['method'].upper()
def iter_issues(self):
try:
- issues = self.parser.select(self.document.getroot(), 'table.issues', 1)
- except BrokenPageError:
+ issues, = self.doc.xpath('//table[has-class("issues")]')
+ except ValueError:
# No results.
return
@@ -227,41 +227,40 @@ class NewIssuePage(BaseIssuePage):
'statuses': 'issue_status_id',
}
+ form = None
+
def get_project_name(self):
m = re.search('/projects/([^/]+)/', self.url)
return m.group(1)
def iter_custom_fields(self):
- for div in self.document.xpath('//form//input[starts-with(@id, "issue_custom_field")]|//form//select[starts-with(@id, "issue_custom_field")]'):
+ for div in self.doc.xpath('//form//input[starts-with(@id, "issue_custom_field")]|//form//select[starts-with(@id, "issue_custom_field")]'):
if 'type' in div.attrib and div.attrib['type'] == 'hidden':
continue
- label = self.document.xpath('//label[@for="%s"]' % div.attrib['id'])[0]
- yield self.parser.tocleanstring(label), div
+ label = self.doc.xpath('//label[@for="%s"]' % div.attrib['id'])[0]
+ yield CleanText('.')(label), div
def set_title(self, title):
- self.browser['issue[subject]'] = title.encode('utf-8')
+ self.form['issue[subject]'] = title
def set_body(self, body):
- self.browser['issue[description]'] = body.encode('utf-8')
+ self.form['issue[description]'] = body
def set_assignee(self, member):
if member:
- self.browser['issue[assigned_to_id]'] = [str(member)]
+ self.form['issue[assigned_to_id]'] = [str(member)]
else:
- self.browser['issue[assigned_to_id]'] = ['']
+ self.form['issue[assigned_to_id]'] = ['']
def set_version(self, version):
- try:
- if version:
- self.browser['issue[fixed_version_id]'] = [str(version)]
- else:
- self.browser['issue[fixed_version_id]'] = ['']
- except mechanize.ItemNotFoundError:
- self.logger.warning('Version not found: %s' % version)
+ if version:
+ self.form['issue[fixed_version_id]'] = [str(version)]
+ else:
+ self.form['issue[fixed_version_id]'] = ['']
def set_tracker(self, tracker):
if tracker:
- select = self.parser.select(self.document.getroot(), 'select#issue_tracker_id', 1)
+ select, = self.doc.xpath('//select[@id="issue_tracker_id"]')
for option in select.findall('option'):
if option.text and option.text.strip() == tracker:
self.browser['issue[tracker_id]'] = [option.attrib['value']]
@@ -277,43 +276,36 @@ def set_tracker(self, tracker):
# self.logger.warning('Tracker "%s" not found' % tracker)
self.logger.warning('Tracker "%s" not found' % tracker)
else:
- try:
- self.browser['issue[tracker_id]'] = ['']
- except mechanize.ControlNotFoundError:
- self.logger.warning('Tracker item not found')
+ self.form['issue[tracker_id]'] = ['']
def set_category(self, category):
if category:
- select = self.parser.select(self.document.getroot(), 'select#issue_category_id', 1)
+ select = self.doc.xpath('//select[@id="issue_category_id"]')
for option in select.findall('option'):
if option.text and option.text.strip() == category:
- self.browser['issue[category_id]'] = [option.attrib['value']]
+ self.form['issue[category_id]'] = [option.attrib['value']]
return
value = None
- if len(self.document.xpath('//a[@title="New category"]')) > 0:
+ if len(self.doc.xpath('//a[@title="New category"]')) > 0:
value = self.browser.create_category(self.get_project_name(), category, self.get_authenticity_token())
if value:
- control = self.browser.find_control('issue[category_id]')
- mechanize.Item(control, {'name': category, 'value': value})
- self.browser['issue[category_id]'] = [value]
+ self.form[category] = value
+ self.form['issue[category_id]'] = [value]
else:
self.logger.warning('Category "%s" not found' % category)
else:
- try:
- self.browser['issue[category_id]'] = ['']
- except mechanize.ControlNotFoundError:
- self.logger.warning('Category item not found')
+ self.form['issue[category_id]'] = ['']
def set_status(self, status):
assert status is not None
- self.browser['issue[status_id]'] = [str(status)]
+ self.form['issue[status_id]'] = [str(status)]
def set_priority(self, priority):
if priority:
- select = self.parser.select(self.document.getroot(), 'select#issue_priority_id', 1)
+ select, = self.doc.xpath('//select[@id="issue_priority_id"]')
for option in select.findall('option'):
if option.text and option.text.strip() == priority:
- self.browser['issue[priority_id]'] = [option.attrib['value']]
+ self.form['issue[priority_id]'] = [option.attrib['value']]
return
# value = None
# if len(self.document.xpath('//a[@title="New priority"]')) > 0:
@@ -326,53 +318,44 @@ def set_priority(self, priority):
# self.logger.warning('Priority "%s" not found' % priority)
self.logger.warning('Priority "%s" not found' % priority)
else:
- try:
- self.browser['issue[priority_id]'] = ['']
- except mechanize.ControlNotFoundError:
- self.logger.warning('Priority item not found')
+ self.form['issue[priority_id]'] = ['']
def set_start(self, start):
if start is not None:
- self.browser['issue[start_date]'] = start.strftime("%Y-%m-%d")
+ self.form['issue[start_date]'] = start.strftime("%Y-%m-%d")
#XXX: else set to "" ?
def set_due(self, due):
if due is not None:
- self.browser['issue[due_date]'] = due.strftime("%Y-%m-%d")
+ self.form['issue[due_date]'] = due.strftime("%Y-%m-%d")
#XXX: else set to "" ?
def set_note(self, message):
- try:
- self.browser['notes'] = message.encode('utf-8')
- except mechanize.ControlNotFoundError:
- self.browser['issue[notes]'] = message.encode('utf-8')
+ if 'notes' in self.form:
+ self.form['notes'] = message
+ else:
+ self.form['issue[notes]'] = message
def set_fields(self, fields):
for key, div in self.iter_custom_fields():
try:
- control = self.browser.find_control(div.attrib['name'], nr=0)
- if isinstance(control, mechanize.TextControl):
- control.value = fields[key]
- else:
- item = control.get(label=fields[key].encode("utf-8"), nr=0)
- if item and fields[key] != '':
- item.selected = True
+ self.form[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():
+ self.form = self.get_form(id='issue-form')
+ for key, value in kwargs.items():
if value is not None:
getattr(self, 'set_%s' % key)(value)
- self.browser.submit()
+ self.form.submit()
class IssuePage(NewIssuePage):
def _parse_selection(self, id):
try:
- select = self.parser.select(self.document.getroot(), 'select#%s' % id, 1)
- except BrokenPageError:
+ select, = self.doc.xpath('//select[@id=$id]', id=id)
+ except ValueError:
# not available for this project
return ('', None)
else:
@@ -384,13 +367,13 @@ def _parse_selection(self, id):
def get_params(self):
params = {}
- content = self.parser.select(self.document.getroot(), 'div#content', 1)
- issue = self.parser.select(content, 'div.issue', 1)
+ content, = self.doc.xpath('//div[@id="content"]')
+ issue, = content.xpath('.//div[has-class("issue")]')
- params['project'] = self.get_project(to_unicode(self.parser.select(self.document.getroot(), 'h1', 1).text))
- params['subject'] = to_unicode(self.parser.select(issue, 'div.subject', 1).find('div').find('h3').text.strip())
- params['body'] = to_unicode(self.parser.select(self.document.getroot(), 'textarea#issue_description', 1).text)
- author = self.parser.select(issue, 'p.author', 1)
+ params['project'] = self.get_project(CleanText('(//h1)[1]')(self.doc))
+ params['subject'] = issue.xpath('.//div[has-class("subject")]/div/h3')[0].text.strip()
+ params['body'] = self.doc.xpath('//textarea[@id="issue_description"]')[0].text.strip()
+ author, = issue.xpath('.//p[has-class("author")]')
# check issue 666 on symlink.me
i = 0
@@ -413,12 +396,12 @@ def get_params(self):
params['tracker'] = self._parse_selection('issue_tracker_id')
params['category'] = self._parse_selection('issue_category_id')
params['version'] = self._parse_selection('issue_fixed_version_id')
- div = self.parser.select(self.document.getroot(), 'input#issue_start_date', 1)
+ div, = self.doc.xpath('//input[@id="issue_start_date"]')
if 'value' in div.attrib:
params['start_date'] = datetime.datetime.strptime(div.attrib['value'], "%Y-%m-%d")
else:
params['start_date'] = None
- div = self.parser.select(self.document.getroot(), 'input#issue_due_date', 1)
+ div, = self.doc.xpath('//input[@id="issue_due_date"]')
if 'value' in div.attrib:
params['due_date'] = datetime.datetime.strptime(div.attrib['value'], "%Y-%m-%d")
else:
@@ -436,19 +419,16 @@ def get_params(self):
params['fields'][key] = value
params['attachments'] = []
- try:
- for p in self.parser.select(content, 'div.attachments', 1).findall('p'):
- attachment = {}
- a = p.find('a')
- attachment['id'] = int(a.attrib['href'].split('/')[-2])
- attachment['filename'] = p.find('a').text
- attachment['url'] = '%s://%s%s' % (self.browser.PROTOCOL, self.browser.DOMAIN, p.find('a').attrib['href'])
- params['attachments'].append(attachment)
- except BrokenPageError:
- pass
+ for p in content.xpath('//div[has-class("attachments")]/p'):
+ attachment = {}
+ a = p.find('a')
+ attachment['id'] = int(a.attrib['href'].split('/')[-2])
+ attachment['filename'] = p.find('a').text
+ attachment['url'] = '%s%s' % (self.browser.BASEURL, p.find('a').attrib['href'])
+ params['attachments'].append(attachment)
params['updates'] = []
- for div in self.parser.select(content, 'div.journal'):
+ for div in content.xpath('.//div[has-class("journal")]'):
update = {}
update['id'] = div.xpath('.//h4//a')[0].text[1:]
user_link = div.xpath('.//h4//a[contains(@href, "/users/")]')
@@ -477,8 +457,8 @@ def get_params(self):
changes = []
try:
- details = self.parser.select(div, 'ul.details', 1)
- except BrokenPageError:
+ details, = div.xpath('.//ul[has-class("details")]')
+ except ValueError:
pass
else:
for li in details.findall('li'):
@@ -504,7 +484,7 @@ def get_params(self):
return params
-class IssueLogTimePage(Page):
+class IssueLogTimePage(BaseHTMLPage):
def logtime(self, hours, message):
self.browser.select_form(predicate=lambda form: form.attrs.get('action', '').endswith('/edit'))
self.browser['time_entry[hours]'] = '%.2f' % hours
@@ -513,5 +493,5 @@ def logtime(self, hours, message):
self.browser.submit()
-class IssueTimeEntriesPage(Page):
+class IssueTimeEntriesPage(BaseHTMLPage):
pass
diff --git a/modules/redmine/pages/wiki.py b/modules/redmine/pages/wiki.py
index c23b4594fc8e39fe06a6d9e567726b1673842cbe..325e785e731d052b50f1c45c92c9fa6c354491be 100644
--- a/modules/redmine/pages/wiki.py
+++ b/modules/redmine/pages/wiki.py
@@ -17,25 +17,27 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see .
+from .index import BaseHTMLPage
-from weboob.deprecated.browser import Page
-
-class WikiEditPage(Page):
+class WikiEditPage(BaseHTMLPage):
def get_source(self):
- return self.parser.select(self.document.getroot(), 'textarea#content_text', 1).text
+ return self.doc.xpath('//textarea[@id="content_text"]')[0].text
def set_source(self, data, message):
- self.browser.select_form(nr=1)
- self.browser['content[text]'] = data.encode('utf-8')
+ form = self.get_form(nr=1)
+ form['content[text]'] = data
if message:
- self.browser['content[comments]'] = message.encode('utf-8')
- self.browser.submit()
+ form['content[comments]'] = message
+ form.submit()
def get_authenticity_token(self):
- wiki_form = self.parser.select(self.document.getroot(), 'form#wiki_form', 1)
- return wiki_form.xpath('div/input')[0].get('value')
+ form = self.get_form(id='wiki_form')
+ return form['authenticity_token']
+
+ def get_submit(self):
+ return self.get_form(id='wiki_form')
-class WikiPage(Page):
+class WikiPage(BaseHTMLPage):
pass
diff --git a/tools/py3-compatible.modules b/tools/py3-compatible.modules
index 0850053873c68f0e7f3b689831baa98480cd5f85..6d6dfaa829710e231b692ee2a07bdaa5fc08ff76 100644
--- a/tools/py3-compatible.modules
+++ b/tools/py3-compatible.modules
@@ -107,6 +107,7 @@ pornhub
ratp
razibus
reddit
+redmine
regionsjob
relaiscolis
s2e