weboob-devel/modules/redmine/module.py
Romain Bignon d61e15cf84 rename things related to browsers
weboob.tools.browser -> weboob.deprecated.browser
weboob.tools.parsers -> weboob.deprecated.browser.parsers
weboob.tools.mech -> weboob.deprecated.mech
weboob.browser2 -> weboob.browser
weboob.core.exceptions -> weboob.exceptions

Also, the new tree for browser2 is:

weboob.browser: import weboob.browser.browsers.* and weboob.browser.url.*
weboob.browser.browsers: all browsers (including PagesBrowser and LoginBrowser)
weboob.browser.url: the URL class
weboob.browser.profiles: all Profile classes
weboob.browser.sessions: WeboobSession and FuturesSession
weboob.browser.cookies: that's a cookies thing
weboob.browser.pages: all Page and derivated classes, and Form class
weboob.browser.exceptions: specific browser exceptions
weboob.browser.elements: AbstractElement classes, and 'method' decorator
weboob.browser.filters.*: all filters
2014-10-07 00:30:07 +02:00

329 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Copyright(C) 2010-2011 Romain Bignon
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from weboob.capabilities.content import CapContent, Content
from weboob.capabilities.bugtracker import CapBugTracker, Issue, Project, User, \
Version, Status, Update, Attachment, \
Query, Change
from weboob.capabilities.collection import CapCollection, Collection, CollectionNotFound
from weboob.tools.backend import Module, BackendConfig
from weboob.exceptions import BrowserHTTPNotFound
from weboob.tools.value import ValueBackendPassword, Value
from .browser import RedmineBrowser
__all__ = ['RedmineModule']
class RedmineModule(Module, CapContent, CapBugTracker, CapCollection):
NAME = 'redmine'
MAINTAINER = u'Romain Bignon'
EMAIL = 'romain@weboob.org'
VERSION = '1.0'
DESCRIPTION = 'The Redmine project management web application'
LICENSE = 'AGPLv3+'
CONFIG = BackendConfig(Value('url', label='URL of the Redmine website', regexp=r'https?://.*'),
Value('username', label='Login'),
ValueBackendPassword('password', label='Password'))
BROWSER = RedmineBrowser
def create_default_browser(self):
return self.create_browser(self.config['url'].get(),
self.config['username'].get(),
self.config['password'].get())
############# CapContent ######################################################
def id2path(self, id):
return id.split('/', 2)
def get_content(self, id, revision=None):
if isinstance(id, basestring):
content = Content(id)
else:
content = id
id = content.id
try:
_type, project, page = self.id2path(id)
except ValueError:
return None
version = revision.id if revision else None
with self.browser:
data = self.browser.get_wiki_source(project, page, version)
content.content = data
return content
def push_content(self, content, message=None, minor=False):
try:
_type, project, page = self.id2path(content.id)
except ValueError:
return
with self.browser:
return self.browser.set_wiki_source(project, page, content.content, message)
def get_content_preview(self, content):
try:
_type, project, page = self.id2path(content.id)
except ValueError:
return
with self.browser:
return self.browser.get_wiki_preview(project, page, content.content)
############# CapCollection ###################################################
def iter_resources(self, objs, split_path):
if Project in objs or Issue in objs:
self._restrict_level(split_path, 1)
if len(split_path) == 0:
return [Collection([project.id], project.name)
for project in self.iter_projects()]
elif len(split_path) == 1:
query = Query()
query.project = unicode(split_path[0])
return self.iter_issues(query)
def validate_collection(self, objs, collection):
if collection.path_level == 0:
return
if Issue in objs and collection.path_level == 1:
for project in self.iter_projects():
if collection.basename == project.id:
return Collection([project.id], project.name)
# if the project is not found by ID, try again by name
for project in self.iter_projects():
if collection.basename == project.name:
return Collection([project.id], project.name)
raise CollectionNotFound(collection.split_path)
############# CapBugTracker ###################################################
@classmethod
def _build_project(cls, project_dict):
project = Project(project_dict['name'], project_dict['name'])
project.members = [User(int(u[0]), u[1]) for u in project_dict['members']]
project.versions = [Version(int(v[0]), v[1]) for v in project_dict['versions']]
project.categories = [c[1] for c in project_dict['categories']]
# TODO set the value of status
project.statuses = [Status(int(s[0]), s[1], 0) for s in project_dict['statuses']]
return project
@staticmethod
def _attr_to_id(availables, text):
if not text:
return None
if isinstance(text, basestring) and text.isdigit():
return text
for value, key in availables:
if key.lower() == text.lower():
return value
return text
def iter_issues(self, query):
"""
Iter issues with optionnal patterns.
@param query [Query]
@return [iter(Issue)] issues
"""
params = self.browser.get_project(query.project)
kwargs = {'subject': query.title,
'author_id': self._attr_to_id(params['members'], query.author),
'assigned_to_id': self._attr_to_id(params['members'], query.assignee),
'fixed_version_id': self._attr_to_id(params['versions'], query.version),
'category_id': self._attr_to_id(params['categories'], query.category),
'status_id': self._attr_to_id(params['statuses'], query.status),
}
r = self.browser.query_issues(query.project, **kwargs)
project = self._build_project(r['project'])
for issue in r['iter']:
obj = Issue(issue['id'])
obj.project = project
obj.title = issue['subject']
obj.creation = issue['created_on']
obj.updated = issue['updated_on']
obj.start = issue['start_date']
obj.due = issue['due_date']
if isinstance(issue['author'], tuple):
obj.author = project.find_user(*issue['author'])
else:
obj.author = User(0, issue['author'])
if isinstance(issue['assigned_to'], tuple):
obj.assignee = project.find_user(*issue['assigned_to'])
else:
obj.assignee = issue['assigned_to']
obj.tracker = issue['tracker']
obj.category = issue['category']
if issue['fixed_version'] is not None:
obj.version = project.find_version(*issue['fixed_version'])
else:
obj.version = None
obj.status = project.find_status(issue['status'])
obj.priority = issue['priority']
yield obj
def get_issue(self, issue):
if isinstance(issue, Issue):
id = issue.id
else:
id = issue
issue = Issue(issue)
try:
with self.browser:
params = self.browser.get_issue(id)
except BrowserHTTPNotFound:
return None
issue.project = self._build_project(params['project'])
issue.title = params['subject']
issue.body = params['body']
issue.creation = params['created_on']
issue.updated = params['updated_on']
issue.start = params['start_date']
issue.due = params['due_date']
issue.fields = {}
for key, value in params['fields'].iteritems():
issue.fields[key] = value
issue.attachments = []
for a in params['attachments']:
attachment = Attachment(a['id'])
attachment.filename = a['filename']
attachment.url = a['url']
issue.attachments.append(attachment)
issue.history = []
for u in params['updates']:
update = Update(u['id'])
update.author = issue.project.find_user(*u['author'])
update.date = u['date']
update.message = u['message']
update.changes = []
for i, (field, last, new) in enumerate(u['changes']):
change = Change(i)
change.field = field
change.last = last
change.new = new
update.changes.append(change)
issue.history.append(update)
issue.author = issue.project.find_user(*params['author'])
issue.assignee = issue.project.find_user(*params['assignee'])
issue.tracker = params['tracker'][1]
issue.category = params['category'][1]
issue.version = issue.project.find_version(*params['version'])
issue.status = issue.project.find_status(params['status'][1])
issue.priority = params['priority'][1]
return issue
def create_issue(self, project):
try:
with self.browser:
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)
return issue
def post_issue(self, issue):
project = issue.project.id
kwargs = {'title': issue.title,
'version': issue.version.id if issue.version else None,
'assignee': issue.assignee.id if issue.assignee else None,
'tracker': issue.tracker if issue.tracker else None,
'category': issue.category,
'status': issue.status.id if issue.status else None,
'priority': issue.priority if issue.priority else None,
'start': issue.start if issue.start else None,
'due': issue.due if issue.due else None,
'body': issue.body,
'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 id is None:
return None
issue.id = id
return issue
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)
def remove_issue(self, issue):
"""
Remove an issue.
"""
if isinstance(issue, Issue):
issue = issue.id
with self.browser:
return self.browser.remove_issue(issue)
def iter_projects(self):
"""
Iter projects.
@return [iter(Project)] projects
"""
with self.browser:
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)
except BrowserHTTPNotFound:
return None
return self._build_project(params)
def fill_issue(self, issue, fields):
return self.get_issue(issue)
OBJECTS = {Issue: fill_issue}