Add pivotal importer prototype

remotes/origin/github-import
Jesús Espino 2017-01-18 09:42:34 +01:00 committed by David Barragán Merino
parent 477d964770
commit 15c2868d24
4 changed files with 994 additions and 0 deletions

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.management.base import BaseCommand
from django.db.models import Q
from taiga.importers.pivotal import PivotalImporter
from taiga.users.models import User
from taiga.projects.services import projects as service
import unittest.mock
import timeit
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--token', dest="token", type=str,
help='Auth token')
parser.add_argument('--project-id', dest="project_id", type=str,
help='Project ID or full name (ex: taigaio/taiga-back)')
parser.add_argument('--template', dest='template', default="scrum",
help='template to use: scrum or scrum (default scrum)')
parser.add_argument('--ask-for-users', dest='ask_for_users', const=True,
action="store_const", default=False,
help='Import closed data')
parser.add_argument('--closed-data', dest='closed_data', const=True,
action="store_const", default=False,
help='Import closed data')
parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True,
action="store_const", default=False,
help='Store external reference of imported data')
def handle(self, *args, **options):
admin = User.objects.get(username="admin")
if options.get('token', None):
token = options.get('token')
else:
print("You need a user token")
return
importer = PivotalImporter(admin, token)
if options.get('project_id', None):
project_id = options.get('project_id')
else:
print("Select the project to import:")
for project in importer.list_projects():
print("- {}: {}".format(project['project_id'], project['project_name']))
project_id = input("Project id: ")
users_bindings = {}
if options.get('ask_for_users', None):
print("Add the username or email for next pivotal users:")
for user in importer.list_users(project_id):
try:
users_bindings[user['id']] = User.objects.get(Q(email=user['person'].get('email', "not-valid")))
break
except User.DoesNotExist:
pass
while True:
username_or_email = input("{}: ".format(user['person']['name']))
if username_or_email == "":
break
try:
users_bindings[user['id']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
break
except User.DoesNotExist:
print("ERROR: Invalid username or email")
options = {
"template": options.get('template'),
"import_closed_data": options.get("closed_data", False),
"users_bindings": users_bindings,
"keep_external_reference": options.get('keep_external_reference')
}
importer.import_project(project_id, options)

View File

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Taiga Agile LLC <support@taiga.io>
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext as _
from django.conf import settings
from taiga.base.api import viewsets
from taiga.base import response
from taiga.base import exceptions as exc
from taiga.base.decorators import list_route
from taiga.users.models import AuthData, User
from taiga.users.services import get_user_photo_url
from taiga.users.gravatar import get_user_gravatar_id
from taiga.projects.serializers import ProjectSerializer
from taiga.importers import permissions
from .importer import PivotalImporter
from . import tasks
class PivotalImporterViewSet(viewsets.ViewSet):
permission_classes = (permissions.ImporterPermission,)
@list_route(methods=["POST"])
def list_users(self, request, *args, **kwargs):
self.check_permissions(request, "list_users", None)
token = request.DATA.get('token', None)
project_id = request.DATA.get('project', None)
if not project_id:
raise exc.WrongArguments(_("The project param is needed"))
importer = PivotalImporter(request.user, token)
users = importer.list_users(project_id)
for user in users:
user['user'] = None
if not user['email']:
continue
try:
taiga_user = User.objects.get(email=user['email'])
except User.DoesNotExist:
continue
user['user'] = {
'id': taiga_user.id,
'full_name': taiga_user.get_full_name(),
'gravatar_id': get_user_gravatar_id(taiga_user),
'photo': get_user_photo_url(taiga_user),
}
return response.Ok(users)
@list_route(methods=["POST"])
def list_projects(self, request, *args, **kwargs):
self.check_permissions(request, "list_projects", None)
token = request.DATA.get('token', None)
importer = PivotalImporter(request.user, token)
projects = importer.list_projects()
return response.Ok(projects)
@list_route(methods=["POST"])
def import_project(self, request, *args, **kwargs):
self.check_permissions(request, "import_project", None)
token = request.DATA.get('token', None)
project_id = request.DATA.get('project', None)
if not project_id:
raise exc.WrongArguments(_("The project param is needed"))
options = {
"template": request.DATA.get('template', "kanban"),
"users_bindings": request.DATA.get("users_bindings", {}),
"keep_external_reference": request.DATA.get("keep_external_reference", False),
"is_private": request.DATA.get("is_private", False),
}
if settings.CELERY_ENABLED:
task = tasks.import_project.delay(request.user.id, token, project_id, options)
return response.Accepted({"pivotal_import_id": task.id})
importer = PivotalImporter(request.user, token)
project = importer.import_project(project_id, options)
project_data = {
"slug": project.slug,
"my_permissions": ["view_us"],
"is_backlog_activated": project.is_backlog_activated,
"is_kanban_activated": project.is_kanban_activated,
}
return response.Ok(project_data)
@list_route(methods=["GET"])
def auth_url(self, request, *args, **kwargs):
self.check_permissions(request, "auth_url", None)
(oauth_token, oauth_secret, url) = PivotalImporter.get_auth_url()
(auth_data, created) = AuthData.objects.get_or_create(
user=request.user,
key="pivotal-oauth",
defaults={
"value": "",
"extra": {},
}
)
auth_data.extra = {
"oauth_token": oauth_token,
"oauth_secret": oauth_secret,
}
auth_data.save()
return response.Ok({"url": url})
@list_route(methods=["POST"])
def authorize(self, request, *args, **kwargs):
self.check_permissions(request, "authorize", None)
try:
oauth_data = request.user.auth_data.get(key="pivotal-oauth")
oauth_token = oauth_data.extra['oauth_token']
oauth_secret = oauth_data.extra['oauth_secret']
oauth_verifier = request.DATA.get('code')
oauth_data.delete()
pivotal_token = PivotalImporter.get_access_token(oauth_token, oauth_secret, oauth_verifier)['oauth_token']
except Exception as e:
raise exc.WrongArguments(_("Invalid or expired auth token"))
return response.Ok({
"token": pivotal_token
})

View File

@ -0,0 +1,702 @@
from django.core.files.base import ContentFile
from django.contrib.contenttypes.models import ContentType
import requests
from taiga.users.models import User
from taiga.projects.references.models import recalc_reference_counter
from taiga.projects.models import Project, ProjectTemplate, Membership, Points
from taiga.projects.userstories.models import UserStory, RolePoints
from taiga.projects.tasks.models import Task
from taiga.projects.milestones.models import Milestone
from taiga.projects.epics.models import Epic, RelatedUserStory
from taiga.projects.attachments.models import Attachment
from taiga.projects.history.services import take_snapshot
from taiga.projects.history.services import (make_diff_from_dicts,
make_diff_values,
make_key_from_model_object,
get_typename_for_model_class,
FrozenDiff)
from taiga.projects.history.models import HistoryEntry
from taiga.projects.history.choices import HistoryType
from taiga.projects.custom_attributes.models import UserStoryCustomAttribute
from taiga.mdrender.service import render as mdrender
from taiga.timeline.rebuilder import rebuild_timeline
from taiga.timeline.models import Timeline
class PivotalClient:
def __init__(self, token):
self.api_url = "https://www.pivotaltracker.com/services/v5/{}"
self.token = token
self.me = self.get('/me')
def get(self, uri_path, query_params=None):
headers = {
'X-TrackerToken': self.token
}
if query_params is None:
query_params = {}
if uri_path[0] == '/':
uri_path = uri_path[1:]
url = self.api_url.format(uri_path)
response = requests.get(url, params=query_params, headers=headers)
if response.status_code == 401:
raise Exception("Unauthorized: %s at %s" % (response.text, url), response)
if response.status_code != 200:
raise Exception("Resource Unavailable: %s at %s" % (response.text, url), response)
return response.json()
def get_attachment(self, attachment_id):
headers = {
'X-TrackerToken': self.token
}
url = "https://www.pivotaltracker.com/file_attachments/{}/download".format(attachment_id)
response = requests.get(url, headers=headers)
return response.content
class PivotalImporter:
def __init__(self, user, token):
self._user = user
self._client = PivotalClient(token=token)
def list_projects(self):
return self._client.me['projects']
def list_users(self, project_id):
return self._client.get("/projects/{}/memberships".format(project_id))
def import_project(self, project_id, options={"template": "scrum", "users_bindings": {}, "keep_external_reference": False}):
(project, project_data) = self._import_project_data(project_id, options)
self._import_epics_data(project_data, project, options)
self._import_user_stories_data(project_data, project, options)
Timeline.objects.filter(project=project).delete()
rebuild_timeline(None, None, project.id)
recalc_reference_counter(project)
def _import_project_data(self, project_id, options):
project_data = self._client.get(
"/projects/{}".format(project_id),
{
"fields": ",".join([
"point_scale",
"name",
"description",
"labels(name)",
])
}
)
project_data['iterations'] = self._client.get(
"/projects/{}/iterations".format(project_id),
{
"fields": ",".join([
"number",
"start",
"finish",
"stories",
])
}
)
project_data['epics'] = self._client.get(
"/projects/{}/epics".format(project_data['id']),
{
"fields": ",".join([
"name",
"label",
"description",
"comments(text,file_attachments,google_attachments,person,created_at)",
"follower_ids",
"created_at",
"updated_at",
"url",
])
}
)
project_template = ProjectTemplate.objects.get(slug=options['template'])
project_template.is_epics_activated = True
project_template.us_statuses = []
project_template.points = [{
"value": None,
"name": "?",
"order": 1,
}]
counter = 2
for points in project_data['point_scale'].split(","):
project_template.points.append({
"value": int(points),
"name": points,
"order": counter
})
counter += 1
project_template.us_statuses.append({
"name": "Unscheduled",
"slug": "unscheduled",
"is_closed": True,
"is_archived": False,
"color": "#999999",
"wip_limit": None,
"order": 1,
})
project_template.us_statuses.append({
"name": "Unstarted",
"slug": "unstarted",
"is_closed": False,
"is_archived": False,
"color": "#999999",
"wip_limit": None,
"order": 2,
})
project_template.us_statuses.append({
"name": "Planned",
"slug": "planned",
"is_closed": False,
"is_archived": False,
"color": "#999999",
"wip_limit": None,
"order": 3,
})
project_template.us_statuses.append({
"name": "Started",
"slug": "started",
"is_closed": False,
"is_archived": False,
"color": "#999999",
"wip_limit": None,
"order": 4,
})
project_template.us_statuses.append({
"name": "Finished",
"slug": "finished",
"is_closed": False,
"is_archived": False,
"color": "#999999",
"wip_limit": None,
"order": 5,
})
project_template.us_statuses.append({
"name": "Delivered",
"slug": "delivered",
"is_closed": True,
"is_archived": False,
"color": "#999999",
"wip_limit": None,
"order": 6,
})
project_template.us_statuses.append({
"name": "Rejected",
"slug": "rejected",
"is_closed": True,
"is_archived": True,
"color": "#999999",
"wip_limit": None,
"order": 7,
})
project_template.us_statuses.append({
"name": "Accepted",
"slug": "accepted",
"is_closed": True,
"is_archived": True,
"color": "#999999",
"wip_limit": None,
"order": 8,
})
project_template.default_options["us_status"] = "Unscheduled"
project_template.task_statuses = []
project_template.task_statuses.append({
"name": "Incomplete",
"slug": "incomplete",
"is_closed": False,
"color": "#ff8a84",
"order": 1,
})
project_template.task_statuses.append({
"name": "Complete",
"slug": "complete",
"is_closed": True,
"color": "#669900",
"order": 2,
})
project_template.default_options["task_status"] = "Incomplete"
main_permissions = project_template.roles[0]['permissions']
project_template.roles = [{
"name": "Main",
"slug": "main",
"computable": True,
"permissions": main_permissions,
"order": 70,
}]
tags_colors = []
for label in project_data['labels']:
name = label['name'].lower()
tags_colors.append([name, None])
project = Project.objects.create(
name=project_data['name'],
description=project_data.get('description', ''),
owner=self._user,
tags_colors=tags_colors,
creation_template=project_template
)
UserStoryCustomAttribute.objects.create(
name="Due date",
description="Due date",
type="date",
order=1,
project=project
)
UserStoryCustomAttribute.objects.create(
name="Type",
description="Story type",
type="text",
order=2,
project=project
)
for user in options.get('users_bindings', {}).values():
if user != self._user:
Membership.objects.get_or_create(
user=user,
project=project,
role=project.get_roles().get(slug="main"),
is_admin=False,
)
for iteration in project_data['iterations']:
milestone = Milestone.objects.create(
name="Sprint {}".format(iteration['number']),
slug="sprint-{}".format(iteration['number']),
owner=self._user,
project=project,
estimated_start=iteration['start'][:10],
estimated_finish=iteration['finish'][:10],
)
Milestone.objects.filter(id=milestone.id).update(
created_date=iteration['start'],
modified_date=iteration['start'],
)
return (project, project_data)
def _import_user_stories_data(self, project_data, project, options):
users_bindings = options.get('users_bindings', {})
epics = {e['label']['id']: e for e in project_data['epics']}
due_date_field = project.userstorycustomattributes.get(name="Due date")
story_type_field = project.userstorycustomattributes.get(name="Type")
story_milestone_binding = {}
for iteration in project_data['iterations']:
for story in iteration['stories']:
story_milestone_binding[story['id']] = Milestone.objects.get(
project=project,
slug="sprint-{}".format(iteration['number'])
)
counter = 0
offset = 0
while True:
stories = self._client.get("/projects/{}/stories".format(project_data['id']), {
"envelope": "true",
"limit": 300,
"offset": offset,
"fields": ",".join([
"name",
"description",
"estimate",
"story_type",
"current_state",
"deadline",
"requested_by_id",
"owner_ids",
"labels(id,name)",
"comments(text,file_attachments,google_attachments,person,created_at)",
"tasks(id,description,position,complete,created_at,updated_at)",
"follower_ids",
"created_at",
"updated_at",
"url",
])})
offset += 300
for story in stories['data']:
tags = []
for label in story['labels']:
tags.append(label['name'])
assigned_to = None
if len(story['owner_ids']) > 0:
assigned_to = users_bindings.get(story['owner_ids'][0], None)
owner = users_bindings.get(story['requested_by_id'], self._user)
external_reference = None
if options.get('keep_external_reference', False):
external_reference = ["pivotal", story['url']]
us = UserStory.objects.create(
project=project,
owner=owner,
assigned_to=assigned_to,
status=project.us_statuses.get(slug=story['current_state']),
kanban_order=counter,
sprint_order=counter,
backlog_order=counter,
subject=story['name'],
description=story.get('description', ''),
tags=tags,
external_reference=external_reference,
milestone=story_milestone_binding.get(story['id'], None)
)
points = Points.objects.get(project=project, value=story.get('estimate', None))
RolePoints.objects.filter(user_story=us, role__slug="main").update(points_id=points.id)
if len(story['owner_ids']) > 1:
watchers = list(set(story['owner_ids'][1:] + story['follower_ids']))
else:
watchers = story['follower_ids']
for watcher in watchers:
watcher_user = users_bindings.get(watcher, None)
if watcher_user:
us.add_watcher(watcher_user)
if story.get('deadline', None):
us.custom_attributes_values.attributes_values = {due_date_field.id: story['deadline']}
us.custom_attributes_values.save()
if story.get('story_type', None):
us.custom_attributes_values.attributes_values = {story_type_field.id: story['story_type']}
us.custom_attributes_values.save()
UserStory.objects.filter(id=us.id).update(
ref=story['id'],
modified_date=story['updated_at'],
created_date=story['created_at']
)
take_snapshot(us, comment="", user=None, delete=False)
for label in story['labels']:
if epics.get(label['id'], None):
RelatedUserStory.objects.create(
epic=Epic.objects.get(project=project, ref=epics.get(label['id'])['id']),
user_story=us,
order=us.backlog_order
)
self._import_tasks(project_data, us, story)
self._import_user_story_activity(project_data, us, story, options)
self._import_comments(project_data, us, story, options)
counter += 1
if len(stories['data']) < 300:
break
def _import_epics_data(self, project_data, project, options):
users_bindings = options.get('users_bindings', {})
counter = 0
for epic in project_data['epics']:
external_reference = None
if options.get('keep_external_reference', False):
external_reference = ["pivotal", epic['url']]
taiga_epic = Epic.objects.create(
project=project,
owner=self._user,
status=project.epic_statuses.get(slug="new"),
epics_order=counter,
subject=epic['name'],
description=epic.get('description', ''),
tags=[],
external_reference=external_reference
)
Epic.objects.filter(id=taiga_epic.id).update(
ref=epic['id'],
modified_date=epic['updated_at'],
created_date=epic['created_at']
)
for watcher in epic['follower_ids']:
watcher_user = users_bindings.get(watcher, None)
if watcher_user:
taiga_epic.add_watcher(watcher_user)
take_snapshot(taiga_epic, comment="", user=None, delete=False)
self._import_comments(project_data, taiga_epic, epic, options)
self._import_epic_activity(project_data, taiga_epic, epic, options)
counter += 1
def _import_tasks(self, project_data, us, story):
for task in story['tasks']:
taiga_task = Task.objects.create(
subject=task['description'],
status=us.project.task_statuses.get(slug="complete" if task['complete'] else "incomplete"),
project=us.project,
us_order=task['position'],
taskboard_order=task['position'],
user_story=us
)
Task.objects.filter(id=taiga_task.id).update(
ref=task['id'],
modified_date=task['updated_at'],
created_date=task['created_at']
)
take_snapshot(taiga_task, comment="", user=None, delete=False)
def _import_attachment(self, obj, attachment_id, attachment_name, created_at, person_id, options):
users_bindings = options.get('users_bindings', {})
data = self._client.get_attachment(attachment_id)
att = Attachment(
owner=users_bindings.get(person_id, self._user),
project=obj.project,
content_type=ContentType.objects.get_for_model(obj),
object_id=obj.id,
name=attachment_name,
size=len(data),
created_date=created_at,
is_deprecated=False,
)
att.attached_file.save(attachment_name, ContentFile(data), save=True)
def _import_comments(self, project_data, obj, story, options):
users_bindings = options.get('users_bindings', {})
for comment in story['comments']:
if 'text' in comment:
snapshot = take_snapshot(
obj,
comment=comment['text'],
user=users_bindings.get(comment['person']['id'], User(full_name=comment['person']['name'])),
delete=False
)
HistoryEntry.objects.filter(id=snapshot.id).update(created_at=comment['created_at'])
for attachment in comment['file_attachments']:
self._import_attachment(
obj,
attachment['id'],
attachment['filename'],
comment['created_at'],
comment['person']['id'],
options
)
def _import_user_story_activity(self, project_data, us, story, options):
offset = 0
while True:
activities = self._client.get(
"/projects/{}/stories/{}/activity".format(
project_data['id'],
story['id'],
),
{"envelope": "true", "limit": 300, "offset": offset}
)
offset += 300
for activity in activities['data']:
self._import_activity(us, activity, options)
if len(activities['data']) < 300:
break
def _import_epic_activity(self, project_data, taiga_epic, epic, options):
offset = 0
while True:
activities = self._client.get(
"/projects/{}/epics/{}/activity".format(
project_data['id'],
epic['id'],
),
{"envelope": "true", "limit": 300, "offset": offset}
)
offset += 300
for activity in activities['data']:
self._import_activity(taiga_epic, activity, options)
if len(activities['data']) < 300:
break
def _import_activity(self, obj, activity, options):
activity_data = self._transform_activity_data(obj, activity, options)
if activity_data is None:
return
change_old = activity_data['change_old']
change_new = activity_data['change_new']
hist_type = activity_data['hist_type']
comment = activity_data['comment']
user = activity_data['user']
key = make_key_from_model_object(activity_data['obj'])
typename = get_typename_for_model_class(type(activity_data['obj']))
diff = make_diff_from_dicts(change_old, change_new)
fdiff = FrozenDiff(key, diff, {})
entry = HistoryEntry.objects.create(
user=user,
project_id=obj.project.id,
key=key,
type=hist_type,
snapshot=None,
diff=fdiff.diff,
values=make_diff_values(typename, fdiff),
comment=comment,
comment_html=mdrender(obj.project, comment),
is_hidden=False,
is_snapshot=False,
)
HistoryEntry.objects.filter(id=entry.id).update(created_at=activity['occurred_at'])
return HistoryEntry.objects.get(id=entry.id)
def _transform_activity_data(self, obj, activity, options):
users_bindings = options.get('users_bindings', {})
due_date_field = obj.project.userstorycustomattributes.get(name="Due date")
story_type_field = obj.project.userstorycustomattributes.get(name="Type")
user = {"pk": None, "name": activity.get('performed_by', {}).get('name', None)}
taiga_user = users_bindings.get(activity.get('performed_by', {}).get('id', None), None)
if taiga_user:
user = {"pk": taiga_user.id, "name": taiga_user.get_full_name()}
result = {
"change_old": {},
"change_new": {},
"hist_type": HistoryType.change,
"comment": "",
"user": user,
"obj": obj
}
if activity['kind'] == "story_create_activity":
UserStory.objects.filter(id=obj.id, created_date__gt=activity['occurred_at']).update(
created_date=activity['occurred_at'],
owner=users_bindings.get(activity["performed_by"]["id"], self._user)
)
return None
elif activity['kind'] == "epic_create_activity":
Epic.objects.filter(id=obj.id, created_date__gt=activity['occurred_at']).update(
created_date=activity['occurred_at'],
owner=users_bindings.get(activity["performed_by"]["id"], self._user)
)
return None
elif activity['kind'] in ["story_update_activity", "epic_update_activity"]:
for change in activity['changes']:
if change['change_type'] != "update" or change['kind'] not in ["story", "epic"]:
continue
if 'description' in change['new_values']:
result['change_old']["description"] = str(change['original_values']['description'])
result['change_new']["description"] = str(change['new_values']['description'])
result['change_old']["description_html"] = mdrender(obj.project, str(change['original_values']['description']))
result['change_new']["description_html"] = mdrender(obj.project, str(change['new_values']['description']))
if 'estimate' in change['new_values']:
old_points = None
if change['original_values']['estimate']:
estimation = change['original_values']['estimate']
(old_points, _) = Points.objects.get_or_create(
project=obj.project,
value=estimation,
defaults={
"name": str(estimation),
"order": estimation,
}
)
old_points = old_points.id
new_points = None
if change['new_values']['estimate']:
estimation = change['new_values']['estimate']
(new_points, _) = Points.objects.get_or_create(
project=obj.project,
value=estimation,
defaults={
"name": str(estimation),
"order": estimation,
}
)
new_points = new_points.id
result['change_old']["points"] = {obj.project.roles.get(slug="main").id: old_points}
result['change_new']["points"] = {obj.project.roles.get(slug="main").id: new_points}
if 'name' in change['new_values']:
result['change_old']["subject"] = change['original_values']['name']
result['change_new']["subject"] = change['new_values']['name']
if 'labels' in change['new_values']:
result['change_old']["tags"] = [l.lower() for l in change['original_values']['labels']]
result['change_new']["tags"] = [l.lower() for l in change['new_values']['labels']]
if 'current_state' in change['new_values']:
result['change_old']["status"] = obj.project.us_statuses.get(slug=change['original_values']['current_state']).id
result['change_new']["status"] = obj.project.us_statuses.get(slug=change['new_values']['current_state']).id
if 'story_type' in change['new_values']:
if "custom_attributes" not in result['change_old']:
result['change_old']["custom_attributes"] = []
if "custom_attributes" not in result['change_new']:
result['change_new']["custom_attributes"] = []
result['change_old']["custom_attributes"].append({
"name": "Type",
"value": change['original_values']['story_type'],
"id": story_type_field.id
})
result['change_new']["custom_attributes"].append({
"name": "Type",
"value": change['new_values']['story_type'],
"id": story_type_field.id
})
if 'deadline' in change['new_values']:
if "custom_attributes" not in result['change_old']:
result['change_old']["custom_attributes"] = []
if "custom_attributes" not in result['change_new']:
result['change_new']["custom_attributes"] = []
result['change_old']["custom_attributes"].append({
"name": "Due date",
"value": change['original_values']['deadline'],
"id": due_date_field.id
})
result['change_new']["custom_attributes"].append({
"name": "Due date",
"value": change['new_values']['deadline'],
"id": due_date_field.id
})
# TODO: Process owners_ids
elif activity['kind'] == "task_create_activity":
return None
elif activity['kind'] == "task_update_activity":
for change in activity['changes']:
if change['change_type'] != "update" or change['kind'] != "task":
continue
try:
task = Task.objects.get(project=obj.project, ref=change['id'])
if 'description' in change['new_values']:
result['change_old']["subject"] = change['original_values']['description']
result['change_new']["subject"] = change['new_values']['description']
result['obj'] = task
if 'complete' in change['new_values']:
result['change_old']["status"] = obj.project.task_statuses.get(slug="complete" if change['original_values']['complete'] else "incomplete").id
result['change_new']["status"] = obj.project.task_statuses.get(slug="complete" if change['new_values']['complete'] else "incomplete").id
result['obj'] = task
except Task.DoesNotExist:
return None
elif activity['kind'] == "comment_create_activity":
return None
elif activity['kind'] == "comment_update_activity":
return None
elif activity['kind'] == "story_move_activity":
return None
return result

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import sys
from django.utils.translation import ugettext as _
from taiga.base.mails import mail_builder
from taiga.users.models import User
from taiga.celery import app
from .importer import PivotalImporter
logger = logging.getLogger('taiga.importers.pivotal')
@app.task(bind=True)
def import_project(self, user_id, token, project_id, options):
user = User.object.get(id=user_id)
importer = PivotalImporter(user, token)
try:
project = importer.import_project(project_id, options)
except Exception as e:
# Error
ctx = {
"user": user,
"error_subject": _("Error importing pivotal project"),
"error_message": _("Error importing pivotal project"),
"project": project_id,
"exception": e
}
email = mail_builder.pivotal_import_error(admin, ctx)
email.send()
logger.error('Error importing pivotal project %s (by %s)', project_id, user, exc_info=sys.exc_info())
else:
ctx = {
"project": project,
"user": user,
}
email = mail_builder.pivotal_import_success(user, ctx)
email.send()