diff --git a/AUTHORS.rst b/AUTHORS.rst index ec62d162..5be3cfd6 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -27,6 +27,7 @@ answer newbie questions, and generally made taiga that much better: - Bruno Clermont - Chris Wilson - David Burke +- Everardo Medina - Hector Colina - Joe Letts - Julien Palard diff --git a/CHANGELOG.md b/CHANGELOG.md index f955a7d8..0b5f288a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Changelog # +## 2.1.0 Ursus Americanus (2016-05-03) + +### Features +- Add sprint name and slug on search results for user stories ((thanks to [@everblut](https://github.com/everblut))) +- [API] projects resource: Random order if `discover_mode=true` and `is_featured=true`. +- Webhooks: Improve webhook data: + - add permalinks + - owner, assigned_to, status, type, priority, severity, user_story, milestone, project are objects + - add role to 'points' object + - add the owner to every notification ('by' field) + - add the date of the notification ('date' field) + - show human diffs in 'changes' + - remove unnecessary data +- CSV Reports: + - Change field name: 'milestone' to 'sprint' + - Add new fields: 'sprint_estimated_start' and 'sprint_estimated_end' +- Importer: + - Remove project after load a dump file fails + - Add more info the the logger if load a dump file fails + +### Misc +- Lots of small and not so small bugfixes. + + ## 2.0.0 Pulsatilla Patens (2016-04-04) ### Features diff --git a/settings/local.py.example b/settings/local.py.example index e1bd9383..09e53ca4 100644 --- a/settings/local.py.example +++ b/settings/local.py.example @@ -96,3 +96,11 @@ DATABASES = { # If is True /front/sitemap.xml show a valid sitemap of taiga-front client #FRONT_SITEMAP_ENABLED = False #FRONT_SITEMAP_CACHE_TIMEOUT = 24*60*60 # In second + +# CELERY +#from .celery import * +#CELERY_ENABLED = True +# +# To use celery in memory +#CELERY_ENABLED = True +#CELERY_ALWAYS_EAGER = True diff --git a/settings/testing.py b/settings/testing.py index b1549a8a..6862a5b1 100644 --- a/settings/testing.py +++ b/settings/testing.py @@ -17,10 +17,8 @@ from .development import * -SKIP_SOUTH_TESTS = True -SOUTH_TESTS_MIGRATE = False -CELERY_ALWAYS_EAGER = True CELERY_ENABLED = False +CELERY_ALWAYS_EAGER = True MEDIA_ROOT = "/tmp" diff --git a/taiga/base/utils/json.py b/taiga/base/utils/json.py index 5cb8f6b6..a5e0a477 100644 --- a/taiga/base/utils/json.py +++ b/taiga/base/utils/json.py @@ -22,8 +22,8 @@ from taiga.base.api.utils import encoders import json -def dumps(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder): - return json.dumps(data, cls=encoder_class, indent=None, ensure_ascii=ensure_ascii) +def dumps(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder, indent=None): + return json.dumps(data, cls=encoder_class, ensure_ascii=ensure_ascii, indent=indent) def loads(data): diff --git a/taiga/export_import/api.py b/taiga/export_import/api.py index 5b570980..f84e263f 100644 --- a/taiga/export_import/api.py +++ b/taiga/export_import/api.py @@ -36,14 +36,14 @@ from taiga.projects.models import Project, Membership from taiga.projects.issues.models import Issue from taiga.projects.tasks.models import Task from taiga.projects.serializers import ProjectSerializer -from taiga.users import services as users_service +from taiga.users import services as users_services +from . import exceptions as err from . import mixins -from . import serializers -from . import service from . import permissions +from . import serializers +from . import services from . import tasks -from . import dump_service from . import throttling from .renderers import ExportRenderer @@ -72,7 +72,7 @@ class ProjectExporterViewSet(mixins.ImportThrottlingPolicyMixin, GenericViewSet) path = "exports/{}/{}-{}.json".format(project.pk, project.slug, uuid.uuid4().hex) storage_path = default_storage.path(path) with default_storage.open(storage_path, mode="w") as outfile: - service.render_project(project, outfile) + services.render_project(project, outfile) response_data = { "url": default_storage.url(path) @@ -96,7 +96,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi total_memberships = len([m for m in data.get("memberships", []) if m.get("email", None) != data["owner"]]) total_memberships = total_memberships + 1 # 1 is the owner - (enough_slots, error_message) = users_service.has_available_slot_for_import_new_project( + (enough_slots, error_message) = users_services.has_available_slot_for_import_new_project( self.request.user, is_private, total_memberships @@ -105,22 +105,22 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi raise exc.NotEnoughSlotsForProject(is_private, total_memberships, error_message) # Create Project - project_serialized = service.store_project(data) + project_serialized = services.store.store_project(data) if not project_serialized: - raise exc.BadRequest(service.get_errors()) + raise exc.BadRequest(services.store.get_errors()) # Create roles roles_serialized = None if "roles" in data: - roles_serialized = service.store_roles(project_serialized.object, data) + roles_serialized = services.store.store_roles(project_serialized.object, data) if not roles_serialized: raise exc.BadRequest(_("We needed at least one role")) # Create memberships if "memberships" in data: - service.store_memberships(project_serialized.object, data) + services.store.store_memberships(project_serialized.object, data) try: owner_membership = project_serialized.object.memberships.get(user=project_serialized.object.owner) @@ -137,57 +137,57 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi # Create project values choicess if "points" in data: - service.store_choices(project_serialized.object, data, - "points", serializers.PointsExportSerializer) + services.store.store_project_attributes_values(project_serialized.object, data, + "points", serializers.PointsExportSerializer) if "issue_types" in data: - service.store_choices(project_serialized.object, data, - "issue_types", - serializers.IssueTypeExportSerializer) + services.store.store_project_attributes_values(project_serialized.object, data, + "issue_types", + serializers.IssueTypeExportSerializer) if "issue_statuses" in data: - service.store_choices(project_serialized.object, data, - "issue_statuses", - serializers.IssueStatusExportSerializer,) + services.store.store_project_attributes_values(project_serialized.object, data, + "issue_statuses", + serializers.IssueStatusExportSerializer,) if "us_statuses" in data: - service.store_choices(project_serialized.object, data, - "us_statuses", - serializers.UserStoryStatusExportSerializer,) + services.store.store_project_attributes_values(project_serialized.object, data, + "us_statuses", + serializers.UserStoryStatusExportSerializer,) if "task_statuses" in data: - service.store_choices(project_serialized.object, data, - "task_statuses", - serializers.TaskStatusExportSerializer) + services.store.store_project_attributes_values(project_serialized.object, data, + "task_statuses", + serializers.TaskStatusExportSerializer) if "priorities" in data: - service.store_choices(project_serialized.object, data, - "priorities", - serializers.PriorityExportSerializer) + services.store.store_project_attributes_values(project_serialized.object, data, + "priorities", + serializers.PriorityExportSerializer) if "severities" in data: - service.store_choices(project_serialized.object, data, - "severities", - serializers.SeverityExportSerializer) + services.store.store_project_attributes_values(project_serialized.object, data, + "severities", + serializers.SeverityExportSerializer) if ("points" in data or "issues_types" in data or "issues_statuses" in data or "us_statuses" in data or "task_statuses" in data or "priorities" in data or "severities" in data): - service.store_default_choices(project_serialized.object, data) + services.store.store_default_project_attributes_values(project_serialized.object, data) # Created custom attributes if "userstorycustomattributes" in data: - service.store_custom_attributes(project_serialized.object, data, - "userstorycustomattributes", - serializers.UserStoryCustomAttributeExportSerializer) + services.store.store_custom_attributes(project_serialized.object, data, + "userstorycustomattributes", + serializers.UserStoryCustomAttributeExportSerializer) if "taskcustomattributes" in data: - service.store_custom_attributes(project_serialized.object, data, - "taskcustomattributes", - serializers.TaskCustomAttributeExportSerializer) + services.store.store_custom_attributes(project_serialized.object, data, + "taskcustomattributes", + serializers.TaskCustomAttributeExportSerializer) if "issuecustomattributes" in data: - service.store_custom_attributes(project_serialized.object, data, - "issuecustomattributes", - serializers.IssueCustomAttributeExportSerializer) + services.store.store_custom_attributes(project_serialized.object, data, + "issuecustomattributes", + serializers.IssueCustomAttributeExportSerializer) # Is there any error? - errors = service.get_errors() + errors = services.store.get_errors() if errors: raise exc.BadRequest(errors) @@ -199,21 +199,33 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi @detail_route(methods=['post']) @method_decorator(atomic) - def issue(self, request, *args, **kwargs): + def milestone(self, request, *args, **kwargs): project = self.get_object_or_none() self.check_permissions(request, 'import_item', project) - signals.pre_save.disconnect(sender=Issue, - dispatch_uid="set_finished_date_when_edit_issue") + milestone = services.store.store_milestone(project, request.DATA.copy()) - issue = service.store_issue(project, request.DATA.copy()) - - errors = service.get_errors() + errors = services.store.get_errors() if errors: raise exc.BadRequest(errors) - headers = self.get_success_headers(issue.data) - return response.Created(issue.data, headers=headers) + headers = self.get_success_headers(milestone.data) + return response.Created(milestone.data, headers=headers) + + @detail_route(methods=['post']) + @method_decorator(atomic) + def us(self, request, *args, **kwargs): + project = self.get_object_or_none() + self.check_permissions(request, 'import_item', project) + + us = services.store.store_user_story(project, request.DATA.copy()) + + errors = services.store.get_errors() + if errors: + raise exc.BadRequest(errors) + + headers = self.get_success_headers(us.data) + return response.Created(us.data, headers=headers) @detail_route(methods=['post']) @method_decorator(atomic) @@ -224,9 +236,9 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi signals.pre_save.disconnect(sender=Task, dispatch_uid="set_finished_date_when_edit_task") - task = service.store_task(project, request.DATA.copy()) + task = services.store.store_task(project, request.DATA.copy()) - errors = service.get_errors() + errors = services.store.get_errors() if errors: raise exc.BadRequest(errors) @@ -235,33 +247,21 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi @detail_route(methods=['post']) @method_decorator(atomic) - def us(self, request, *args, **kwargs): + def issue(self, request, *args, **kwargs): project = self.get_object_or_none() self.check_permissions(request, 'import_item', project) - us = service.store_user_story(project, request.DATA.copy()) + signals.pre_save.disconnect(sender=Issue, + dispatch_uid="set_finished_date_when_edit_issue") - errors = service.get_errors() + issue = services.store.store_issue(project, request.DATA.copy()) + + errors = services.store.get_errors() if errors: raise exc.BadRequest(errors) - headers = self.get_success_headers(us.data) - return response.Created(us.data, headers=headers) - - @detail_route(methods=['post']) - @method_decorator(atomic) - def milestone(self, request, *args, **kwargs): - project = self.get_object_or_none() - self.check_permissions(request, 'import_item', project) - - milestone = service.store_milestone(project, request.DATA.copy()) - - errors = service.get_errors() - if errors: - raise exc.BadRequest(errors) - - headers = self.get_success_headers(milestone.data) - return response.Created(milestone.data, headers=headers) + headers = self.get_success_headers(issue.data) + return response.Created(issue.data, headers=headers) @detail_route(methods=['post']) @method_decorator(atomic) @@ -269,9 +269,9 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi project = self.get_object_or_none() self.check_permissions(request, 'import_item', project) - wiki_page = service.store_wiki_page(project, request.DATA.copy()) + wiki_page = services.store.store_wiki_page(project, request.DATA.copy()) - errors = service.get_errors() + errors = services.store.get_errors() if errors: raise exc.BadRequest(errors) @@ -284,9 +284,9 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi project = self.get_object_or_none() self.check_permissions(request, 'import_item', project) - wiki_link = service.store_wiki_link(project, request.DATA.copy()) + wiki_link = services.store.store_wiki_link(project, request.DATA.copy()) - errors = service.get_errors() + errors = services.store.get_errors() if errors: raise exc.BadRequest(errors) @@ -327,7 +327,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi total_memberships = len([m for m in dump.get("memberships", []) if m.get("email", None) != dump["owner"]]) total_memberships = total_memberships + 1 # 1 is the owner - (enough_slots, error_message) = users_service.has_available_slot_for_import_new_project( + (enough_slots, error_message) = users_services.has_available_slot_for_import_new_project( user, is_private, total_memberships @@ -335,11 +335,23 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi if not enough_slots: raise exc.NotEnoughSlotsForProject(is_private, total_memberships, error_message) + # Async mode if settings.CELERY_ENABLED: task = tasks.load_project_dump.delay(user, dump) return response.Accepted({"import_id": task.id}) - project = dump_service.dict_to_project(dump, request.user) - response_data = ProjectSerializer(project).data - return response.Created(response_data) + # Sync mode + try: + project = services.store_project_from_dict(dump, request.user) + except err.TaigaImportError as e: + # On Error + ## remove project + if e.project: + e.project.delete_related_content() + e.project.delete() + return response.BadRequest({"error": e.message, "details": e.errors}) + else: + # On Success + response_data = ProjectSerializer(project).data + return response.Created(response_data) diff --git a/taiga/export_import/dump_service.py b/taiga/export_import/dump_service.py deleted file mode 100644 index 243b9167..00000000 --- a/taiga/export_import/dump_service.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (C) 2014-2016 Andrey Antukh -# Copyright (C) 2014-2016 Jesús Espino -# Copyright (C) 2014-2016 David Barragán -# Copyright (C) 2014-2016 Alejandro Alonso -# 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 . - -from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _ - -from taiga.projects.models import Membership, Project -from taiga.users import services as users_service - -from . import serializers -from . import service - - -class TaigaImportError(Exception): - def __init__(self, message): - self.message = message - - -def store_milestones(project, data): - results = [] - for milestone_data in data.get("milestones", []): - milestone = service.store_milestone(project, milestone_data) - results.append(milestone) - return results - - -def store_tasks(project, data): - results = [] - for task in data.get("tasks", []): - task = service.store_task(project, task) - results.append(task) - return results - - -def store_wiki_pages(project, data): - results = [] - for wiki_page in data.get("wiki_pages", []): - results.append(service.store_wiki_page(project, wiki_page)) - return results - - -def store_wiki_links(project, data): - results = [] - for wiki_link in data.get("wiki_links", []): - results.append(service.store_wiki_link(project, wiki_link)) - return results - - -def store_user_stories(project, data): - results = [] - for userstory in data.get("user_stories", []): - us = service.store_user_story(project, userstory) - results.append(us) - return results - - -def store_timeline_entries(project, data): - results = [] - for timeline in data.get("timeline", []): - tl = service.store_timeline_entry(project, timeline) - results.append(tl) - return results - - -def store_issues(project, data): - issues = [] - for issue in data.get("issues", []): - issues.append(service.store_issue(project, issue)) - return issues - - -def store_tags_colors(project, data): - project.tags_colors = data.get("tags_colors", []) - project.save() - return None - - -def dict_to_project(data, owner=None): - if owner: - data["owner"] = owner.email - - # Validate if the owner can have this project - is_private = data.get("is_private", False) - total_memberships = len([m for m in data.get("memberships", []) - if m.get("email", None) != data["owner"]]) - total_memberships = total_memberships + 1 # 1 is the owner - (enough_slots, error_message) = users_service.has_available_slot_for_import_new_project( - owner, - is_private, - total_memberships - ) - if not enough_slots: - raise TaigaImportError(error_message) - - project_serialized = service.store_project(data) - - if not project_serialized: - raise TaigaImportError(_("error importing project data")) - - proj = project_serialized.object - - service.store_choices(proj, data, "points", serializers.PointsExportSerializer) - service.store_choices(proj, data, "issue_types", serializers.IssueTypeExportSerializer) - service.store_choices(proj, data, "issue_statuses", serializers.IssueStatusExportSerializer) - service.store_choices(proj, data, "us_statuses", serializers.UserStoryStatusExportSerializer) - service.store_choices(proj, data, "task_statuses", serializers.TaskStatusExportSerializer) - service.store_choices(proj, data, "priorities", serializers.PriorityExportSerializer) - service.store_choices(proj, data, "severities", serializers.SeverityExportSerializer) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing lists of project attributes")) - - service.store_default_choices(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing default project attributes values")) - - service.store_custom_attributes(proj, data, "userstorycustomattributes", - serializers.UserStoryCustomAttributeExportSerializer) - service.store_custom_attributes(proj, data, "taskcustomattributes", - serializers.TaskCustomAttributeExportSerializer) - service.store_custom_attributes(proj, data, "issuecustomattributes", - serializers.IssueCustomAttributeExportSerializer) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing custom attributes")) - - service.store_roles(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing roles")) - - service.store_memberships(proj, data) - - if proj.memberships.filter(user=proj.owner).count() == 0: - if proj.roles.all().count() > 0: - Membership.objects.create( - project=proj, - email=proj.owner.email, - user=proj.owner, - role=proj.roles.all().first(), - is_admin=True - ) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing memberships")) - - store_milestones(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing sprints")) - - store_wiki_pages(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing wiki pages")) - - store_wiki_links(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing wiki links")) - - store_issues(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing issues")) - - store_user_stories(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing user stories")) - - store_tasks(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing tasks")) - - store_tags_colors(proj, data) - - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing tags")) - - store_timeline_entries(proj, data) - if service.get_errors(clear=False): - raise TaigaImportError(_("error importing timelines")) - - proj.refresh_totals() - return proj diff --git a/taiga/export_import/exceptions.py b/taiga/export_import/exceptions.py new file mode 100644 index 00000000..623d5b24 --- /dev/null +++ b/taiga/export_import/exceptions.py @@ -0,0 +1,23 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# 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 . + + +class TaigaImportError(Exception): + def __init__(self, message, project, errors=[]): + self.message = message + self.project = project + self.errors = errors diff --git a/taiga/export_import/management/commands/dump_project.py b/taiga/export_import/management/commands/dump_project.py index daf6f6d5..d1248ad4 100644 --- a/taiga/export_import/management/commands/dump_project.py +++ b/taiga/export_import/management/commands/dump_project.py @@ -18,25 +18,45 @@ from django.core.management.base import BaseCommand, CommandError from taiga.projects.models import Project -from taiga.export_import.renderers import ExportRenderer -from taiga.export_import.service import render_project +from taiga.export_import.services import render_project - -import resource +import os class Command(BaseCommand): - args = '' - help = 'Export a project to json' - renderer_context = {"indent": 4} - renderer = ExportRenderer() + help = "Export projects to json" + + def add_arguments(self, parser): + parser.add_argument("project_slugs", + nargs="+", + help="") + + parser.add_argument("-d", "--dst_dir", + action="store", + dest="dst_dir", + default="./", + metavar="DIR", + help="Directory to save the json files. ('./' by default)") def handle(self, *args, **options): - for project_slug in args: + dst_dir = options["dst_dir"] + + if not os.path.exists(dst_dir): + raise CommandError("Directory {} does not exist.".format(dst_dir)) + + if not os.path.isdir(dst_dir): + raise CommandError("'{}' must be a directory, not a file.".format(dst_dir)) + + project_slugs = options["project_slugs"] + + for project_slug in project_slugs: try: project = Project.objects.get(slug=project_slug) except Project.DoesNotExist: - raise CommandError('Project "%s" does not exist' % project_slug) + raise CommandError("Project '{}' does not exist".format(project_slug)) - with open('%s.json'%(project_slug), 'w') as outfile: - render_project(project, outfile) + dst_file = os.path.join(dst_dir, "{}.json".format(project_slug)) + with open(src_file, "w") as f: + render_project(project, f) + + print("-> Generate dump of project '{}' in '{}'".format(project.name, src_file)) diff --git a/taiga/export_import/management/commands/load_dump.py b/taiga/export_import/management/commands/load_dump.py index 367a2401..a1d919f0 100644 --- a/taiga/export_import/management/commands/load_dump.py +++ b/taiga/export_import/management/commands/load_dump.py @@ -21,10 +21,10 @@ from django.db.models import signals from optparse import make_option from taiga.base.utils import json -from taiga.projects.models import Project +from taiga.export_import.import services +from taiga.export_import.exceptions as err from taiga.export_import.renderers import ExportRenderer -from taiga.export_import.dump_service import dict_to_project, TaigaImportError -from taiga.export_import.service import get_errors +from taiga.projects.models import Project from taiga.users.models import User @@ -61,8 +61,12 @@ class Command(BaseCommand): signals.post_delete.receivers = receivers_back user = User.objects.get(email=args[1]) - dict_to_project(data, user) - except TaigaImportError as e: + services.store_project_from_dict(data, user) + except err.TaigaImportError as e: + if e.project: + e.project.delete_related_content() + e.project.delete() + print("ERROR:", end=" ") print(e.message) - print(get_errors()) + print(services.store.get_errors()) diff --git a/taiga/export_import/services/__init__.py b/taiga/export_import/services/__init__.py new file mode 100644 index 00000000..8aad0f08 --- /dev/null +++ b/taiga/export_import/services/__init__.py @@ -0,0 +1,26 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# 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 . + +# This makes all code that import services works and +# is not the baddest practice ;) + +from .render import render_project +from . import render + +from .store import store_project_from_dict +from . import store + diff --git a/taiga/export_import/services/render.py b/taiga/export_import/services/render.py new file mode 100644 index 00000000..b9905baf --- /dev/null +++ b/taiga/export_import/services/render.py @@ -0,0 +1,124 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# 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 . + +# This makes all code that import services works and +# is not the baddest practice ;) + +import base64 +import gc +import os + +from django.core.files.storage import default_storage + +from taiga.base.utils import json +from taiga.timeline.service import get_project_timeline +from taiga.base.api.fields import get_component + +from .. import serializers + + +def render_project(project, outfile, chunk_size = 8190): + serializer = serializers.ProjectExportSerializer(project) + outfile.write('{\n') + + first_field = True + for field_name in serializer.fields.keys(): + # Avoid writing "," in the last element + if not first_field: + outfile.write(",\n") + else: + first_field = False + + field = serializer.fields.get(field_name) + field.initialize(parent=serializer, field_name=field_name) + + # These four "special" fields hava attachments so we use them in a special way + if field_name in ["wiki_pages", "user_stories", "tasks", "issues"]: + value = get_component(project, field_name) + outfile.write('"{}": [\n'.format(field_name)) + + attachments_field = field.fields.pop("attachments", None) + if attachments_field: + attachments_field.initialize(parent=field, field_name="attachments") + + first_item = True + for item in value.iterator(): + # Avoid writing "," in the last element + if not first_item: + outfile.write(",\n") + else: + first_item = False + + + dumped_value = json.dumps(field.to_native(item)) + writing_value = dumped_value[:-1]+ ',\n "attachments": [\n' + outfile.write(writing_value) + + first_attachment = True + for attachment in item.attachments.iterator(): + # Avoid writing "," in the last element + if not first_attachment: + outfile.write(",\n") + else: + first_attachment = False + + # Write all the data expect the serialized file + attachment_serializer = serializers.AttachmentExportSerializer(instance=attachment) + attached_file_serializer = attachment_serializer.fields.pop("attached_file") + dumped_value = json.dumps(attachment_serializer.data) + dumped_value = dumped_value[:-1] + ',\n "attached_file":{\n "data":"' + outfile.write(dumped_value) + + # We write the attached_files by chunks so the memory used is not increased + attachment_file = attachment.attached_file + if default_storage.exists(attachment_file.name): + with default_storage.open(attachment_file.name) as f: + while True: + bin_data = f.read(chunk_size) + if not bin_data: + break + + b64_data = base64.b64encode(bin_data).decode('utf-8') + outfile.write(b64_data) + + outfile.write('", \n "name":"{}"}}\n}}'.format( + os.path.basename(attachment_file.name))) + + outfile.write(']}') + outfile.flush() + gc.collect() + outfile.write(']') + + else: + value = field.field_to_native(project, field_name) + outfile.write('"{}": {}'.format(field_name, json.dumps(value))) + + # Generate the timeline + outfile.write(',\n"timeline": [\n') + first_timeline = True + for timeline_item in get_project_timeline(project).iterator(): + # Avoid writing "," in the last element + if not first_timeline: + outfile.write(",\n") + else: + first_timeline = False + + dumped_value = json.dumps(serializers.TimelineExportSerializer(timeline_item).data) + outfile.write(dumped_value) + + outfile.write(']}\n') + diff --git a/taiga/export_import/service.py b/taiga/export_import/services/store.py similarity index 63% rename from taiga/export_import/service.py rename to taiga/export_import/services/store.py index 14ecd22d..e286c97c 100644 --- a/taiga/export_import/service.py +++ b/taiga/export_import/services/store.py @@ -15,30 +15,35 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import base64 -import gc -import resource +# This makes all code that import services works and +# is not the baddest practice ;) + import os -import os.path as path import uuid from unidecode import unidecode -from django.template.defaultfilters import slugify from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist -from django.core.files.storage import default_storage +from django.template.defaultfilters import slugify +from django.utils.translation import ugettext as _ -from taiga.base.utils import json from taiga.projects.history.services import make_key_from_model_object, take_snapshot -from taiga.timeline.service import build_project_namespace, get_project_timeline +from taiga.projects.models import Membership from taiga.projects.references import sequences as seq from taiga.projects.references import models as refs from taiga.projects.userstories.models import RolePoints from taiga.projects.services import find_invited_user -from taiga.base.api.fields import get_component +from taiga.timeline.service import build_project_namespace +from taiga.users import services as users_service -from . import serializers +from .. import exceptions as err +from .. import serializers + + +######################################################################## +## Manage errors +######################################################################## _errors_log = {} @@ -57,97 +62,16 @@ def add_errors(section, errors): _errors_log[section] = [errors] -def render_project(project, outfile, chunk_size = 8190): - serializer = serializers.ProjectExportSerializer(project) - outfile.write('{\n') - - first_field = True - for field_name in serializer.fields.keys(): - # Avoid writing "," in the last element - if not first_field: - outfile.write(",\n") - else: - first_field = False - - field = serializer.fields.get(field_name) - field.initialize(parent=serializer, field_name=field_name) - - # These four "special" fields hava attachments so we use them in a special way - if field_name in ["wiki_pages", "user_stories", "tasks", "issues"]: - value = get_component(project, field_name) - outfile.write('"{}": [\n'.format(field_name)) - - attachments_field = field.fields.pop("attachments", None) - if attachments_field: - attachments_field.initialize(parent=field, field_name="attachments") - - first_item = True - for item in value.iterator(): - # Avoid writing "," in the last element - if not first_item: - outfile.write(",\n") - else: - first_item = False +def reset_errors(): + _errors_log.clear() - dumped_value = json.dumps(field.to_native(item)) - writing_value = dumped_value[:-1]+ ',\n "attachments": [\n' - outfile.write(writing_value) +######################################################################## +## Store functions +######################################################################## - first_attachment = True - for attachment in item.attachments.iterator(): - # Avoid writing "," in the last element - if not first_attachment: - outfile.write(",\n") - else: - first_attachment = False - - # Write all the data expect the serialized file - attachment_serializer = serializers.AttachmentExportSerializer(instance=attachment) - attached_file_serializer = attachment_serializer.fields.pop("attached_file") - dumped_value = json.dumps(attachment_serializer.data) - dumped_value = dumped_value[:-1] + ',\n "attached_file":{\n "data":"' - outfile.write(dumped_value) - - # We write the attached_files by chunks so the memory used is not increased - attachment_file = attachment.attached_file - if default_storage.exists(attachment_file.name): - with default_storage.open(attachment_file.name) as f: - while True: - bin_data = f.read(chunk_size) - if not bin_data: - break - - b64_data = base64.b64encode(bin_data).decode('utf-8') - outfile.write(b64_data) - - outfile.write('", \n "name":"{}"}}\n}}'.format( - os.path.basename(attachment_file.name))) - - outfile.write(']}') - outfile.flush() - gc.collect() - outfile.write(']') - - else: - value = field.field_to_native(project, field_name) - outfile.write('"{}": {}'.format(field_name, json.dumps(value))) - - # Generate the timeline - outfile.write(',\n"timeline": [\n') - first_timeline = True - for timeline_item in get_project_timeline(project).iterator(): - # Avoid writing "," in the last element - if not first_timeline: - outfile.write(",\n") - else: - first_timeline = False - - dumped_value = json.dumps(serializers.TimelineExportSerializer(timeline_item).data) - outfile.write(dumped_value) - - outfile.write(']}\n') +## PROJECT def store_project(data): project_data = {} @@ -175,43 +99,19 @@ def store_project(data): return None -def _store_choice(project, data, field, serializer): - serialized = serializer(data=data) - if serialized.is_valid(): - serialized.object.project = project - serialized.object._importing = True - serialized.save() - return serialized.object - add_errors(field, serialized.errors) - return None +## MISC + +def _use_id_instead_name_as_key_in_custom_attributes_values(custom_attributes, values): + ret = {} + for attr in custom_attributes: + value = values.get(attr["name"], None) + if value is not None: + ret[str(attr["id"])] = value + + return ret -def store_choices(project, data, field, serializer): - result = [] - for choice_data in data.get(field, []): - result.append(_store_choice(project, choice_data, field, serializer)) - return result - - -def _store_custom_attribute(project, data, field, serializer): - serialized = serializer(data=data) - if serialized.is_valid(): - serialized.object.project = project - serialized.object._importing = True - serialized.save() - return serialized.object - add_errors(field, serialized.errors) - return None - - -def store_custom_attributes(project, data, field, serializer): - result = [] - for custom_attribute_data in data.get(field, []): - result.append(_store_custom_attribute(project, custom_attribute_data, field, serializer)) - return result - - -def store_custom_attributes_values(obj, data_values, obj_field, serializer_class): +def _store_custom_attributes_values(obj, data_values, obj_field, serializer_class): data = { obj_field: obj.id, "attributes_values": data_values, @@ -231,17 +131,39 @@ def store_custom_attributes_values(obj, data_values, obj_field, serializer_class return None -def _use_id_instead_name_as_key_in_custom_attributes_values(custom_attributes, values): - ret = {} - for attr in custom_attributes: - value = values.get(attr["name"], None) - if value is not None: - ret[str(attr["id"])] = value - - return ret +def _store_attachment(project, obj, attachment): + serialized = serializers.AttachmentExportSerializer(data=attachment) + if serialized.is_valid(): + serialized.object.content_type = ContentType.objects.get_for_model(obj.__class__) + serialized.object.object_id = obj.id + serialized.object.project = project + if serialized.object.owner is None: + serialized.object.owner = serialized.object.project.owner + serialized.object._importing = True + serialized.object.size = serialized.object.attached_file.size + serialized.object.name = os.path.basename(serialized.object.attached_file.name) + serialized.save() + return serialized + add_errors("attachments", serialized.errors) + return serialized -def store_role(project, role): +def _store_history(project, obj, history): + serialized = serializers.HistoryExportSerializer(data=history, context={"project": project}) + if serialized.is_valid(): + serialized.object.key = make_key_from_model_object(obj) + if serialized.object.diff is None: + serialized.object.diff = [] + serialized.object._importing = True + serialized.save() + return serialized + add_errors("history", serialized.errors) + return serialized + + +## ROLES + +def _store_role(project, role): serialized = serializers.RoleExportSerializer(data=role) if serialized.is_valid(): serialized.object.project = project @@ -255,14 +177,60 @@ def store_role(project, role): def store_roles(project, data): results = [] for role in data.get("roles", []): - serialized = store_role(project, role) + serialized = _store_role(project, role) if serialized: results.append(serialized) return results -def store_default_choices(project, data): +## MEMGERSHIPS + +def _store_membership(project, membership): + serialized = serializers.MembershipExportSerializer(data=membership, context={"project": project}) + if serialized.is_valid(): + serialized.object.project = project + serialized.object._importing = True + serialized.object.token = str(uuid.uuid1()) + serialized.object.user = find_invited_user(serialized.object.email, + default=serialized.object.user) + serialized.save() + return serialized + + add_errors("memberships", serialized.errors) + return None + + +def store_memberships(project, data): + results = [] + for membership in data.get("memberships", []): + results.append(_store_membership(project, membership)) + return results + + +## PROJECT ATTRIBUTES + +def _store_project_attribute_value(project, data, field, serializer): + serialized = serializer(data=data) + if serialized.is_valid(): + serialized.object.project = project + serialized.object._importing = True + serialized.save() + return serialized.object + add_errors(field, serialized.errors) + return None + + +def store_project_attributes_values(project, data, field, serializer): + result = [] + for choice_data in data.get(field, []): + result.append(_store_project_attribute_value(project, choice_data, field, serializer)) + return result + + +## DEFAULT PROJECT ATTRIBUTES VALUES + +def store_default_project_attributes_values(project, data): def helper(project, field, related, data): if field in data: value = related.all().get(name=data[field]) @@ -281,75 +249,27 @@ def store_default_choices(project, data): project.save() -def store_membership(project, membership): - serialized = serializers.MembershipExportSerializer(data=membership, context={"project": project}) +## CUSTOM ATTRIBUTES + +def _store_custom_attribute(project, data, field, serializer): + serialized = serializer(data=data) if serialized.is_valid(): serialized.object.project = project serialized.object._importing = True - serialized.object.token = str(uuid.uuid1()) - serialized.object.user = find_invited_user(serialized.object.email, - default=serialized.object.user) serialized.save() - return serialized - - add_errors("memberships", serialized.errors) + return serialized.object + add_errors(field, serialized.errors) return None -def store_memberships(project, data): - results = [] - for membership in data.get("memberships", []): - results.append(store_membership(project, membership)) - return results +def store_custom_attributes(project, data, field, serializer): + result = [] + for custom_attribute_data in data.get(field, []): + result.append(_store_custom_attribute(project, custom_attribute_data, field, serializer)) + return result -def store_task(project, data): - if "status" not in data and project.default_task_status: - data["status"] = project.default_task_status.name - - serialized = serializers.TaskExportSerializer(data=data, context={"project": project}) - if serialized.is_valid(): - serialized.object.project = project - if serialized.object.owner is None: - serialized.object.owner = serialized.object.project.owner - serialized.object._importing = True - serialized.object._not_notify = True - - serialized.save() - serialized.save_watchers() - - if serialized.object.ref: - sequence_name = refs.make_sequence_name(project) - if not seq.exists(sequence_name): - seq.create(sequence_name) - seq.set_max(sequence_name, serialized.object.ref) - else: - serialized.object.ref, _ = refs.make_reference(serialized.object, project) - serialized.object.save() - - for task_attachment in data.get("attachments", []): - store_attachment(project, serialized.object, task_attachment) - - history_entries = data.get("history", []) - for history in history_entries: - store_history(project, serialized.object, history) - - if not history_entries: - take_snapshot(serialized.object, user=serialized.object.owner) - - custom_attributes_values = data.get("custom_attributes_values", None) - if custom_attributes_values: - custom_attributes = serialized.object.project.taskcustomattributes.all().values('id', 'name') - custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values( - custom_attributes, custom_attributes_values) - store_custom_attributes_values(serialized.object, custom_attributes_values, - "task", serializers.TaskCustomAttributesValuesExportSerializer) - - return serialized - - add_errors("tasks", serialized.errors) - return None - +## MILESTONE def store_milestone(project, milestone): serialized = serializers.MilestoneExportSerializer(data=milestone, project=project) @@ -368,90 +288,17 @@ def store_milestone(project, milestone): return None -def store_attachment(project, obj, attachment): - serialized = serializers.AttachmentExportSerializer(data=attachment) - if serialized.is_valid(): - serialized.object.content_type = ContentType.objects.get_for_model(obj.__class__) - serialized.object.object_id = obj.id - serialized.object.project = project - if serialized.object.owner is None: - serialized.object.owner = serialized.object.project.owner - serialized.object._importing = True - serialized.object.size = serialized.object.attached_file.size - serialized.object.name = path.basename(serialized.object.attached_file.name) - serialized.save() - return serialized - add_errors("attachments", serialized.errors) - return serialized +def store_milestones(project, data): + results = [] + for milestone_data in data.get("milestones", []): + milestone = store_milestone(project, milestone_data) + results.append(milestone) + return results -def store_timeline_entry(project, timeline): - serialized = serializers.TimelineExportSerializer(data=timeline, context={"project": project}) - if serialized.is_valid(): - serialized.object.project = project - serialized.object.namespace = build_project_namespace(project) - serialized.object.object_id = project.id - serialized.object._importing = True - serialized.save() - return serialized - add_errors("timeline", serialized.errors) - return serialized +## USER STORIES - -def store_history(project, obj, history): - serialized = serializers.HistoryExportSerializer(data=history, context={"project": project}) - if serialized.is_valid(): - serialized.object.key = make_key_from_model_object(obj) - if serialized.object.diff is None: - serialized.object.diff = [] - serialized.object._importing = True - serialized.save() - return serialized - add_errors("history", serialized.errors) - return serialized - - -def store_wiki_page(project, wiki_page): - wiki_page["slug"] = slugify(unidecode(wiki_page.get("slug", ""))) - serialized = serializers.WikiPageExportSerializer(data=wiki_page) - if serialized.is_valid(): - serialized.object.project = project - if serialized.object.owner is None: - serialized.object.owner = serialized.object.project.owner - serialized.object._importing = True - serialized.object._not_notify = True - serialized.save() - serialized.save_watchers() - - for attachment in wiki_page.get("attachments", []): - store_attachment(project, serialized.object, attachment) - - history_entries = wiki_page.get("history", []) - for history in history_entries: - store_history(project, serialized.object, history) - - if not history_entries: - take_snapshot(serialized.object, user=serialized.object.owner) - - return serialized - - add_errors("wiki_pages", serialized.errors) - return None - - -def store_wiki_link(project, wiki_link): - serialized = serializers.WikiLinkExportSerializer(data=wiki_link) - if serialized.is_valid(): - serialized.object.project = project - serialized.object._importing = True - serialized.save() - return serialized - - add_errors("wiki_links", serialized.errors) - return None - - -def store_role_point(project, us, role_point): +def _store_role_point(project, us, role_point): serialized = serializers.RolePointsExportSerializer(data=role_point, context={"project": project}) if serialized.is_valid(): try: @@ -468,7 +315,6 @@ def store_role_point(project, us, role_point): add_errors("role_points", serialized.errors) return None - def store_user_story(project, data): if "status" not in data and project.default_us_status: data["status"] = project.default_us_status.name @@ -497,14 +343,14 @@ def store_user_story(project, data): serialized.object.save() for us_attachment in data.get("attachments", []): - store_attachment(project, serialized.object, us_attachment) + _store_attachment(project, serialized.object, us_attachment) for role_point in data.get("role_points", []): - store_role_point(project, serialized.object, role_point) + _store_role_point(project, serialized.object, role_point) history_entries = data.get("history", []) for history in history_entries: - store_history(project, serialized.object, history) + _store_history(project, serialized.object, history) if not history_entries: take_snapshot(serialized.object, user=serialized.object.owner) @@ -514,7 +360,7 @@ def store_user_story(project, data): custom_attributes = serialized.object.project.userstorycustomattributes.all().values('id', 'name') custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values( custom_attributes, custom_attributes_values) - store_custom_attributes_values(serialized.object, custom_attributes_values, + _store_custom_attributes_values(serialized.object, custom_attributes_values, "user_story", serializers.UserStoryCustomAttributesValuesExportSerializer) return serialized @@ -523,6 +369,74 @@ def store_user_story(project, data): return None +def store_user_stories(project, data): + results = [] + for userstory in data.get("user_stories", []): + us = store_user_story(project, userstory) + results.append(us) + return results + + +## TASKS + +def store_task(project, data): + if "status" not in data and project.default_task_status: + data["status"] = project.default_task_status.name + + serialized = serializers.TaskExportSerializer(data=data, context={"project": project}) + if serialized.is_valid(): + serialized.object.project = project + if serialized.object.owner is None: + serialized.object.owner = serialized.object.project.owner + serialized.object._importing = True + serialized.object._not_notify = True + + serialized.save() + serialized.save_watchers() + + if serialized.object.ref: + sequence_name = refs.make_sequence_name(project) + if not seq.exists(sequence_name): + seq.create(sequence_name) + seq.set_max(sequence_name, serialized.object.ref) + else: + serialized.object.ref, _ = refs.make_reference(serialized.object, project) + serialized.object.save() + + for task_attachment in data.get("attachments", []): + _store_attachment(project, serialized.object, task_attachment) + + history_entries = data.get("history", []) + for history in history_entries: + _store_history(project, serialized.object, history) + + if not history_entries: + take_snapshot(serialized.object, user=serialized.object.owner) + + custom_attributes_values = data.get("custom_attributes_values", None) + if custom_attributes_values: + custom_attributes = serialized.object.project.taskcustomattributes.all().values('id', 'name') + custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values( + custom_attributes, custom_attributes_values) + _store_custom_attributes_values(serialized.object, custom_attributes_values, + "task", serializers.TaskCustomAttributesValuesExportSerializer) + + return serialized + + add_errors("tasks", serialized.errors) + return None + + +def store_tasks(project, data): + results = [] + for task in data.get("tasks", []): + task = store_task(project, task) + results.append(task) + return results + + +## ISSUES + def store_issue(project, data): serialized = serializers.IssueExportSerializer(data=data, context={"project": project}) @@ -558,11 +472,11 @@ def store_issue(project, data): serialized.object.save() for attachment in data.get("attachments", []): - store_attachment(project, serialized.object, attachment) + _store_attachment(project, serialized.object, attachment) history_entries = data.get("history", []) for history in history_entries: - store_history(project, serialized.object, history) + _store_history(project, serialized.object, history) if not history_entries: take_snapshot(serialized.object, user=serialized.object.owner) @@ -572,10 +486,248 @@ def store_issue(project, data): custom_attributes = serialized.object.project.issuecustomattributes.all().values('id', 'name') custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values( custom_attributes, custom_attributes_values) - store_custom_attributes_values(serialized.object, custom_attributes_values, + _store_custom_attributes_values(serialized.object, custom_attributes_values, "issue", serializers.IssueCustomAttributesValuesExportSerializer) return serialized add_errors("issues", serialized.errors) return None + + +def store_issues(project, data): + issues = [] + for issue in data.get("issues", []): + issues.append(store_issue(project, issue)) + return issues + + +## WIKI PAGES + +def store_wiki_page(project, wiki_page): + wiki_page["slug"] = slugify(unidecode(wiki_page.get("slug", ""))) + serialized = serializers.WikiPageExportSerializer(data=wiki_page) + if serialized.is_valid(): + serialized.object.project = project + if serialized.object.owner is None: + serialized.object.owner = serialized.object.project.owner + serialized.object._importing = True + serialized.object._not_notify = True + serialized.save() + serialized.save_watchers() + + for attachment in wiki_page.get("attachments", []): + _store_attachment(project, serialized.object, attachment) + + history_entries = wiki_page.get("history", []) + for history in history_entries: + _store_history(project, serialized.object, history) + + if not history_entries: + take_snapshot(serialized.object, user=serialized.object.owner) + + return serialized + + add_errors("wiki_pages", serialized.errors) + return None + + +def store_wiki_pages(project, data): + results = [] + for wiki_page in data.get("wiki_pages", []): + results.append(store_wiki_page(project, wiki_page)) + return results + + +## WIKI LINKS + +def store_wiki_link(project, wiki_link): + serialized = serializers.WikiLinkExportSerializer(data=wiki_link) + if serialized.is_valid(): + serialized.object.project = project + serialized.object._importing = True + serialized.save() + return serialized + + add_errors("wiki_links", serialized.errors) + return None + + +def store_wiki_links(project, data): + results = [] + for wiki_link in data.get("wiki_links", []): + results.append(store_wiki_link(project, wiki_link)) + return results + + +## TAGS COLORS + +def store_tags_colors(project, data): + project.tags_colors = data.get("tags_colors", []) + project.save() + return None + + +## TIMELINE + +def _store_timeline_entry(project, timeline): + serialized = serializers.TimelineExportSerializer(data=timeline, context={"project": project}) + if serialized.is_valid(): + serialized.object.project = project + serialized.object.namespace = build_project_namespace(project) + serialized.object.object_id = project.id + serialized.object._importing = True + serialized.save() + return serialized + add_errors("timeline", serialized.errors) + return serialized + + +def store_timeline_entries(project, data): + results = [] + for timeline in data.get("timeline", []): + tl = _store_timeline_entry(project, timeline) + results.append(tl) + return results + + +############################################# +## Store project dict +############################################# + + +def _validate_if_owner_have_enought_space_to_this_project(owner, data): + # Validate if the owner can have this project + data["owner"] = owner.email + + is_private = data.get("is_private", False) + total_memberships = len([m for m in data.get("memberships", []) + if m.get("email", None) != data["owner"]]) + total_memberships = total_memberships + 1 # 1 is the owner + (enough_slots, error_message) = users_service.has_available_slot_for_import_new_project( + owner, + is_private, + total_memberships + ) + if not enough_slots: + raise err.TaigaImportError(error_message, None) + + +def _create_project_object(data): + # Create the project + project_serialized = store_project(data) + + if not project_serialized: + raise err.TaigaImportError(_("error importing project data"), None) + + return project_serialized.object if project_serialized else None + + +def _create_membership_for_project_owner(project): + if project.memberships.filter(user=project.owner).count() == 0: + if project.roles.all().count() > 0: + Membership.objects.create( + project=project, + email=project.owner.email, + user=project.owner, + role=project.roles.all().first(), + is_admin=True + ) + + +def _populate_project_object(project, data): + def check_if_there_is_some_error(message=_("error importing project data"), project=None): + errors = get_errors(clear=False) + if errors: + raise err.TaigaImportError(message, project, errors=errors) + + # Create roles + store_roles(project, data) + check_if_there_is_some_error(_("error importing roles"), None) + + # Create memberships + store_memberships(project, data) + _create_membership_for_project_owner(project) + check_if_there_is_some_error(_("error importing memberships"), project) + + # Create project attributes values + store_project_attributes_values(project, data, "us_statuses", serializers.UserStoryStatusExportSerializer) + store_project_attributes_values(project, data, "points", serializers.PointsExportSerializer) + store_project_attributes_values(project, data, "task_statuses", serializers.TaskStatusExportSerializer) + store_project_attributes_values(project, data, "issue_types", serializers.IssueTypeExportSerializer) + store_project_attributes_values(project, data, "issue_statuses", serializers.IssueStatusExportSerializer) + store_project_attributes_values(project, data, "priorities", serializers.PriorityExportSerializer) + store_project_attributes_values(project, data, "severities", serializers.SeverityExportSerializer) + check_if_there_is_some_error(_("error importing lists of project attributes"), project) + + # Create default values for project attributes + store_default_project_attributes_values(project, data) + check_if_there_is_some_error(_("error importing default project attributes values"), project) + + # Create custom attributes + store_custom_attributes(project, data, "userstorycustomattributes", + serializers.UserStoryCustomAttributeExportSerializer) + store_custom_attributes(project, data, "taskcustomattributes", + serializers.TaskCustomAttributeExportSerializer) + store_custom_attributes(project, data, "issuecustomattributes", + serializers.IssueCustomAttributeExportSerializer) + check_if_there_is_some_error(_("error importing custom attributes"), project) + + + # Create milestones + store_milestones(project, data) + check_if_there_is_some_error(_("error importing sprints"), project) + + # Create user stories + store_user_stories(project, data) + check_if_there_is_some_error(_("error importing user stories"), project) + + # Createer tasks + store_tasks(project, data) + check_if_there_is_some_error(_("error importing tasks"), project) + + # Create issues + store_issues(project, data) + check_if_there_is_some_error(_("error importing issues"), project) + + # Create wiki pages + store_wiki_pages(project, data) + check_if_there_is_some_error(_("error importing wiki pages"), project) + + # Create wiki links + store_wiki_links(project, data) + check_if_there_is_some_error(_("error importing wiki links"), project) + + # Create tags + store_tags_colors(project, data) + check_if_there_is_some_error(_("error importing tags"), project) + + # Create timeline + store_timeline_entries(project, data) + check_if_there_is_some_error(_("error importing timelines"), project) + + # Regenerate stats + project.refresh_totals() + + +def store_project_from_dict(data, owner=None): + reset_errors() + + # Validate + if owner: + _validate_if_owner_have_enought_space_to_this_project(owner, data) + + # Create project + project = _create_project_object(data) + + # Populate project + try: + _populate_project_object(project, data) + except err.TaigaImportError: + # reraise known inport errors + raise + except: + # reise unknown errors as import error + raise err.TaigaImportError(_("unexpected error importing project"), project) + + return project diff --git a/taiga/export_import/tasks.py b/taiga/export_import/tasks.py index 79880ba4..4e5012d1 100644 --- a/taiga/export_import/tasks.py +++ b/taiga/export_import/tasks.py @@ -27,10 +27,11 @@ from django.conf import settings from django.utils.translation import ugettext as _ from taiga.base.mails import mail_builder +from taiga.base.utils import json from taiga.celery import app -from .service import render_project -from .dump_service import dict_to_project +from . import exceptions as err +from . import services from .renderers import ExportRenderer logger = logging.getLogger('taiga.export_import') @@ -46,7 +47,7 @@ def dump_project(self, user, project): try: url = default_storage.url(path) with default_storage.open(storage_path, mode="w") as outfile: - render_project(project, outfile) + services.render_project(project, outfile) except Exception: # Error @@ -77,28 +78,57 @@ def delete_project_dump(project_id, project_slug, task_id): default_storage.delete("exports/{}/{}-{}.json".format(project_id, project_slug, task_id)) +ADMIN_ERROR_LOAD_PROJECT_DUMP_MESSAGE = _(""" + +Error loading dump by {user_full_name} <{user_email}>:" + + +REASON: +------- +{reason} + +DETAILS: +-------- +{details} + +TRACE ERROR: +------------""") + + @app.task def load_project_dump(user, dump): try: - project = dict_to_project(dump, user) - except Exception: - # Error + project = services.store_project_from_dict(dump, user) + except err.TaigaImportError as e: + # On Error + ## remove project + if e.project: + e.project.delete_related_content() + e.project.delete() + + ## send email to the user + error_subject = _("Error loading project dump") + error_message = e.message or _("Error loading your project dump file") + ctx = { "user": user, - "error_subject": _("Error loading project dump"), - "error_message": _("Error loading project dump"), + "error_subject": error_message, + "error_message": error_subject, } email = mail_builder.import_error(user, ctx) email.send() - logger.error('Error loading dump by %s <%s>', - user, - user.email, - exc_info=sys.exc_info()) - # TODO: [Rollback] Remove project because it can be corrupted + ## logged the error to sysadmins + text = ADMIN_ERROR_LOAD_PROJECT_DUMP_MESSAGE.format( + user_full_name=user, + user_email=user.email, + reason=e.message or _(" -- no detail info --"), + details=json.dumps(e.errors, indent=4) + ) + logger.error(text, exc_info=sys.exc_info()) else: - # Success + # On Success ctx = {"user": user, "project": project} email = mail_builder.load_dump(user, ctx) email.send() diff --git a/taiga/locale/ca/LC_MESSAGES/django.po b/taiga/locale/ca/LC_MESSAGES/django.po index 10eb5a90..9deffb7e 100644 --- a/taiga/locale/ca/LC_MESSAGES/django.po +++ b/taiga/locale/ca/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Catalan (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/ca/)\n" @@ -480,59 +480,59 @@ msgstr "Es necessita arxiu dump." msgid "Invalid dump format" msgstr "Format d'arxiu dump invàlid" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "" @@ -555,11 +555,11 @@ msgstr "Conté camps personalitzats invàlids." msgid "Name duplicated for the project" msgstr "" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "" @@ -718,11 +718,12 @@ msgstr "" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "Nom" @@ -738,7 +739,7 @@ msgstr "" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "Descripció" @@ -776,7 +777,7 @@ msgstr "Comentari" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -810,7 +811,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Informació extra" @@ -1197,17 +1198,17 @@ msgstr "" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "Amo" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1226,7 +1227,7 @@ msgstr "Id d'objecte" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1246,10 +1247,10 @@ msgstr "està obsolet " #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "Ordre" @@ -1528,9 +1529,10 @@ msgid "Likes" msgstr "Fans" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slug" @@ -1542,8 +1544,8 @@ msgstr "Data estimada d'inici" msgid "estimated finish date" msgstr "Data estimada de finalització" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "està tancat" @@ -1640,27 +1642,27 @@ msgstr "total de fites" msgid "total story points" msgstr "total de punts d'història" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "activa panell de backlog" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "activa panell de kanban" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "activa panell de wiki" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "activa panell d'incidències" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "sistema de videoconferència" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "" @@ -1676,7 +1678,7 @@ msgstr "permisos d'anònims" msgid "user permissions" msgstr "permisos d'usuaris" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "es privat" @@ -1737,67 +1739,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "configuració de mòdules" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "està arxivat" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "color" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "limit de treball en progrés" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "valor" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "rol d'amo per defecte" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "opcions per defecte" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "status d'històries d'usuari" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "punts" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "status de tasques" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "status d'incidències" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "tipus d'incidències" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "prioritats" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "severitats" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "rols" @@ -3145,19 +3147,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Informació personal" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Permissos" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Dates importants" diff --git a/taiga/locale/de/LC_MESSAGES/django.po b/taiga/locale/de/LC_MESSAGES/django.po index 5c261f3f..d38d46fd 100644 --- a/taiga/locale/de/LC_MESSAGES/django.po +++ b/taiga/locale/de/LC_MESSAGES/django.po @@ -17,8 +17,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: German (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/de/)\n" @@ -535,59 +535,59 @@ msgstr "Exportdatei erforderlich" msgid "Invalid dump format" msgstr "Ungültiges Exportdatei Format" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "Fehler beim Importieren der Projektdaten" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "Fehler beim Importieren der Listen von Projektattributen" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "Fehler beim Importieren der vorgegebenen Projekt Attributwerte " -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "Fehler beim Importieren der Kundenattribute" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "Fehler beim Importieren der Rollen" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "Fehler beim Importieren der Mitgliedschaften" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "Fehler beim Import der Sprints" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "Fehler beim Importieren von Wiki Seiten" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "Fehler beim Importieren von Wiki Links" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "Fehler beim Importieren der Tickets" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "Fehler beim Importieren der User-Stories" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "Fehler beim Importieren der Aufgaben" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "Fehler beim Importieren der Schlagworte" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "Fehler beim Importieren der Chroniken" @@ -610,11 +610,11 @@ msgstr "Enthält ungültige Benutzerfelder." msgid "Name duplicated for the project" msgstr "Der Name für das Projekt ist doppelt vergeben" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Fehler beim Erzeugen der Projekt Export-Datei " -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Fehler beim Laden von Projekt Export-Datei" @@ -866,11 +866,12 @@ msgstr "Authentifizierung erforderlich" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "Name" @@ -886,7 +887,7 @@ msgstr "Web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "Beschreibung" @@ -924,7 +925,7 @@ msgstr "Kommentar" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -957,7 +958,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Zusätzliche Information" @@ -1387,17 +1388,17 @@ msgstr "Nr. unterschreidet sich zwischen dem Objekt und dem Projekt" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "Besitzer" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1416,7 +1417,7 @@ msgstr "Objekt Nr." #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1436,10 +1437,10 @@ msgstr "wurde verworfen" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "Reihenfolge" @@ -1722,9 +1723,10 @@ msgid "Likes" msgstr "Likes" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "Slug" @@ -1736,8 +1738,8 @@ msgstr "geschätzter Starttermin" msgid "estimated finish date" msgstr "geschätzter Endtermin" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "ist geschlossen" @@ -1834,27 +1836,27 @@ msgstr "Meilensteine Gesamt" msgid "total story points" msgstr "Story Punkte insgesamt" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "aktives Backlog Panel" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "aktives Kanban Panel" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "aktives Wiki Panel" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "aktives Tickets Panel" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "Videokonferenzsystem" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "Zusatzdaten Videokonferenz" @@ -1870,7 +1872,7 @@ msgstr "Rechte für anonyme Nutzer" msgid "user permissions" msgstr "Rechte für registrierte Nutzer" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "ist privat" @@ -1931,67 +1933,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "Module konfigurieren" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "ist archiviert" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "Farbe" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "Ausführungslimit" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "Wert" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "voreingestellte Besitzerrolle" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "Vorgabe Optionen" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "User-Story Status " -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "Punkte" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "Aufgaben Status" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "Ticket Status" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "Ticket Arten" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "Prioritäten" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "Gewichtung" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "Rollen" @@ -3668,19 +3670,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Prüfe die API der Historie auf Übereinstimmung" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Personal Information" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Berechtigungen" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Wichtige Termine" diff --git a/taiga/locale/en/LC_MESSAGES/django.po b/taiga/locale/en/LC_MESSAGES/django.po index 7e173433..866f8483 100644 --- a/taiga/locale/en/LC_MESSAGES/django.po +++ b/taiga/locale/en/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" "PO-Revision-Date: 2015-03-25 20:09+0100\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Taiga Dev Team \n" @@ -469,59 +469,59 @@ msgstr "" msgid "Invalid dump format" msgstr "" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "" @@ -544,11 +544,11 @@ msgstr "" msgid "Name duplicated for the project" msgstr "" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "" @@ -707,11 +707,12 @@ msgstr "" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "" @@ -727,7 +728,7 @@ msgstr "" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "" @@ -765,7 +766,7 @@ msgstr "" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -791,7 +792,7 @@ msgid "" msgstr "" #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "" @@ -1170,17 +1171,17 @@ msgstr "" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1199,7 +1200,7 @@ msgstr "" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1219,10 +1220,10 @@ msgstr "" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "" @@ -1501,9 +1502,10 @@ msgid "Likes" msgstr "" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "" @@ -1515,8 +1517,8 @@ msgstr "" msgid "estimated finish date" msgstr "" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "" @@ -1613,27 +1615,27 @@ msgstr "" msgid "total story points" msgstr "" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "" @@ -1649,7 +1651,7 @@ msgstr "" msgid "user permissions" msgstr "" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "" @@ -1710,67 +1712,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "" @@ -3094,19 +3096,39 @@ msgstr "" msgid "Check the history API for the exact diff" msgstr "" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "" diff --git a/taiga/locale/es/LC_MESSAGES/django.po b/taiga/locale/es/LC_MESSAGES/django.po index f9b32379..7538d688 100644 --- a/taiga/locale/es/LC_MESSAGES/django.po +++ b/taiga/locale/es/LC_MESSAGES/django.po @@ -16,8 +16,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Spanish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/es/)\n" @@ -522,59 +522,59 @@ msgstr "Se necesita el fichero con los datos exportados" msgid "Invalid dump format" msgstr "Formato de fichero de exportación inválido" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "error importando los datos del proyecto" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "error importando la listados de valores de attributos del proyecto" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "error importando los valores por defecto de los atributos del proyecto" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "error importando los atributos personalizados" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "error importando los roles" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "error importando los miembros" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "error importando los sprints" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "error importando las páginas del wiki" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "error importando los enlaces del wiki" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "error importando las peticiones" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "error importando las historias de usuario" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "error importando las tareas" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "error importando las etiquetas" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "error importando los timelines" @@ -597,11 +597,11 @@ msgstr "Contiene attributos personalizados inválidos." msgid "Name duplicated for the project" msgstr "Nombre duplicado para el proyecto" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Erro generando el volcado de datos del proyecto" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Error cargando el volcado de datos del proyecto" @@ -848,11 +848,12 @@ msgstr "Se requiere autenticación" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "nombre" @@ -868,7 +869,7 @@ msgstr "web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "descripción" @@ -906,7 +907,7 @@ msgstr "comentario" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -939,7 +940,7 @@ msgstr "" "

%(comment)s

" #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Información extra" @@ -1369,17 +1370,17 @@ msgstr "El ID de proyecto no coincide entre el adjunto y un proyecto" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "Dueño" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1398,7 +1399,7 @@ msgstr "id de objeto" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1418,10 +1419,10 @@ msgstr "está desactualizado" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "orden" @@ -1700,9 +1701,10 @@ msgid "Likes" msgstr "Likes" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slug" @@ -1714,8 +1716,8 @@ msgstr "fecha estimada de comienzo" msgid "estimated finish date" msgstr "fecha estimada de finalización" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "está cerrada" @@ -1814,27 +1816,27 @@ msgstr "total de sprints" msgid "total story points" msgstr "puntos de historia totales" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "panel de backlog activado" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "panel de kanban activado" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "panel de wiki activo" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "panel de peticiones activo" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "sistema de videoconferencia" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "datos extra de videoconferencia" @@ -1850,7 +1852,7 @@ msgstr "permisos de anónimo" msgid "user permissions" msgstr "permisos de usuario" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "privado" @@ -1911,67 +1913,67 @@ msgstr "actividad el último mes" msgid "activity last year" msgstr "actividad el último áño" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "configuración de modulos" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "archivado" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "color" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "limite del trabajo en progreso" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "valor" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "rol por defecto para el propietario" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "opciones por defecto" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "estatuas de historias" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "puntos" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "estatus de tareas" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "estados de petición" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "tipos de petición" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "prioridades" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "gravedades" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "roles" @@ -3608,19 +3610,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Comprueba la API de histórico para obtener el diff exacto" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Información personal" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Permisos" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "Restricciones" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "datos importántes" diff --git a/taiga/locale/fi/LC_MESSAGES/django.po b/taiga/locale/fi/LC_MESSAGES/django.po index 1d97923b..42a4be30 100644 --- a/taiga/locale/fi/LC_MESSAGES/django.po +++ b/taiga/locale/fi/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Finnish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/fi/)\n" @@ -505,59 +505,59 @@ msgstr "Tarvitaan tiedosto" msgid "Invalid dump format" msgstr "Virheellinen tiedostomuoto" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "virhe projektidatan tuonnissa" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "virhe atribuuttilistan tuonnissa" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "virhe oletusarvojen tuonnissa" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "virhe omien arvojen tuonnissa" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "virhe roolien tuonnissa" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "virhe jäsenyyksien tuonnissa" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "virhe kierroksien tuonnissa" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "virhe wiki-sivujen tuonnissa" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "virhe viki-linkkien tuonnissa" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "virhe pyyntöjen tuonnissa" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "virhe käyttäjätarinoiden tuonnissa" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "virhe tehtävien tuonnissa" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "virhe avainsanojen sisäänlukemisessa" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "virhe aikajanojen tuonnissa" @@ -580,11 +580,11 @@ msgstr "Sisältää vieheellisiä omia kenttiä." msgid "Name duplicated for the project" msgstr "Nimi on tuplana projektille" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Virhe tiedoston luonnissa" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Virhe tiedoston latauksessa" @@ -828,11 +828,12 @@ msgstr "" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "nimi" @@ -848,7 +849,7 @@ msgstr "" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "kuvaus" @@ -886,7 +887,7 @@ msgstr "kommentti" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -921,7 +922,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Lisätiedot" @@ -1326,17 +1327,17 @@ msgstr "Projekti ID ei vastaa kohdetta ja projektia" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "omistaja" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1355,7 +1356,7 @@ msgstr "objekti ID" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1375,10 +1376,10 @@ msgstr "on poistettu" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "order" @@ -1657,9 +1658,10 @@ msgid "Likes" msgstr "" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "hukka-aika" @@ -1671,8 +1673,8 @@ msgstr "arvioitu alkupvm" msgid "estimated finish date" msgstr "arvioitu loppupvm" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "on suljettu" @@ -1769,27 +1771,27 @@ msgstr "virstapyväitä yhteensä" msgid "total story points" msgstr "käyttäjätarinan yhteispisteet" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "aktiivinen odottavien paneeli" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "aktiivinen kanban-paneeli" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "aktiivinen wiki-paneeli" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "aktiivinen pyyntöpaneeli" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "videokokous järjestelmä" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "" @@ -1805,7 +1807,7 @@ msgstr "vieraan oikeudet" msgid "user permissions" msgstr "käyttäjän oikeudet" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "on yksityinen" @@ -1866,67 +1868,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "moduulien asetukset" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "on arkistoitu" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "väri" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "työn alla olevien max" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "arvo" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "oletus omistajan rooli" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "oletus optiot" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "kt tilat" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "pisteet" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "tehtävän tilat" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "pyyntöjen tilat" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "pyyntötyypit" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "kiireellisyydet" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "vakavuudet" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "roolit" @@ -3545,19 +3547,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Henkilökohtaiset tiedot" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Oikeudet" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Tärkeät päivämäärät" diff --git a/taiga/locale/fr/LC_MESSAGES/django.po b/taiga/locale/fr/LC_MESSAGES/django.po index 9533481a..f84c878e 100644 --- a/taiga/locale/fr/LC_MESSAGES/django.po +++ b/taiga/locale/fr/LC_MESSAGES/django.po @@ -22,8 +22,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: French (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/fr/)\n" @@ -539,60 +539,60 @@ msgstr "Fichier de dump obligatoire" msgid "Invalid dump format" msgstr "Format de dump invalide" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "Erreur lors de l'importation de données" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "erreur lors de l'importation des listes des attributs de projet" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "" "erreur lors de l'importation des valeurs par défaut des attributs de projet" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "Erreur à l'importation des champs personnalisés" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "Erreur à l'importation des rôles" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "Erreur à l'importation des groupes d'utilisateurs" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "Erreur lors de l'importation des sprints." -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "Erreur à l'importation des pages Wiki" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "Erreur à l'importation des liens Wiki" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "erreur à l'importation des problèmes" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "erreur à l'importation des histoires utilisateur" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "Erreur lors de l'importation des tâches." -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "erreur lors de l'importation des mots-clés" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "erreur lors de l'import des timelines" @@ -615,11 +615,11 @@ msgstr "Contient des champs personnalisés non valides." msgid "Name duplicated for the project" msgstr "Nom dupliqué pour ce projet" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Erreur dans la génération du dump du projet" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Erreur au chargement du dump du projet" @@ -852,11 +852,12 @@ msgstr "Authentification requise" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "nom" @@ -872,7 +873,7 @@ msgstr "web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "description" @@ -910,7 +911,7 @@ msgstr "Commentaire" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -943,7 +944,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Informations supplémentaires" @@ -1339,17 +1340,17 @@ msgstr "L'identifiant du projet de correspond pas entre l'objet et le projet" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "propriétaire" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1368,7 +1369,7 @@ msgstr "identifiant de l'objet" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1388,10 +1389,10 @@ msgstr "est obsolète" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "ordre" @@ -1670,9 +1671,10 @@ msgid "Likes" msgstr "Aime" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slug" @@ -1684,8 +1686,8 @@ msgstr "date de démarrage estimée" msgid "estimated finish date" msgstr "date de fin estimée" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "est fermé" @@ -1782,27 +1784,27 @@ msgstr "total des jalons" msgid "total story points" msgstr "total des points d'histoire" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "panneau backlog actif" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "panneau kanban actif" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "panneau wiki actif" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "panneau problèmes actif" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "plateforme de vidéoconférence" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "données complémentaires pour la salle de vidéoconférence" @@ -1818,7 +1820,7 @@ msgstr "Permissions anonymes" msgid "user permissions" msgstr "Permission de l'utilisateur" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "est privé" @@ -1879,67 +1881,67 @@ msgstr "activité du mois écoulé" msgid "activity last year" msgstr "activité de l'année écoulée" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "Configurations des modules" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "est archivé" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "couleur" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "limite de travail en cours" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "valeur" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "rôle par défaut du propriétaire" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "options par défaut" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "statuts des us" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "points" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "états des tâches" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "statuts des problèmes" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "types de problèmes" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "priorités" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "sévérités" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "rôles" @@ -3332,19 +3334,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Informations personnelles" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Permissions" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Dates importantes" diff --git a/taiga/locale/it/LC_MESSAGES/django.po b/taiga/locale/it/LC_MESSAGES/django.po index 1054bf89..c1f4fcd6 100644 --- a/taiga/locale/it/LC_MESSAGES/django.po +++ b/taiga/locale/it/LC_MESSAGES/django.po @@ -3,6 +3,7 @@ # This file is distributed under the same license as the taiga-back package. # # Translators: +# Alberto Gloder , 2016 # Andrea Raimondi , 2015 # David Barragán , 2015 # F B , 2016 @@ -14,8 +15,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Italian (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/it/)\n" @@ -27,30 +28,30 @@ msgstr "" #: taiga/auth/api.py:100 msgid "Public register is disabled." -msgstr "Registro pubblico disabilitato" +msgstr "La registrazione pubblica è disabilitata." #: taiga/auth/api.py:133 msgid "invalid register type" -msgstr "Tipo di registro invalido" +msgstr "Tipo di registrazione non valida" #: taiga/auth/api.py:146 msgid "invalid login type" -msgstr "Tipo di login invalido" +msgstr "Tipo di login non valido" #: taiga/auth/serializers.py:35 taiga/users/serializers.py:64 msgid "invalid username" -msgstr "Username non valido" +msgstr "Nome utente non valido" #: taiga/auth/serializers.py:40 taiga/users/serializers.py:70 msgid "" "Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'" msgstr "" -"Sono richiesti 255 caratteri, o meno, contenenti: lettere, numeri e " +"Obbligatorio. Al massimo 255 caratteri, Contenenti: lettere, numeri e " "caratteri /./-/_ " #: taiga/auth/services.py:75 msgid "Username is already in use." -msgstr "Il nome utente appena scelto è già utilizzato." +msgstr "Il nome utente scelto è già utilizzato." #: taiga/auth/services.py:78 msgid "Email is already in use." @@ -58,7 +59,7 @@ msgstr "L'email inserita è già utilizzata." #: taiga/auth/services.py:94 msgid "Token not matches any valid invitation." -msgstr "Il token non corrisponde a nessun invito valido" +msgstr "Il token non corrisponde a nessun invito valido." #: taiga/auth/services.py:122 msgid "User is already registered." @@ -70,7 +71,7 @@ msgstr "Questo utente fa già parte del progetto." #: taiga/auth/services.py:172 msgid "Error on creating new user." -msgstr "Errore nella creazione dell'utente." +msgstr "Errore nella creazione della nuova utenza." #: taiga/auth/tokens.py:48 taiga/auth/tokens.py:55 #: taiga/external_apps/services.py:35 taiga/projects/api.py:376 @@ -89,19 +90,20 @@ msgstr "Valore non valido." #: taiga/base/api/fields.py:477 #, python-format msgid "'%s' value must be either True or False." -msgstr "il valore di '%s' deve essere o vero o falso." +msgstr "il valore di '%s' deve essere o Vero o Falso." #: taiga/base/api/fields.py:541 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Uno slug valido è composto da lettere, numeri, caratteri di sottolineatura o " -"trattini" +"Uno 'slug' valido è composto da lettere, numeri, caratteri di sottolineatura " +"o trattini." #: taiga/base/api/fields.py:556 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "Seleziona un valore valido. %(value)s non è una scelta disponibile." +msgstr "" +"Seleziona una scelta valida. %(value)s non è fra le scelte disponibili." #: taiga/base/api/fields.py:619 msgid "Enter a valid email address." @@ -120,21 +122,21 @@ msgstr "L'orario non ha un formato valido. Usa uno dei formati disponibili: %s" #: taiga/base/api/fields.py:795 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" -msgstr "Formato temporale errato. Usare uno dei seguenti formati: %s" +msgstr "Formato temporale errato. Usa uno dei seguenti formati: %s" #: taiga/base/api/fields.py:852 msgid "Enter a whole number." -msgstr "Inserire il numero completo." +msgstr "Inserisci il numero completo." #: taiga/base/api/fields.py:853 taiga/base/api/fields.py:906 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "Assicurati che il valore sia minore o uguale a %(limit_value)s." +msgstr "Assicurati che questo valore sia minore o uguale di %(limit_value)s." #: taiga/base/api/fields.py:854 taiga/base/api/fields.py:907 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "Assicurati che il valore sia maggiore o uguale a %(limit_value)s." +msgstr "Assicurati che questo valore sia maggiore o uguale di %(limit_value)s." #: taiga/base/api/fields.py:884 #, python-format @@ -143,7 +145,7 @@ msgstr "il valore \"%s\" deve essere un valore \"float\"." #: taiga/base/api/fields.py:905 msgid "Enter a number." -msgstr "Inserisci un numero" +msgstr "Inserisci un numero." #: taiga/base/api/fields.py:908 #, python-format @@ -163,7 +165,7 @@ msgstr "Assicurati che non ci siano più di %s cifre prima del punto decimale." #: taiga/base/api/fields.py:977 msgid "No file was submitted. Check the encoding type on the form." msgstr "" -"Non è stato caricato nessun file. Controlla il tipo di codifica nella scheda." +"Non è stato caricato alcun file. Controlla il tipo di codifica nella scheda." #: taiga/base/api/fields.py:978 msgid "No file was submitted." @@ -178,7 +180,7 @@ msgstr "Il file caricato è vuoto." msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "" -"Assicurati che il nome del file abbiamo al massimo %(max)d caratteri (ne ha " +"Assicurati che il nome del file abbia al massimo %(max)d caratteri (ne ha " "%(length)d)." #: taiga/base/api/fields.py:981 @@ -529,60 +531,60 @@ msgstr "E' richiesto un file di dump" msgid "Invalid dump format" msgstr "Formato di dump invalido" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "Errore nell'importazione del progetto dati" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "Errore nell'importazione della lista degli attributi di progetto" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "" "Errore nell'importazione dei valori predefiniti degli attributi del progetto." -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "Errore nell'importazione degli attributi personalizzati" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "Errore nell'importazione i ruoli" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "Errore nell'importazione delle iscrizioni" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "errore nell'importazione degli sprints" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "Errore nell'importazione delle pagine wiki" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "Errore nell'importazione dei link di wiki" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "errore nell'importazione dei problemi" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "Errore nell'importazione delle user story" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "Errore nell'importazione dei compiti " -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "Errore nell'importazione dei tags" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "Errore nell'importazione delle timelines" @@ -605,11 +607,11 @@ msgstr "Contiene campi personalizzati invalidi." msgid "Name duplicated for the project" msgstr "Il nome del progetto è duplicato" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Errore nella creazione del dump di progetto" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Errore nel caricamento del dump di progetto" @@ -915,11 +917,12 @@ msgstr "E' richiesta l'autenticazione" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "nome" @@ -935,7 +938,7 @@ msgstr "web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "descrizione" @@ -973,7 +976,7 @@ msgstr "Commento" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -1006,7 +1009,7 @@ msgstr "" "

%(comment)s

" #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Informazioni aggiuntive" @@ -1460,17 +1463,17 @@ msgstr "L'ID di progetto non corrisponde tra oggetto e progetto" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "proprietario" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1489,7 +1492,7 @@ msgstr "ID dell'oggetto" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1509,10 +1512,10 @@ msgstr "non approvato" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "ordine" @@ -1791,9 +1794,10 @@ msgid "Likes" msgstr "Piaciuto" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "lumaca" @@ -1805,8 +1809,8 @@ msgstr "data stimata di inizio" msgid "estimated finish date" msgstr "data stimata di fine" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "è concluso" @@ -1904,27 +1908,27 @@ msgstr "tappe totali" msgid "total story points" msgstr "punti totali della storia" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "pannello di backlog attivo" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "pannello kanban attivo" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "pannello wiki attivo" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "pannello dei problemi attivo" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "sistema di videoconferenza" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "ulteriori dati di videoconferenza " @@ -1940,7 +1944,7 @@ msgstr "permessi anonimi" msgid "user permissions" msgstr "permessi dell'utente" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "è privato" @@ -2001,67 +2005,67 @@ msgstr "attività nel mese" msgid "activity last year" msgstr "attività nell'anno" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "configurazione dei moduli" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "è archivitato" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "colore" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "limite dei lavori in corso" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "valore" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "ruolo proprietario predefinito" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "opzioni predefinite " -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "stati della storia utente" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "punti" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "stati del compito" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "stati del probema" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "tipologie del problema" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "priorità" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "criticità " -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "ruoli" @@ -3291,11 +3295,15 @@ msgid "" "new project owner for \"%(project_name)s\".

\n" " " msgstr "" +"\n" +"

Ciao %(old_owner_name)s,

\n" +"

%(new_owner_name)s ha accettato la tua offerta e diventerà il nuovo " +"proprietario del progetto \"%(project_name)s\".

" #: taiga/projects/templates/emails/transfer_accept-body-html.jinja:10 #, python-format msgid "

%(new_owner_name)s says:

" -msgstr "" +msgstr "

%(new_owner_name)s dice:

" #: taiga/projects/templates/emails/transfer_accept-body-html.jinja:14 msgid "" @@ -3304,6 +3312,9 @@ msgid "" "p>\n" " " msgstr "" +"\n" +"

Da adesso in avanti, il tuo nuovo status per questo progetto sarà \"admin" +"\".

" #: taiga/projects/templates/emails/transfer_accept-body-text.jinja:1 #, python-format @@ -3313,6 +3324,10 @@ msgid "" "%(new_owner_name)s has accepted your offer and will become the new project " "owner for \"%(project_name)s\".\n" msgstr "" +"\n" +"Ciao %(old_owner_name)s,\n" +"%(new_owner_name)s ha accettato la tua offerta e diventerà il nuovo " +"proprietario del progetto \"%(project_name)s\".\n" #: taiga/projects/templates/emails/transfer_accept-body-text.jinja:7 #, python-format @@ -3843,19 +3858,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Controlla le API della storie per la differenza esatta" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Informazioni personali" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Permessi" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Date importanti" diff --git a/taiga/locale/nl/LC_MESSAGES/django.po b/taiga/locale/nl/LC_MESSAGES/django.po index e2543498..d1782718 100644 --- a/taiga/locale/nl/LC_MESSAGES/django.po +++ b/taiga/locale/nl/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Dutch (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/nl/)\n" @@ -518,59 +518,59 @@ msgstr "Dump file nodig" msgid "Invalid dump format" msgstr "Ongeldig dump formaat" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "fout bij het importeren van project data" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "fout bij importeren van project attributenlijst" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "fout bij importeren van standaard projectattributen waarden" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "fout bij importeren eigen attributen" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "fout bij importeren rollen" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "fout bij importeren lidmaatschappen" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "fout bij importeren sprints" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "fout bij importeren wiki pagina's" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "fout bij importeren wiki links" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "fout bij importeren issues" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "fout bij importeren user stories" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "fout bij importeren taken" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "fout bij importeren tags" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "fout bij importeren tijdlijnen" @@ -593,11 +593,11 @@ msgstr "Het bevat ongeldige eigen velden:" msgid "Name duplicated for the project" msgstr "Naam gedupliceerd voor het project" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Fout bij genereren project dump" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Fout bij laden project dump" @@ -778,11 +778,12 @@ msgstr "" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "naam" @@ -798,7 +799,7 @@ msgstr "" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "omschrijving" @@ -836,7 +837,7 @@ msgstr "commentaar" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -870,7 +871,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Extra info" @@ -1260,17 +1261,17 @@ msgstr "Project ID van object is niet gelijk aan die van het project" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "eigenaar" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1289,7 +1290,7 @@ msgstr "object id" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1309,10 +1310,10 @@ msgstr "is verouderd" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "volgorde" @@ -1593,9 +1594,10 @@ msgid "Likes" msgstr "Personen die dit leuk vinden" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slug" @@ -1607,8 +1609,8 @@ msgstr "geschatte start datum" msgid "estimated finish date" msgstr "geschatte datum van afwerking" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "is gesloten" @@ -1705,27 +1707,27 @@ msgstr "totaal van de milestones" msgid "total story points" msgstr "totaal story points" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "actief backlog paneel" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "actief kanban paneel" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "actief wiki paneel" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "actief issues paneel" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "videoconference systeem" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "" @@ -1741,7 +1743,7 @@ msgstr "anonieme toestemmingen" msgid "user permissions" msgstr "gebruikers toestemmingen" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "is privé" @@ -1802,67 +1804,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "module config" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "is gearchiveerd" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "kleur" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "work in progress limiet" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "waarde" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "standaard rol eigenaar" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "standaard instellingen" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "us statussen" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "punten" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "taak statussen" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "issue statussen" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "issue types" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "prioriteiten" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "ernstniveaus" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "rollen" @@ -3240,19 +3242,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Persoonlijke info" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Toestemmingen" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Belangrijke data" diff --git a/taiga/locale/pl/LC_MESSAGES/django.po b/taiga/locale/pl/LC_MESSAGES/django.po index f205e65f..5ba1251c 100644 --- a/taiga/locale/pl/LC_MESSAGES/django.po +++ b/taiga/locale/pl/LC_MESSAGES/django.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Polish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/pl/)\n" @@ -519,59 +519,59 @@ msgstr "Wymagany plik zrzutu" msgid "Invalid dump format" msgstr "Nieprawidłowy format zrzutu" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "błąd w trakcie importu danych projektu" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "błąd w trakcie importu atrybutów projektu" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "błąd w trakcie importu domyślnych atrybutów projektu" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "błąd w trakcie importu niestandardowych atrybutów" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "błąd w trakcie importu ról" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "błąd w trakcie importu członkostw" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "błąd w trakcie importu sprintów" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "błąd w trakcie importu stron Wiki" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "błąd w trakcie importu linków Wiki" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "błąd w trakcie importu zgłoszeń" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "błąd w trakcie importu historyjek użytkownika" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "błąd w trakcie importu zadań" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "błąd w trakcie importu tagów" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "błąd w trakcie importu osi czasu" @@ -594,11 +594,11 @@ msgstr "Zawiera niewłaściwe pola niestandardowe." msgid "Name duplicated for the project" msgstr "Nazwa projektu zduplikowana" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Błąd w trakcie generowania zrzutu projektu" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Błąd w trakcie wczytywania zrzutu projektu" @@ -846,11 +846,12 @@ msgstr "" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "nazwa" @@ -866,7 +867,7 @@ msgstr "web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "opis" @@ -904,7 +905,7 @@ msgstr "komentarz" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -938,7 +939,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Dodatkowe info" @@ -1368,17 +1369,17 @@ msgstr "ID nie pasuje pomiędzy obiektem a projektem" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "właściciel" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1397,7 +1398,7 @@ msgstr "id obiektu" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1417,10 +1418,10 @@ msgstr "jest przestarzałe" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "kolejność" @@ -1699,9 +1700,10 @@ msgid "Likes" msgstr "" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slug" @@ -1713,8 +1715,8 @@ msgstr "szacowana data rozpoczecia" msgid "estimated finish date" msgstr "szacowana data zakończenia" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "jest zamknięte" @@ -1811,27 +1813,27 @@ msgstr "wszystkich kamieni milowych" msgid "total story points" msgstr "wszystkich punktów " -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "aktywny panel backlog" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "aktywny panel Kanban" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "aktywny panel Wiki" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "aktywny panel zgłoszeń " -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "system wideokonferencji" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "dodatkowe dane dla wideokonferencji" @@ -1847,7 +1849,7 @@ msgstr "uprawnienia anonimowych" msgid "user permissions" msgstr "uprawnienia użytkownika" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "jest prywatna" @@ -1908,67 +1910,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "konfiguracja modułów" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "zarchiwizowane" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "kolor" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "limit postępu prac" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "wartość" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "domyśla rola właściciela" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "domyślne opcje" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "statusy HU" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "pinkty" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "statusy zadań" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "statusy zgłoszeń" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "typy zgłoszeń" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "priorytety" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "ważność" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "role" @@ -3607,19 +3609,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Dla pełengo diffa sprawdź API historii" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Informacje osobiste" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Uprawnienia" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Ważne daty" diff --git a/taiga/locale/pt_BR/LC_MESSAGES/django.po b/taiga/locale/pt_BR/LC_MESSAGES/django.po index 41b4e7b3..9b3d2b72 100644 --- a/taiga/locale/pt_BR/LC_MESSAGES/django.po +++ b/taiga/locale/pt_BR/LC_MESSAGES/django.po @@ -19,8 +19,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/taiga-agile-llc/" "taiga-back/language/pt_BR/)\n" @@ -527,59 +527,59 @@ msgstr "Necessário de arquivo de restauração" msgid "Invalid dump format" msgstr "Formato de aquivo de restauração inválido" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "erro ao importar informações de projeto" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "erro importando lista de atributos do projeto" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "erro importando valores de atributos do projeto padrão" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "erro importando atributos personalizados" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "erro importando funcões" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "erro importando filiações" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "erro importando sprints" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "erro importando páginas wiki" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "erro importando wiki links" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "erro importando casos" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "erro importando user stories" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "erro importando tarefas" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "erro importando tags" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "erro importando linha do tempo" @@ -602,11 +602,11 @@ msgstr "Contém campos personalizados inválidos" msgid "Name duplicated for the project" msgstr "Nome duplicado para o projeto" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Erro gerando arquivo de restauração do projeto" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Erro carregando arquivo de restauração do projeto" @@ -853,11 +853,12 @@ msgstr "Autenticação necessária" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "Nome" @@ -873,7 +874,7 @@ msgstr "web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "descrição" @@ -911,7 +912,7 @@ msgstr "comentário" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -945,7 +946,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Informação extra" @@ -1374,17 +1375,17 @@ msgstr "ID do projeto não combina entre objeto e projeto" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "dono" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1403,7 +1404,7 @@ msgstr "identidade de objeto" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1423,10 +1424,10 @@ msgstr "está obsoleto" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "ordem" @@ -1705,9 +1706,10 @@ msgid "Likes" msgstr "Curtidas" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slug" @@ -1719,8 +1721,8 @@ msgstr "data de início estimada" msgid "estimated finish date" msgstr "data de encerramento estimada" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "está fechado" @@ -1817,27 +1819,27 @@ msgstr "total de marcos de progresso" msgid "total story points" msgstr "pontos totais de US" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "painel de backlog ativo" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "painel de kanban ativo" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "painel de wiki ativo" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "painel de casos ativo" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "sistema de vídeo conferência" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "informação extra de vídeo conferência" @@ -1853,7 +1855,7 @@ msgstr "permissão anônima" msgid "user permissions" msgstr "permissão de usuário" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "é privado" @@ -1914,67 +1916,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "configurações de módulos" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "está arquivado" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "cor" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "trabalho no limite de progresso" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "valor" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "função padrão para dono " -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "opções padrão" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "status de US" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "pontos" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "status de tarefa" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "status de casos" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "tipos de caso" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "prioridades" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "severidades" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "funções" @@ -3588,19 +3590,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Verifique o histórico da API para a exata diferença" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Informação pessoal" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Permissões" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Datas importantes" diff --git a/taiga/locale/ru/LC_MESSAGES/django.po b/taiga/locale/ru/LC_MESSAGES/django.po index ddcdfa3c..f9c67e5f 100644 --- a/taiga/locale/ru/LC_MESSAGES/django.po +++ b/taiga/locale/ru/LC_MESSAGES/django.po @@ -15,8 +15,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Russian (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/ru/)\n" @@ -529,59 +529,59 @@ msgstr "Необходим дамп-файл" msgid "Invalid dump format" msgstr "Неправильный формат дампа" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "ошибка при импорте данных проекта" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "ошибка при импорте списков свойств проекта" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "ошибка при импорте значений по умолчанию свойств проекта" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "ошибка при импорте пользовательских свойств" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "ошибка при импорте ролей" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "ошибка при импорте членства" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "ошибка при импорте спринтов" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "ошибка при импорте вики-страниц" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "ошибка при импорте вики-ссылок" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "ошибка при импорте запросов" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "ошибка импорта историй от пользователей" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "ошибка импорта задач" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "ошибка импорта тэгов" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "ошибка импорта хронологии проекта" @@ -604,11 +604,11 @@ msgstr "Содержит неверные специальные поля" msgid "Name duplicated for the project" msgstr "Уже есть такое имя для проекта" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Ошибка создания свалочного файла для проекта" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Ошибка загрузки свалочного файла проекта" @@ -853,11 +853,12 @@ msgstr "Необходима аутентификация" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "имя" @@ -873,7 +874,7 @@ msgstr "веб" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "описание" @@ -911,7 +912,7 @@ msgstr "комментарий" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -945,7 +946,7 @@ msgstr "" " " #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Дополнительное инфо" @@ -1375,17 +1376,17 @@ msgstr "Идентификатор проекта не подходит к эт #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "владелец" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1404,7 +1405,7 @@ msgstr "идентификатор объекта" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1424,10 +1425,10 @@ msgstr "устаревшее" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "порядок" @@ -1710,9 +1711,10 @@ msgid "Likes" msgstr "Лайки" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "ссылочное имя" @@ -1724,8 +1726,8 @@ msgstr "предполагаемая дата начала" msgid "estimated finish date" msgstr "предполагаемая дата завершения" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "закрыто" @@ -1824,27 +1826,27 @@ msgstr "общее количество вех" msgid "total story points" msgstr "очки истории" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "активная панель списка задач" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "активная панель kanban" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "активная wiki-панель" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "панель активных запросов" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "система видеоконференций" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "дополнительные данные системы видеоконференций" @@ -1860,7 +1862,7 @@ msgstr "права анонимов" msgid "user permissions" msgstr "права пользователя" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "личное" @@ -1921,67 +1923,67 @@ msgstr "активность за месяц" msgid "activity last year" msgstr "активность за год" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "конфигурация модулей" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "архивировано" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "цвет" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "ограничение на активную работу" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "значение" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "роль владельца по умолчанию" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "параметры по умолчанию" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "статусы ПИ" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "очки" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "статусы задач" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "статусы запросов" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "типы запросов" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "приоритеты" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "степени важности" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "роли" @@ -3609,19 +3611,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Свертесть с историей API для получения изменений" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Личные данные" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Права доступа" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Важные даты" diff --git a/taiga/locale/sv/LC_MESSAGES/django.po b/taiga/locale/sv/LC_MESSAGES/django.po index d217159d..88143ba9 100644 --- a/taiga/locale/sv/LC_MESSAGES/django.po +++ b/taiga/locale/sv/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Swedish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/sv/)\n" @@ -503,59 +503,59 @@ msgstr "Behöver en hämtningsfil" msgid "Invalid dump format" msgstr "Invalid hämtningsfilformat" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "fel vid import av projektdata" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "fel vid import av en lista på projektegenskaper" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "fel vid import av standard projektegenskapsvärden" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "fel vid import av anpassade egenskaper" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "fel vid importering av roller" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "fel vid import av medlemskap" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "felaktig import av sprintar" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "vel vid import av wiki-sidor" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "fel vid import av wiki-länkar" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "fel vid import av ärenden" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "fel vid import av användarhistorier" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "fel vid import av uppgifter" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "fel vid importering av taggar" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "fel vid importering av tidslinje" @@ -578,11 +578,11 @@ msgstr "Innehåller felaktigt anpassad fält." msgid "Name duplicated for the project" msgstr "Namnet är upprepad för projektet" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Fel vid skapandet av projektkopia" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Feil vid hämtning av projektkopia" @@ -741,11 +741,12 @@ msgstr "Verifiering krävs" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "namn" @@ -761,7 +762,7 @@ msgstr "Internet" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "beskrivning" @@ -799,7 +800,7 @@ msgstr "kommentera" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -825,7 +826,7 @@ msgid "" msgstr "" #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Extra information" @@ -1215,17 +1216,17 @@ msgstr "Projekt-ID stämmer inte mellan objekt och projekt" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "ägare" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1244,7 +1245,7 @@ msgstr "objekt-ID" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1264,10 +1265,10 @@ msgstr "undviks" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "sortera" @@ -1546,9 +1547,10 @@ msgid "Likes" msgstr "Gillar" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "slugg" @@ -1560,8 +1562,8 @@ msgstr "Beräknad startdatum" msgid "estimated finish date" msgstr "Beräknad slutdato" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "är stängd" @@ -1658,27 +1660,27 @@ msgstr "totalt antal milstolpar" msgid "total story points" msgstr "totalt antal historiepoäng" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "aktivt panel för inkorg" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "aktiv kanban-panel" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "aktiv wiki-panel" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "aktiv panel för ärenden" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "videokonferensssystem" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "videokonferens - extra data" @@ -1694,7 +1696,7 @@ msgstr "anonyma rättigheter" msgid "user permissions" msgstr "användarbehörigheter" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "är privat" @@ -1755,67 +1757,67 @@ msgstr "" msgid "activity last year" msgstr "" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "konfigurera moduler" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "är arkiverad" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "färg" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "begränsad arbete pågår" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "värde" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "ägarens standardroll" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "standard val" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "US statuser" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "poäng" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "statuser för uppgifter" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "status för ärenden" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "ärendentyper" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "prioriteter" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "allvarsgrad" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "roller" @@ -3152,19 +3154,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "Kolla historie API för exakt skillnad" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Personalinformation" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "Behörigheter" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Viktiga datum" diff --git a/taiga/locale/tr/LC_MESSAGES/django.po b/taiga/locale/tr/LC_MESSAGES/django.po index fe7cf855..59c3af70 100644 --- a/taiga/locale/tr/LC_MESSAGES/django.po +++ b/taiga/locale/tr/LC_MESSAGES/django.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Turkish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/tr/)\n" @@ -513,59 +513,59 @@ msgstr "İhtiyaç duyulan döküm dosyası" msgid "Invalid dump format" msgstr "Geçersiz döküm biçemi" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "İçeri aktarılan proje verisinde hata" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "proje öznitelikleri listesi içeriye aktarılırken hata oluştu" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "varsayılan proje öznitelikleri değerlerinin içeriye aktarımında hata" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "özel öznitelikler içeri aktarılırken hata" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "İçeri aktarılan rollerde hata" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "İçeri aktarılan üyeliklerde hata" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "İçeri aktarılan sprintlerde hata" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "İçeri aktarılan wiki sayfalarında hata" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "İçeri aktarılan wiki bağlantılarında hata" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "İçeri aktarılan taleplerde hata" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "İçeri aktarılan kullanıcı hikayelerinde hata" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "İçeri aktarılan görevlerde hata" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "İçeri aktarılan etiketlerde hata" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "zaman çizelgesi içeri aktarılırken hata" @@ -588,11 +588,11 @@ msgstr "Geçersiz özel alanlar içeriyor." msgid "Name duplicated for the project" msgstr "Aynı isimde proje bulunmakta" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "Proje dökümü oluşturulurken hata" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "Proje dökümü yükleniyorken hata" @@ -836,11 +836,12 @@ msgstr "Kimlik doğrulama gerekli" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "isim" @@ -856,7 +857,7 @@ msgstr "web" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "tanı" @@ -894,7 +895,7 @@ msgstr "yorum" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -926,7 +927,7 @@ msgstr "" "

%(comment)s

" #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "Ekstra bilgi" @@ -1322,17 +1323,17 @@ msgstr "Proje ve nesne arasında Proje ID uyuşmazlığı mevcut" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "sahip" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1351,7 +1352,7 @@ msgstr "nesne id" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1371,10 +1372,10 @@ msgstr "kaldırıldı" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "sıra" @@ -1653,9 +1654,10 @@ msgid "Likes" msgstr "Beğeniler" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "satır" @@ -1667,8 +1669,8 @@ msgstr "yaklaşık başlama tarihi" msgid "estimated finish date" msgstr "yaklaşık bitiş tarihi" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "kapatılmış" @@ -1765,27 +1767,27 @@ msgstr "aşamaların toplamı" msgid "total story points" msgstr "toplam hikaye puanı" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "aktif birikmiş iler paneli" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "aktif kanban paneli" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "aktif wiki paneli" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "aktif talep paneli" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "video konferans sistemi" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "videokonferans ekstra verisi" @@ -1801,7 +1803,7 @@ msgstr "anonim izinler" msgid "user permissions" msgstr "kullanıcı izinleri" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "gizli" @@ -1862,67 +1864,67 @@ msgstr "geçen ayın aktiviteleri" msgid "activity last year" msgstr "geçen yılın aktiviteleri" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "modül ayarları" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "arşivlenmiş" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "renk" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "değer" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "varsayılan sahip rolü" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "varsayılan ayarlar" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "kh durumları" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "puanlar" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "görev durumları" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "talep durumları" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "talep tipleri" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "öncelikler" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "önem durumları" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "roller" @@ -3324,19 +3326,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "Kişisel bilgi" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "İzinler" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "Önemli tarihler" diff --git a/taiga/locale/zh-Hant/LC_MESSAGES/django.po b/taiga/locale/zh-Hant/LC_MESSAGES/django.po index 0734e55d..d357abfa 100644 --- a/taiga/locale/zh-Hant/LC_MESSAGES/django.po +++ b/taiga/locale/zh-Hant/LC_MESSAGES/django.po @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-04-08 13:23+0200\n" -"PO-Revision-Date: 2016-04-08 11:23+0000\n" +"POT-Creation-Date: 2016-04-19 16:00+0200\n" +"PO-Revision-Date: 2016-04-19 14:00+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Chinese Traditional (http://www.transifex.com/taiga-agile-llc/" "taiga-back/language/zh-Hant/)\n" @@ -510,59 +510,59 @@ msgstr "需要的堆存檔案" msgid "Invalid dump format" msgstr "無效堆存格式" -#: taiga/export_import/dump_service.py:115 +#: taiga/export_import/dump_service.py:112 msgid "error importing project data" msgstr "滙入重要專案資料出錯" -#: taiga/export_import/dump_service.py:128 +#: taiga/export_import/dump_service.py:125 msgid "error importing lists of project attributes" msgstr "滙入標籤出錯" -#: taiga/export_import/dump_service.py:133 +#: taiga/export_import/dump_service.py:130 msgid "error importing default project attributes values" msgstr "滙入預設專案屬性數值出錯" -#: taiga/export_import/dump_service.py:143 +#: taiga/export_import/dump_service.py:140 msgid "error importing custom attributes" msgstr "滙入客制性屬出錯" -#: taiga/export_import/dump_service.py:148 +#: taiga/export_import/dump_service.py:145 msgid "error importing roles" msgstr "滙入角色出錯" -#: taiga/export_import/dump_service.py:163 +#: taiga/export_import/dump_service.py:160 msgid "error importing memberships" msgstr "滙入成員資格出錯" -#: taiga/export_import/dump_service.py:168 +#: taiga/export_import/dump_service.py:165 msgid "error importing sprints" msgstr "滙入衝刺任務出錯" -#: taiga/export_import/dump_service.py:173 +#: taiga/export_import/dump_service.py:170 msgid "error importing wiki pages" msgstr "滙入維基頁出錯" -#: taiga/export_import/dump_service.py:178 +#: taiga/export_import/dump_service.py:175 msgid "error importing wiki links" msgstr "滙入維基連結出錯" -#: taiga/export_import/dump_service.py:183 +#: taiga/export_import/dump_service.py:180 msgid "error importing issues" msgstr "滙入問題出錯" -#: taiga/export_import/dump_service.py:188 +#: taiga/export_import/dump_service.py:185 msgid "error importing user stories" msgstr "滙入使用者故事出錯" -#: taiga/export_import/dump_service.py:193 +#: taiga/export_import/dump_service.py:190 msgid "error importing tasks" msgstr "滙入任務出錯" -#: taiga/export_import/dump_service.py:198 +#: taiga/export_import/dump_service.py:195 msgid "error importing tags" msgstr "滙入標籤出錯" -#: taiga/export_import/dump_service.py:202 +#: taiga/export_import/dump_service.py:199 msgid "error importing timelines" msgstr "滙入時間軸出錯" @@ -585,11 +585,11 @@ msgstr "包括無效慣例欄位" msgid "Name duplicated for the project" msgstr "專案的名稱被複製了" -#: taiga/export_import/tasks.py:54 taiga/export_import/tasks.py:55 +#: taiga/export_import/tasks.py:55 taiga/export_import/tasks.py:56 msgid "Error generating project dump" msgstr "產生專案傾倒時出錯" -#: taiga/export_import/tasks.py:86 taiga/export_import/tasks.py:87 +#: taiga/export_import/tasks.py:88 taiga/export_import/tasks.py:89 msgid "Error loading project dump" msgstr "載入專案傾倒時出錯" @@ -833,11 +833,12 @@ msgstr "要求取得授權" #: taiga/external_apps/models.py:34 #: taiga/projects/custom_attributes/models.py:35 #: taiga/projects/milestones/models.py:38 taiga/projects/models.py:146 -#: taiga/projects/models.py:472 taiga/projects/models.py:511 -#: taiga/projects/models.py:536 taiga/projects/models.py:573 -#: taiga/projects/models.py:596 taiga/projects/models.py:619 -#: taiga/projects/models.py:654 taiga/projects/models.py:677 -#: taiga/users/models.py:292 taiga/webhooks/models.py:28 +#: taiga/projects/models.py:478 taiga/projects/models.py:517 +#: taiga/projects/models.py:542 taiga/projects/models.py:579 +#: taiga/projects/models.py:602 taiga/projects/models.py:625 +#: taiga/projects/models.py:660 taiga/projects/models.py:683 +#: taiga/users/admin.py:53 taiga/users/models.py:292 +#: taiga/webhooks/models.py:28 msgid "name" msgstr "姓名" @@ -853,7 +854,7 @@ msgstr "網頁" #: taiga/projects/custom_attributes/models.py:36 #: taiga/projects/history/templatetags/functions.py:24 #: taiga/projects/issues/models.py:62 taiga/projects/models.py:150 -#: taiga/projects/models.py:681 taiga/projects/tasks/models.py:61 +#: taiga/projects/models.py:687 taiga/projects/tasks/models.py:61 #: taiga/projects/userstories/models.py:92 msgid "description" msgstr "描述" @@ -891,7 +892,7 @@ msgstr "評論" #: taiga/projects/custom_attributes/models.py:45 #: taiga/projects/issues/models.py:54 taiga/projects/likes/models.py:32 #: taiga/projects/milestones/models.py:49 taiga/projects/models.py:157 -#: taiga/projects/models.py:683 taiga/projects/notifications/models.py:88 +#: taiga/projects/models.py:689 taiga/projects/notifications/models.py:88 #: taiga/projects/tasks/models.py:47 taiga/projects/userstories/models.py:84 #: taiga/projects/votes/models.py:53 taiga/projects/wiki/models.py:40 #: taiga/userstorage/models.py:28 @@ -923,7 +924,7 @@ msgstr "" "

%(comment)s

" #: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 -#: taiga/users/admin.py:52 +#: taiga/users/admin.py:120 msgid "Extra info" msgstr "額外資訊" @@ -1348,17 +1349,17 @@ msgstr "專案ID不符合物件與專案" #: taiga/projects/milestones/models.py:43 taiga/projects/models.py:162 #: taiga/projects/notifications/models.py:61 taiga/projects/tasks/models.py:38 #: taiga/projects/userstories/models.py:66 taiga/projects/wiki/models.py:36 -#: taiga/userstorage/models.py:26 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:26 msgid "owner" msgstr "所有者" #: taiga/projects/attachments/models.py:40 #: taiga/projects/custom_attributes/models.py:42 #: taiga/projects/issues/models.py:52 taiga/projects/milestones/models.py:45 -#: taiga/projects/models.py:460 taiga/projects/models.py:486 -#: taiga/projects/models.py:517 taiga/projects/models.py:546 -#: taiga/projects/models.py:579 taiga/projects/models.py:602 -#: taiga/projects/models.py:629 taiga/projects/models.py:660 +#: taiga/projects/models.py:466 taiga/projects/models.py:492 +#: taiga/projects/models.py:523 taiga/projects/models.py:552 +#: taiga/projects/models.py:585 taiga/projects/models.py:608 +#: taiga/projects/models.py:635 taiga/projects/models.py:666 #: taiga/projects/notifications/models.py:73 #: taiga/projects/notifications/models.py:90 taiga/projects/tasks/models.py:42 #: taiga/projects/userstories/models.py:64 taiga/projects/wiki/models.py:30 @@ -1377,7 +1378,7 @@ msgstr "物件ID" #: taiga/projects/attachments/models.py:50 #: taiga/projects/custom_attributes/models.py:47 #: taiga/projects/issues/models.py:57 taiga/projects/milestones/models.py:52 -#: taiga/projects/models.py:160 taiga/projects/models.py:686 +#: taiga/projects/models.py:160 taiga/projects/models.py:692 #: taiga/projects/tasks/models.py:50 taiga/projects/userstories/models.py:87 #: taiga/projects/wiki/models.py:43 taiga/userstorage/models.py:30 msgid "modified date" @@ -1397,10 +1398,10 @@ msgstr "棄用" #: taiga/projects/attachments/models.py:61 #: taiga/projects/custom_attributes/models.py:40 -#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:476 -#: taiga/projects/models.py:513 taiga/projects/models.py:540 -#: taiga/projects/models.py:575 taiga/projects/models.py:598 -#: taiga/projects/models.py:623 taiga/projects/models.py:656 +#: taiga/projects/milestones/models.py:58 taiga/projects/models.py:482 +#: taiga/projects/models.py:519 taiga/projects/models.py:546 +#: taiga/projects/models.py:581 taiga/projects/models.py:604 +#: taiga/projects/models.py:629 taiga/projects/models.py:662 #: taiga/projects/wiki/models.py:73 taiga/users/models.py:300 msgid "order" msgstr "次序" @@ -1679,9 +1680,10 @@ msgid "Likes" msgstr "喜歡" #: taiga/projects/milestones/models.py:41 taiga/projects/models.py:148 -#: taiga/projects/models.py:474 taiga/projects/models.py:538 -#: taiga/projects/models.py:621 taiga/projects/models.py:679 -#: taiga/projects/wiki/models.py:32 taiga/users/models.py:294 +#: taiga/projects/models.py:480 taiga/projects/models.py:544 +#: taiga/projects/models.py:627 taiga/projects/models.py:685 +#: taiga/projects/wiki/models.py:32 taiga/users/admin.py:57 +#: taiga/users/models.py:294 msgid "slug" msgstr "代稱" @@ -1693,8 +1695,8 @@ msgstr "预計開始日期" msgid "estimated finish date" msgstr "預計完成日期" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:478 -#: taiga/projects/models.py:542 taiga/projects/models.py:625 +#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:484 +#: taiga/projects/models.py:548 taiga/projects/models.py:631 msgid "is closed" msgstr "被關閉" @@ -1791,27 +1793,27 @@ msgstr "全部里程碑" msgid "total story points" msgstr "全部故事點數" -#: taiga/projects/models.py:171 taiga/projects/models.py:692 +#: taiga/projects/models.py:171 taiga/projects/models.py:698 msgid "active backlog panel" msgstr "活躍的待辦任務優先表面板" -#: taiga/projects/models.py:173 taiga/projects/models.py:694 +#: taiga/projects/models.py:173 taiga/projects/models.py:700 msgid "active kanban panel" msgstr "活躍的看板式面板" -#: taiga/projects/models.py:175 taiga/projects/models.py:696 +#: taiga/projects/models.py:175 taiga/projects/models.py:702 msgid "active wiki panel" msgstr "活躍的維基面板" -#: taiga/projects/models.py:177 taiga/projects/models.py:698 +#: taiga/projects/models.py:177 taiga/projects/models.py:704 msgid "active issues panel" msgstr "活躍的問題面板" -#: taiga/projects/models.py:180 taiga/projects/models.py:701 +#: taiga/projects/models.py:180 taiga/projects/models.py:707 msgid "videoconference system" msgstr "視訊會議系統" -#: taiga/projects/models.py:182 taiga/projects/models.py:703 +#: taiga/projects/models.py:182 taiga/projects/models.py:709 msgid "videoconference extra data" msgstr "視訊會議額外資料" @@ -1827,7 +1829,7 @@ msgstr "匿名權限" msgid "user permissions" msgstr "使用者權限" -#: taiga/projects/models.py:198 +#: taiga/projects/models.py:198 taiga/users/admin.py:61 msgid "is private" msgstr "私密" @@ -1888,67 +1890,67 @@ msgstr "上月活躍成員" msgid "activity last year" msgstr "去年活躍成員" -#: taiga/projects/models.py:461 +#: taiga/projects/models.py:467 msgid "modules config" msgstr "模組設定" -#: taiga/projects/models.py:480 +#: taiga/projects/models.py:486 msgid "is archived" msgstr "已歸檔" -#: taiga/projects/models.py:482 taiga/projects/models.py:544 -#: taiga/projects/models.py:577 taiga/projects/models.py:600 -#: taiga/projects/models.py:627 taiga/projects/models.py:658 +#: taiga/projects/models.py:488 taiga/projects/models.py:550 +#: taiga/projects/models.py:583 taiga/projects/models.py:606 +#: taiga/projects/models.py:633 taiga/projects/models.py:664 #: taiga/users/models.py:140 msgid "color" msgstr "顏色" -#: taiga/projects/models.py:484 +#: taiga/projects/models.py:490 msgid "work in progress limit" msgstr "工作進度限制" -#: taiga/projects/models.py:515 taiga/userstorage/models.py:32 +#: taiga/projects/models.py:521 taiga/userstorage/models.py:32 msgid "value" msgstr "價值" -#: taiga/projects/models.py:689 +#: taiga/projects/models.py:695 msgid "default owner's role" msgstr "預設所有者角色" -#: taiga/projects/models.py:705 +#: taiga/projects/models.py:711 msgid "default options" msgstr "預設選項" -#: taiga/projects/models.py:706 +#: taiga/projects/models.py:712 msgid "us statuses" msgstr "我們狀況" -#: taiga/projects/models.py:707 taiga/projects/userstories/models.py:42 +#: taiga/projects/models.py:713 taiga/projects/userstories/models.py:42 #: taiga/projects/userstories/models.py:74 msgid "points" msgstr "點數" -#: taiga/projects/models.py:708 +#: taiga/projects/models.py:714 msgid "task statuses" msgstr "任務狀況" -#: taiga/projects/models.py:709 +#: taiga/projects/models.py:715 msgid "issue statuses" msgstr "問題狀況" -#: taiga/projects/models.py:710 +#: taiga/projects/models.py:716 msgid "issue types" msgstr "問題類型" -#: taiga/projects/models.py:711 +#: taiga/projects/models.py:717 msgid "priorities" msgstr "優先性" -#: taiga/projects/models.py:712 +#: taiga/projects/models.py:718 msgid "severities" msgstr "嚴重性" -#: taiga/projects/models.py:713 +#: taiga/projects/models.py:719 msgid "roles" msgstr "角色" @@ -3564,19 +3566,39 @@ msgstr "href" msgid "Check the history API for the exact diff" msgstr "檢查API過去資料以找出差異" -#: taiga/users/admin.py:51 +#: taiga/users/admin.py:38 +msgid "Project Member" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Members" +msgstr "" + +#: taiga/users/admin.py:49 +msgid "id" +msgstr "" + +#: taiga/users/admin.py:81 +msgid "Project Ownership" +msgstr "" + +#: taiga/users/admin.py:82 +msgid "Project Ownerships" +msgstr "" + +#: taiga/users/admin.py:119 msgid "Personal info" msgstr "個人資訊" -#: taiga/users/admin.py:54 +#: taiga/users/admin.py:122 msgid "Permissions" msgstr "許可" -#: taiga/users/admin.py:55 +#: taiga/users/admin.py:123 msgid "Restrictions" msgstr "" -#: taiga/users/admin.py:57 +#: taiga/users/admin.py:125 msgid "Important dates" msgstr "重要日期" diff --git a/taiga/projects/filters.py b/taiga/projects/filters.py index f01ed179..3bdb10c8 100644 --- a/taiga/projects/filters.py +++ b/taiga/projects/filters.py @@ -40,6 +40,10 @@ class DiscoverModeFilterBackend(FilterBackend): qs = qs.filter(anon_permissions__contains=["view_project"], blocked_code__isnull=True) + # random order for featured projects + if request.QUERY_PARAMS.get("is_featured", None) == 'true': + qs = qs.order_by("?") + return super().filter_queryset(request, qs.distinct(), view) diff --git a/taiga/projects/issues/services.py b/taiga/projects/issues/services.py index 6da95c9b..6a6c5a37 100644 --- a/taiga/projects/issues/services.py +++ b/taiga/projects/issues/services.py @@ -84,12 +84,11 @@ def update_issues_order_in_bulk(bulk_data): def issues_to_csv(project, queryset): csv_data = io.StringIO() - fieldnames = ["ref", "subject", "description", "milestone", "owner", - "owner_full_name", "assigned_to", "assigned_to_full_name", - "status", "severity", "priority", "type", "is_closed", - "attachments", "external_reference", "tags", - "watchers", "voters", - "created_date", "modified_date", "finished_date"] + fieldnames = ["ref", "subject", "description", "sprint", "sprint_estimated_start", + "sprint_estimated_finish", "owner", "owner_full_name", "assigned_to", + "assigned_to_full_name", "status", "severity", "priority", "type", + "is_closed", "attachments", "external_reference", "tags", "watchers", + "voters", "created_date", "modified_date", "finished_date"] custom_attrs = project.issuecustomattributes.all() for custom_attr in custom_attrs: @@ -112,7 +111,9 @@ def issues_to_csv(project, queryset): "ref": issue.ref, "subject": issue.subject, "description": issue.description, - "milestone": issue.milestone.name if issue.milestone else None, + "sprint": issue.milestone.name if issue.milestone else None, + "sprint_estimated_start": issue.milestone.estimated_start if issue.milestone else None, + "sprint_estimated_finish": issue.milestone.estimated_finish if issue.milestone else None, "owner": issue.owner.username if issue.owner else None, "owner_full_name": issue.owner.get_full_name() if issue.owner else None, "assigned_to": issue.assigned_to.username if issue.assigned_to else None, diff --git a/taiga/projects/tasks/services.py b/taiga/projects/tasks/services.py index 7e3e97ce..e1f76f67 100644 --- a/taiga/projects/tasks/services.py +++ b/taiga/projects/tasks/services.py @@ -95,11 +95,10 @@ def snapshot_tasks_in_bulk(bulk_data, user): def tasks_to_csv(project, queryset): csv_data = io.StringIO() - fieldnames = ["ref", "subject", "description", "user_story", "milestone", "owner", - "owner_full_name", "assigned_to", "assigned_to_full_name", - "status", "is_iocaine", "is_closed", "us_order", - "taskboard_order", "attachments", "external_reference", "tags", - "watchers", "voters"] + fieldnames = ["ref", "subject", "description", "user_story", "sprint", "sprint_estimated_start", + "sprint_estimated_finish", "owner", "owner_full_name", "assigned_to", + "assigned_to_full_name", "status", "is_iocaine", "is_closed", "us_order", + "taskboard_order", "attachments", "external_reference", "tags", "watchers", "voters"] custom_attrs = project.taskcustomattributes.all() for custom_attr in custom_attrs: @@ -124,7 +123,9 @@ def tasks_to_csv(project, queryset): "subject": task.subject, "description": task.description, "user_story": task.user_story.ref if task.user_story else None, - "milestone": task.milestone.name if task.milestone else None, + "sprint": task.milestone.name if task.milestone else None, + "sprint_estimated_start": task.milestone.estimated_start if task.milestone else None, + "sprint_estimated_finish": task.milestone.estimated_finish if task.milestone else None, "owner": task.owner.username if task.owner else None, "owner_full_name": task.owner.get_full_name() if task.owner else None, "assigned_to": task.assigned_to.username if task.assigned_to else None, diff --git a/taiga/projects/userstories/services.py b/taiga/projects/userstories/services.py index b8a0533c..b0b881a6 100644 --- a/taiga/projects/userstories/services.py +++ b/taiga/projects/userstories/services.py @@ -130,9 +130,9 @@ def open_userstory(us): def userstories_to_csv(project,queryset): csv_data = io.StringIO() - fieldnames = ["ref", "subject", "description", "milestone", "owner", - "owner_full_name", "assigned_to", "assigned_to_full_name", - "status", "is_closed"] + fieldnames = ["ref", "subject", "description", "sprint", "sprint_estimated_start", + "sprint_estimated_finish", "owner", "owner_full_name", "assigned_to", + "assigned_to_full_name", "status", "is_closed"] roles = project.roles.filter(computable=True).order_by('slug') for role in roles: @@ -144,8 +144,7 @@ def userstories_to_csv(project,queryset): "created_date", "modified_date", "finish_date", "client_requirement", "team_requirement", "attachments", "generated_from_issue", "external_reference", "tasks", - "tags", - "watchers", "voters"] + "tags","watchers", "voters"] custom_attrs = project.userstorycustomattributes.all() for custom_attr in custom_attrs: @@ -174,7 +173,9 @@ def userstories_to_csv(project,queryset): "ref": us.ref, "subject": us.subject, "description": us.description, - "milestone": us.milestone.name if us.milestone else None, + "sprint": us.milestone.name if us.milestone else None, + "sprint_estimated_start": us.milestone.estimated_start if us.milestone else None, + "sprint_estimated_finish": us.milestone.estimated_finish if us.milestone else None, "owner": us.owner.username if us.owner else None, "owner_full_name": us.owner.get_full_name() if us.owner else None, "assigned_to": us.assigned_to.username if us.assigned_to else None, diff --git a/taiga/searches/serializers.py b/taiga/searches/serializers.py index d1173b65..1938b5d3 100644 --- a/taiga/searches/serializers.py +++ b/taiga/searches/serializers.py @@ -41,7 +41,8 @@ class TaskSearchResultsSerializer(TaskSerializer): class UserStorySearchResultsSerializer(UserStorySerializer): class Meta: model = UserStory - fields = ('id', 'ref', 'subject', 'status', 'total_points') + fields = ('id', 'ref', 'subject', 'status', 'total_points', + 'milestone_name', 'milestone_slug') class WikiPageSearchResultsSerializer(WikiPageSerializer): diff --git a/taiga/webhooks/api.py b/taiga/webhooks/api.py index a9b8545e..426537dc 100644 --- a/taiga/webhooks/api.py +++ b/taiga/webhooks/api.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from django.utils import timezone from django.utils.translation import ugettext as _ from taiga.base import filters @@ -45,7 +46,7 @@ class WebhookViewSet(BlockedByProjectMixin, ModelCrudViewSet): self.check_permissions(request, 'test', webhook) self.pre_conditions_blocked(webhook) - webhooklog = tasks.test_webhook(webhook.id, webhook.url, webhook.key) + webhooklog = tasks.test_webhook(webhook.id, webhook.url, webhook.key, request.user, timezone.now()) log = serializers.WebhookLogSerializer(webhooklog) return response.Ok(log.data) diff --git a/taiga/webhooks/serializers.py b/taiga/webhooks/serializers.py index a2714a05..a2553b69 100644 --- a/taiga/webhooks/serializers.py +++ b/taiga/webhooks/serializers.py @@ -20,21 +20,26 @@ from django.core.exceptions import ObjectDoesNotExist from taiga.base.api import serializers from taiga.base.fields import TagsField, PgArrayField, JsonField -from taiga.projects.userstories import models as us_models -from taiga.projects.tasks import models as task_models +from taiga.front.templatetags.functions import resolve as resolve_front_url + +from taiga.projects.history import models as history_models from taiga.projects.issues import models as issue_models from taiga.projects.milestones import models as milestone_models -from taiga.projects.wiki import models as wiki_models -from taiga.projects.history import models as history_models from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer +from taiga.projects.services import get_logo_big_thumbnail_url +from taiga.projects.tasks import models as task_models +from taiga.projects.userstories import models as us_models +from taiga.projects.wiki import models as wiki_models + +from taiga.users.gravatar import get_gravatar_url +from taiga.users.services import get_photo_or_gravatar_url from .models import Webhook, WebhookLog -class HistoryDiffField(serializers.Field): - def to_native(self, obj): - return {key: {"from": value[0], "to": value[1]} for key, value in obj.items()} - +######################################################################## +## WebHooks +######################################################################## class WebhookSerializer(serializers.ModelSerializer): logs_counter = serializers.SerializerMethodField("get_logs_counter") @@ -55,16 +60,93 @@ class WebhookLogSerializer(serializers.ModelSerializer): model = WebhookLog +######################################################################## +## User +######################################################################## + class UserSerializer(serializers.Serializer): id = serializers.SerializerMethodField("get_pk") - name = serializers.SerializerMethodField("get_name") + permalink = serializers.SerializerMethodField("get_permalink") + gravatar_url = serializers.SerializerMethodField("get_gravatar_url") + username = serializers.SerializerMethodField("get_username") + full_name = serializers.SerializerMethodField("get_full_name") + photo = serializers.SerializerMethodField("get_photo") def get_pk(self, obj): return obj.pk - def get_name(self, obj): - return obj.full_name + def get_permalink(self, obj): + return resolve_front_url("user", obj.username) + def get_gravatar_url(self, obj): + return get_gravatar_url(obj.email) + + def get_username(self, obj): + return obj.get_username + + def get_full_name(self, obj): + return obj.get_full_name() + + def get_photo(self, obj): + return get_photo_or_gravatar_url(obj) + +######################################################################## +## Project +######################################################################## + +class ProjectSerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + permalink = serializers.SerializerMethodField("get_permalink") + name = serializers.SerializerMethodField("get_name") + logo_big_url = serializers.SerializerMethodField("get_logo_big_url") + + def get_pk(self, obj): + return obj.pk + + def get_permalink(self, obj): + return resolve_front_url("project", obj.slug) + + def get_name(self, obj): + return obj.name + + def get_logo_big_url(self, obj): + return get_logo_big_thumbnail_url(obj) + + +######################################################################## +## History Serializer +######################################################################## + +class HistoryDiffField(serializers.Field): + def to_native(self, value): + # Tip: 'value' is the object returned by + # taiga.projects.history.models.HistoryEntry.values_diff() + + ret = {} + + for key, val in value.items(): + if key in ["attachments", "custom_attributes"]: + ret[key] = val + elif key == "points": + ret[key] = {k: {"from": v[0], "to": v[1]} for k, v in val.items()} + else: + ret[key] = {"from": val[0], "to": val[1]} + + return ret + + +class HistoryEntrySerializer(serializers.ModelSerializer): + diff = HistoryDiffField(source="values_diff") + + class Meta: + model = history_models.HistoryEntry + exclude = ("id", "type", "key", "is_hidden", "is_snapshot", "snapshot", "user", "delete_comment_user", + "values", "created_at") + + +######################################################################## +## _Misc_ +######################################################################## class CustomAttributesValuesWebhookSerializerMixin(serializers.ModelSerializer): custom_attributes_values = serializers.SerializerMethodField("get_custom_attributes_values") @@ -90,86 +172,251 @@ class CustomAttributesValuesWebhookSerializerMixin(serializers.ModelSerializer): except ObjectDoesNotExist: return None -class PointSerializer(serializers.Serializer): - id = serializers.SerializerMethodField("get_pk") + +class RolePointsSerializer(serializers.Serializer): + role = serializers.SerializerMethodField("get_role") name = serializers.SerializerMethodField("get_name") value = serializers.SerializerMethodField("get_value") + def get_role(self, obj): + return obj.role.name + + def get_name(self, obj): + return obj.points.name + + def get_value(self, obj): + return obj.points.value + + +class UserStoryStatusSerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + name = serializers.SerializerMethodField("get_name") + slug = serializers.SerializerMethodField("get_slug") + color = serializers.SerializerMethodField("get_color") + is_closed = serializers.SerializerMethodField("get_is_closed") + is_archived = serializers.SerializerMethodField("get_is_archived") + def get_pk(self, obj): return obj.pk def get_name(self, obj): return obj.name - def get_value(self, obj): - return obj.value + def get_slug(self, obj): + return obj.slug + + def get_color(self, obj): + return obj.color + + def get_is_closed(self, obj): + return obj.is_closed + + def get_is_archived(self, obj): + return obj.is_archived -class UserStorySerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, - serializers.ModelSerializer): - tags = TagsField(default=[], required=False) - external_reference = PgArrayField(required=False) - owner = UserSerializer() - assigned_to = UserSerializer() - points = PointSerializer(many=True) +class TaskStatusSerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + name = serializers.SerializerMethodField("get_name") + slug = serializers.SerializerMethodField("get_slug") + color = serializers.SerializerMethodField("get_color") + is_closed = serializers.SerializerMethodField("get_is_closed") - class Meta: - model = us_models.UserStory - exclude = ("backlog_order", "sprint_order", "kanban_order", "version") + def get_pk(self, obj): + return obj.pk - def custom_attributes_queryset(self, project): - return project.userstorycustomattributes.all() + def get_name(self, obj): + return obj.name + + def get_slug(self, obj): + return obj.slug + + def get_color(self, obj): + return obj.color + + def get_is_closed(self, obj): + return obj.is_closed -class TaskSerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, - serializers.ModelSerializer): - tags = TagsField(default=[], required=False) - owner = UserSerializer() - assigned_to = UserSerializer() +class IssueStatusSerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + name = serializers.SerializerMethodField("get_name") + slug = serializers.SerializerMethodField("get_slug") + color = serializers.SerializerMethodField("get_color") + is_closed = serializers.SerializerMethodField("get_is_closed") - class Meta: - model = task_models.Task + def get_pk(self, obj): + return obj.pk - def custom_attributes_queryset(self, project): - return project.taskcustomattributes.all() + def get_name(self, obj): + return obj.name + + def get_slug(self, obj): + return obj.slug + + def get_color(self, obj): + return obj.color + + def get_is_closed(self, obj): + return obj.is_closed -class IssueSerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, - serializers.ModelSerializer): - tags = TagsField(default=[], required=False) - owner = UserSerializer() - assigned_to = UserSerializer() +class IssueTypeSerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + name = serializers.SerializerMethodField("get_name") + color = serializers.SerializerMethodField("get_color") - class Meta: - model = issue_models.Issue + def get_pk(self, obj): + return obj.pk - def custom_attributes_queryset(self, project): - return project.issuecustomattributes.all() + def get_name(self, obj): + return obj.name + + def get_color(self, obj): + return obj.color -class WikiPageSerializer(serializers.ModelSerializer): - owner = UserSerializer() - last_modifier = UserSerializer() +class PrioritySerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + name = serializers.SerializerMethodField("get_name") + color = serializers.SerializerMethodField("get_color") - class Meta: - model = wiki_models.WikiPage - exclude = ("watchers", "version") + def get_pk(self, obj): + return obj.pk + def get_name(self, obj): + return obj.name + + def get_color(self, obj): + return obj.color + + +class SeveritySerializer(serializers.Serializer): + id = serializers.SerializerMethodField("get_pk") + name = serializers.SerializerMethodField("get_name") + color = serializers.SerializerMethodField("get_color") + + def get_pk(self, obj): + return obj.pk + + def get_name(self, obj): + return obj.name + + def get_color(self, obj): + return obj.color + + +######################################################################## +## Milestone +######################################################################## class MilestoneSerializer(serializers.ModelSerializer): + permalink = serializers.SerializerMethodField("get_permalink") + project = ProjectSerializer() owner = UserSerializer() class Meta: model = milestone_models.Milestone exclude = ("order", "watchers") + def get_permalink(self, obj): + return resolve_front_url("taskboard", obj.project.slug, obj.slug) -class HistoryEntrySerializer(serializers.ModelSerializer): - diff = HistoryDiffField() - snapshot = JsonField() - values = JsonField() - user = JsonField() - delete_comment_user = JsonField() + +######################################################################## +## User Story +######################################################################## + +class UserStorySerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, + serializers.ModelSerializer): + permalink = serializers.SerializerMethodField("get_permalink") + tags = TagsField(default=[], required=False) + external_reference = PgArrayField(required=False) + project = ProjectSerializer() + owner = UserSerializer() + assigned_to = UserSerializer() + points = RolePointsSerializer(source="role_points", many=True) + status = UserStoryStatusSerializer() + milestone = MilestoneSerializer() class Meta: - model = history_models.HistoryEntry + model = us_models.UserStory + exclude = ("backlog_order", "sprint_order", "kanban_order", "version", "total_watchers", "is_watcher") + + def get_permalink(self, obj): + return resolve_front_url("userstory", obj.project.slug, obj.ref) + + def custom_attributes_queryset(self, project): + return project.userstorycustomattributes.all() + + +######################################################################## +## Task +######################################################################## + +class TaskSerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, + serializers.ModelSerializer): + permalink = serializers.SerializerMethodField("get_permalink") + tags = TagsField(default=[], required=False) + project = ProjectSerializer() + owner = UserSerializer() + assigned_to = UserSerializer() + status = TaskStatusSerializer() + user_story = UserStorySerializer() + milestone = MilestoneSerializer() + + class Meta: + model = task_models.Task + exclude = ("version", "total_watchers", "is_watcher") + + def get_permalink(self, obj): + return resolve_front_url("task", obj.project.slug, obj.ref) + + def custom_attributes_queryset(self, project): + return project.taskcustomattributes.all() + + +######################################################################## +## Issue +######################################################################## + +class IssueSerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, + serializers.ModelSerializer): + permalink = serializers.SerializerMethodField("get_permalink") + tags = TagsField(default=[], required=False) + project = ProjectSerializer() + milestone = MilestoneSerializer() + owner = UserSerializer() + assigned_to = UserSerializer() + status = IssueStatusSerializer() + type = IssueTypeSerializer() + priority = PrioritySerializer() + severity = SeveritySerializer() + + class Meta: + model = issue_models.Issue + exclude = ("version", "total_watchers", "is_watcher") + + def get_permalink(self, obj): + return resolve_front_url("issue", obj.project.slug, obj.ref) + + def custom_attributes_queryset(self, project): + return project.issuecustomattributes.all() + + +######################################################################## +## Wiki Page +######################################################################## + +class WikiPageSerializer(serializers.ModelSerializer): + permalink = serializers.SerializerMethodField("get_permalink") + project = ProjectSerializer() + owner = UserSerializer() + last_modifier = UserSerializer() + + class Meta: + model = wiki_models.WikiPage + exclude = ("watchers", "total_watchers", "is_watcher", "version") + + def get_permalink(self, obj): + return resolve_front_url("wiki", obj.project.slug, obj.slug) diff --git a/taiga/webhooks/signal_handlers.py b/taiga/webhooks/signal_handlers.py index 42755553..415d4fcf 100644 --- a/taiga/webhooks/signal_handlers.py +++ b/taiga/webhooks/signal_handlers.py @@ -61,10 +61,13 @@ def on_new_history_entry(sender, instance, created, **kwargs): extra_args = [instance] elif instance.type == HistoryType.delete: task = tasks.delete_webhook - extra_args = [timezone.now()] + extra_args = [] + + by = instance.owner + date = timezone.now() for webhook in webhooks: - args = [webhook["id"], webhook["url"], webhook["key"], obj] + extra_args + args = [webhook["id"], webhook["url"], webhook["key"], by, date, obj] + extra_args if settings.CELERY_ENABLED: connection.on_commit(lambda: task.delay(*args)) diff --git a/taiga/webhooks/tasks.py b/taiga/webhooks/tasks.py index c9ca6b88..7427fe6e 100644 --- a/taiga/webhooks/tasks.py +++ b/taiga/webhooks/tasks.py @@ -26,7 +26,7 @@ from taiga.celery import app from .serializers import (UserStorySerializer, IssueSerializer, TaskSerializer, WikiPageSerializer, MilestoneSerializer, - HistoryEntrySerializer) + HistoryEntrySerializer, UserSerializer) from .models import WebhookLog @@ -67,58 +67,70 @@ def _send_request(webhook_id, url, key, data): request = requests.Request('POST', url, data=serialized_data, headers=headers) prepared_request = request.prepare() - session = requests.Session() - try: - response = session.send(prepared_request) - webhook_log = WebhookLog.objects.create(webhook_id=webhook_id, url=url, - status=response.status_code, - request_data=data, - request_headers=dict(prepared_request.headers), - response_data=response.content, - response_headers=dict(response.headers), - duration=response.elapsed.total_seconds()) - except RequestException as e: - webhook_log = WebhookLog.objects.create(webhook_id=webhook_id, url=url, status=0, - request_data=data, - request_headers=dict(prepared_request.headers), - response_data="error-in-request: {}".format(str(e)), - response_headers={}, - duration=0) - session.close() + with requests.Session() as session: + try: + response = session.send(prepared_request) + except RequestException as e: + # Error sending the webhook + webhook_log = WebhookLog.objects.create(webhook_id=webhook_id, url=url, status=0, + request_data=data, + request_headers=dict(prepared_request.headers), + response_data="error-in-request: {}".format(str(e)), + response_headers={}, + duration=0) + else: + # Webhook was sent successfully + webhook_log = WebhookLog.objects.create(webhook_id=webhook_id, url=url, + status=response.status_code, + request_data=data, + request_headers=dict(prepared_request.headers), + response_data=response.content, + response_headers=dict(response.headers), + duration=response.elapsed.total_seconds()) + finally: + # Only the last ten webhook logs traces are required + # so remove the leftover + ids = (WebhookLog.objects.filter(webhook_id=webhook_id) + .order_by("-id") + .values_list('id', flat=True)[10:]) + WebhookLog.objects.filter(id__in=ids).delete() - ids = [log.id for log in WebhookLog.objects.filter(webhook_id=webhook_id).order_by("-id")[10:]] - WebhookLog.objects.filter(id__in=ids).delete() return webhook_log @app.task -def change_webhook(webhook_id, url, key, obj, change): +def create_webhook(webhook_id, url, key, by, date, obj): data = {} - data['data'] = _serialize(obj) - data['action'] = "change" - data['type'] = _get_type(obj) - data['change'] = _serialize(change) - - return _send_request(webhook_id, url, key, data) - - -@app.task -def create_webhook(webhook_id, url, key, obj): - data = {} - data['data'] = _serialize(obj) data['action'] = "create" data['type'] = _get_type(obj) + data['by'] = UserSerializer(by).data + data['date'] = date + data['data'] = _serialize(obj) return _send_request(webhook_id, url, key, data) @app.task -def delete_webhook(webhook_id, url, key, obj, deleted_date): +def delete_webhook(webhook_id, url, key, by, date, obj): data = {} - data['data'] = _serialize(obj) data['action'] = "delete" data['type'] = _get_type(obj) - data['deleted_date'] = deleted_date + data['by'] = UserSerializer(by).data + data['date'] = date + data['data'] = _serialize(obj) + + return _send_request(webhook_id, url, key, data) + + +@app.task +def change_webhook(webhook_id, url, key, by, date, obj, change): + data = {} + data['action'] = "change" + data['type'] = _get_type(obj) + data['by'] = UserSerializer(by).data + data['date'] = date + data['data'] = _serialize(obj) + data['change'] = _serialize(change) return _send_request(webhook_id, url, key, data) @@ -129,10 +141,12 @@ def resend_webhook(webhook_id, url, key, data): @app.task -def test_webhook(webhook_id, url, key): +def test_webhook(webhook_id, url, key, by, date): data = {} - data['data'] = {"test": "test"} data['action'] = "test" data['type'] = "test" + data['by'] = UserSerializer(by).data + data['date'] = date + data['data'] = {"test": "test"} return _send_request(webhook_id, url, key, data) diff --git a/tests/integration/test_importer_api.py b/tests/integration/test_importer_api.py index a881e67d..b9e2d1c7 100644 --- a/tests/integration/test_importer_api.py +++ b/tests/integration/test_importer_api.py @@ -23,7 +23,8 @@ from django.core.urlresolvers import reverse from django.core.files.base import ContentFile from taiga.base.utils import json -from taiga.export_import.dump_service import dict_to_project, TaigaImportError +from taiga.export_import import services +from taiga.export_import.exceptions import TaigaImportError from taiga.projects.models import Project, Membership from taiga.projects.issues.models import Issue from taiga.projects.userstories.models import UserStory @@ -36,6 +37,11 @@ from ..utils import DUMMY_BMP_DATA pytestmark = pytest.mark.django_db + +####################################################### +## test api/v1/importer +####################################################### + def test_invalid_project_import(client): user = f.UserFactory.create() client.login(user) @@ -43,7 +49,7 @@ def test_invalid_project_import(client): url = reverse("importer-list") data = {} - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 @@ -60,17 +66,16 @@ def test_valid_project_import_without_extra_data(client): "watchers": ["testing@taiga.io"] } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data must_empty_children = [ "issues", "user_stories", "us_statuses", "wiki_pages", "priorities", "severities", "milestones", "points", "issue_types", "task_statuses", "issue_statuses", "wiki_links", ] - assert all(map(lambda x: len(response_data[x]) == 0, must_empty_children)) - assert response_data["owner"] == user.email - assert response_data["watchers"] == [user.email, user_watching.email] + assert all(map(lambda x: len(response.data[x]) == 0, must_empty_children)) + assert response.data["owner"] == user.email + assert response.data["watchers"] == [user.email, user_watching.email] def test_valid_project_without_enough_public_projects_slots(client): @@ -170,11 +175,10 @@ def test_valid_project_import_with_not_existing_memberships(client): "roles": [{"name": "Role"}] } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data # The new membership and the owner membership - assert len(response_data["memberships"]) == 2 + assert len(response.data["memberships"]) == 2 def test_valid_project_import_with_membership_uuid_rewrite(client): @@ -193,9 +197,8 @@ def test_valid_project_import_with_membership_uuid_rewrite(client): "roles": [{"name": "Role"}] } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data assert Membership.objects.filter(email="with-uuid@email.com", token="123").count() == 0 @@ -234,9 +237,8 @@ def test_valid_project_import_with_extra_data(client): }], } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data must_empty_children = [ "issues", "user_stories", "wiki_pages", "milestones", "wiki_links", @@ -247,10 +249,10 @@ def test_valid_project_import_with_extra_data(client): "issue_types", "task_statuses", "issue_statuses", "memberships", ] - assert all(map(lambda x: len(response_data[x]) == 0, must_empty_children)) + assert all(map(lambda x: len(response.data[x]) == 0, must_empty_children)) # Allwais is created at least the owner membership - assert all(map(lambda x: len(response_data[x]) == 1, must_one_instance_children)) - assert response_data["owner"] == user.email + assert all(map(lambda x: len(response.data[x]) == 1, must_one_instance_children)) + assert response.data["owner"] == user.email def test_invalid_project_import_without_roles(client): @@ -263,10 +265,9 @@ def test_invalid_project_import_without_roles(client): "description": "Imported project", } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 2 + assert len(response.data) == 2 assert Project.objects.filter(slug="imported-project").count() == 0 def test_invalid_project_import_with_extra_data(client): @@ -290,10 +291,9 @@ def test_invalid_project_import_with_extra_data(client): "issue_statuses": [{}], } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 7 + assert len(response.data) == 7 assert Project.objects.filter(slug="imported-project").count() == 0 @@ -360,6 +360,327 @@ def test_invalid_project_import_with_custom_attributes(client): assert Project.objects.filter(slug="imported-project").count() == 0 +####################################################### +## tes api/v1/importer/milestone +####################################################### + +def test_invalid_milestone_import(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + client.login(user) + + url = reverse("importer-milestone", args=[project.pk]) + data = {} + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + + +def test_valid_milestone_import(client): + user = f.UserFactory.create() + user_watching = f.UserFactory.create(email="testing@taiga.io") + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + client.login(user) + + url = reverse("importer-milestone", args=[project.pk]) + data = { + "name": "Imported milestone", + "estimated_start": "2014-10-10", + "estimated_finish": "2014-10-20", + "watchers": ["testing@taiga.io"] + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + assert response.data["watchers"] == [user_watching.email] + +def test_milestone_import_duplicated_milestone(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + client.login(user) + + url = reverse("importer-milestone", args=[project.pk]) + data = { + "name": "Imported milestone", + "estimated_start": "2014-10-10", + "estimated_finish": "2014-10-20", + } + # We create twice the same milestone + response = client.json.post(url, json.dumps(data)) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert response.data["milestones"][0]["name"][0] == "Name duplicated for the project" + + + +####################################################### +## tes api/v1/importer/us +####################################################### + +def test_invalid_us_import(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + client.login(user) + + url = reverse("importer-us", args=[project.pk]) + data = {} + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + + +def test_valid_us_import_without_extra_data(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_us_status = f.UserStoryStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-us", args=[project.pk]) + data = { + "subject": "Test" + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + assert response.data["owner"] == user.email + assert response.data["ref"] is not None + + +def test_valid_us_import_with_extra_data(client): + user = f.UserFactory.create() + user_watching = f.UserFactory.create(email="testing@taiga.io") + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_us_status = f.UserStoryStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-us", args=[project.pk]) + data = { + "subject": "Imported us", + "description": "Imported us", + "attachments": [{ + "owner": user.email, + "attached_file": { + "name": "imported attachment", + "data": base64.b64encode(b"TEST").decode("utf-8") + } + }], + "watchers": ["testing@taiga.io"] + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + assert len(response.data["attachments"]) == 1 + assert response.data["owner"] == user.email + assert response.data["ref"] is not None + assert response.data["watchers"] == [user_watching.email] + + +def test_invalid_us_import_with_extra_data(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_us_status = f.UserStoryStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-us", args=[project.pk]) + data = { + "subject": "Imported us", + "description": "Imported us", + "attachments": [{}], + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert len(response.data) == 1 + assert UserStory.objects.filter(subject="Imported us").count() == 0 + + +def test_invalid_us_import_with_bad_choices(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_us_status = f.UserStoryStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-us", args=[project.pk]) + data = { + "subject": "Imported us", + "description": "Imported us", + "status": "Not valid" + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert len(response.data) == 1 + + +####################################################### +## tes api/v1/importer/task +####################################################### + +def test_invalid_task_import(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + client.login(user) + + url = reverse("importer-task", args=[project.pk]) + data = {} + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + + +def test_valid_task_import_without_extra_data(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_task_status = f.TaskStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-task", args=[project.pk]) + data = { + "subject": "Test" + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + assert response.data["owner"] == user.email + assert response.data["ref"] is not None + + +def test_valid_task_import_with_custom_attributes_values(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + membership = f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_task_status = f.TaskStatusFactory.create(project=project) + project.save() + custom_attr = f.TaskCustomAttributeFactory(project=project) + + url = reverse("importer-task", args=[project.pk]) + data = { + "subject": "Test Custom Attrs Values Tasks", + "custom_attributes_values": { + custom_attr.name: "test_value" + } + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + custom_attributes_values = apps.get_model("custom_attributes.TaskCustomAttributesValues").objects.get( + task__subject=response.data["subject"]) + assert custom_attributes_values.attributes_values == {str(custom_attr.id): "test_value"} + + +def test_valid_task_import_with_extra_data(client): + user = f.UserFactory.create() + user_watching = f.UserFactory.create(email="testing@taiga.io") + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_task_status = f.TaskStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-task", args=[project.pk]) + data = { + "subject": "Imported task", + "description": "Imported task", + "attachments": [{ + "owner": user.email, + "attached_file": { + "name": "imported attachment", + "data": base64.b64encode(b"TEST").decode("utf-8") + } + }], + "watchers": ["testing@taiga.io"] + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + assert len(response.data["attachments"]) == 1 + assert response.data["owner"] == user.email + assert response.data["ref"] is not None + assert response.data["watchers"] == [user_watching.email] + + +def test_invalid_task_import_with_extra_data(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_task_status = f.TaskStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-task", args=[project.pk]) + data = { + "subject": "Imported task", + "description": "Imported task", + "attachments": [{}], + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert len(response.data) == 1 + assert Task.objects.filter(subject="Imported task").count() == 0 + + +def test_invalid_task_import_with_bad_choices(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_task_status = f.TaskStatusFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-task", args=[project.pk]) + data = { + "subject": "Imported task", + "description": "Imported task", + "status": "Not valid" + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert len(response.data) == 1 + + +def test_valid_task_with_user_story(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(project=project, user=user, is_admin=True) + project.default_task_status = f.TaskStatusFactory.create(project=project) + us = f.UserStoryFactory.create(project=project) + project.save() + client.login(user) + + url = reverse("importer-task", args=[project.pk]) + data = { + "subject": "Imported task", + "description": "Imported task", + "user_story": us.ref + } + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + assert us.tasks.all().count() == 1 + + +####################################################### +## tes api/v1/importer/issue +####################################################### + def test_invalid_issue_import(client): user = f.UserFactory.create() project = f.ProjectFactory.create(owner=user) @@ -369,7 +690,7 @@ def test_invalid_issue_import(client): url = reverse("importer-issue", args=[project.pk]) data = {} - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 @@ -387,11 +708,10 @@ def test_valid_user_story_import(client): "finish_date": "2014-10-24T00:00:00+0000" } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data - assert response_data["subject"] == "Imported issue" - assert response_data["finish_date"] == "2014-10-24T00:00:00+0000" + assert response.data["subject"] == "Imported issue" + assert response.data["finish_date"] == "2014-10-24T00:00:00+0000" def test_valid_user_story_import_with_custom_attributes_values(client): @@ -434,11 +754,10 @@ def test_valid_issue_import_without_extra_data(client): "subject": "Test" } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data - assert response_data["owner"] == user.email - assert response_data["ref"] is not None + assert response.data["owner"] == user.email + assert response.data["ref"] is not None def test_valid_issue_import_with_custom_attributes_values(client): @@ -495,14 +814,13 @@ def test_valid_issue_import_with_extra_data(client): "watchers": ["testing@taiga.io"] } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data - assert len(response_data["attachments"]) == 1 - assert response_data["owner"] == user.email - assert response_data["ref"] is not None - assert response_data["finished_date"] == "2014-10-24T00:00:00+0000" - assert response_data["watchers"] == [user_watching.email] + assert len(response.data["attachments"]) == 1 + assert response.data["owner"] == user.email + assert response.data["ref"] is not None + assert response.data["finished_date"] == "2014-10-24T00:00:00+0000" + assert response.data["watchers"] == [user_watching.email] def test_invalid_issue_import_with_extra_data(client): @@ -523,10 +841,9 @@ def test_invalid_issue_import_with_extra_data(client): "attachments": [{}], } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 + assert len(response.data) == 1 assert Issue.objects.filter(subject="Imported issue").count() == 0 @@ -548,10 +865,9 @@ def test_invalid_issue_import_with_bad_choices(client): "status": "Not valid" } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 + assert len(response.data) == 1 url = reverse("importer-issue", args=[project.pk]) data = { @@ -560,10 +876,9 @@ def test_invalid_issue_import_with_bad_choices(client): "priority": "Not valid" } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 + assert len(response.data) == 1 url = reverse("importer-issue", args=[project.pk]) data = { @@ -572,10 +887,9 @@ def test_invalid_issue_import_with_bad_choices(client): "severity": "Not valid" } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 + assert len(response.data) == 1 url = reverse("importer-issue", args=[project.pk]) data = { @@ -584,272 +898,14 @@ def test_invalid_issue_import_with_bad_choices(client): "type": "Not valid" } - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 - - -def test_invalid_us_import(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - client.login(user) - - url = reverse("importer-us", args=[project.pk]) - data = {} - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - - -def test_valid_us_import_without_extra_data(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_us_status = f.UserStoryStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-us", args=[project.pk]) - data = { - "subject": "Test" - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 201 - response_data = response.data - assert response_data["owner"] == user.email - assert response_data["ref"] is not None - - -def test_valid_us_import_with_extra_data(client): - user = f.UserFactory.create() - user_watching = f.UserFactory.create(email="testing@taiga.io") - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_us_status = f.UserStoryStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-us", args=[project.pk]) - data = { - "subject": "Imported us", - "description": "Imported us", - "attachments": [{ - "owner": user.email, - "attached_file": { - "name": "imported attachment", - "data": base64.b64encode(b"TEST").decode("utf-8") - } - }], - "watchers": ["testing@taiga.io"] - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 201 - response_data = response.data - assert len(response_data["attachments"]) == 1 - assert response_data["owner"] == user.email - assert response_data["ref"] is not None - assert response_data["watchers"] == [user_watching.email] - - -def test_invalid_us_import_with_extra_data(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_us_status = f.UserStoryStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-us", args=[project.pk]) - data = { - "subject": "Imported us", - "description": "Imported us", - "attachments": [{}], - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 - assert UserStory.objects.filter(subject="Imported us").count() == 0 - - -def test_invalid_us_import_with_bad_choices(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_us_status = f.UserStoryStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-us", args=[project.pk]) - data = { - "subject": "Imported us", - "description": "Imported us", - "status": "Not valid" - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 - - -def test_invalid_task_import(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - client.login(user) - - url = reverse("importer-task", args=[project.pk]) - data = {} - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - - -def test_valid_task_import_without_extra_data(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_task_status = f.TaskStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-task", args=[project.pk]) - data = { - "subject": "Test" - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 201 - response_data = response.data - assert response_data["owner"] == user.email - assert response_data["ref"] is not None - - -def test_valid_task_import_with_custom_attributes_values(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - membership = f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_task_status = f.TaskStatusFactory.create(project=project) - project.save() - custom_attr = f.TaskCustomAttributeFactory(project=project) - - url = reverse("importer-task", args=[project.pk]) - data = { - "subject": "Test Custom Attrs Values Tasks", - "custom_attributes_values": { - custom_attr.name: "test_value" - } - } - - client.login(user) response = client.json.post(url, json.dumps(data)) - assert response.status_code == 201 - custom_attributes_values = apps.get_model("custom_attributes.TaskCustomAttributesValues").objects.get( - task__subject=response.data["subject"]) - assert custom_attributes_values.attributes_values == {str(custom_attr.id): "test_value"} - - -def test_valid_task_import_with_extra_data(client): - user = f.UserFactory.create() - user_watching = f.UserFactory.create(email="testing@taiga.io") - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_task_status = f.TaskStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-task", args=[project.pk]) - data = { - "subject": "Imported task", - "description": "Imported task", - "attachments": [{ - "owner": user.email, - "attached_file": { - "name": "imported attachment", - "data": base64.b64encode(b"TEST").decode("utf-8") - } - }], - "watchers": ["testing@taiga.io"] - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 201 - response_data = response.data - assert len(response_data["attachments"]) == 1 - assert response_data["owner"] == user.email - assert response_data["ref"] is not None - assert response_data["watchers"] == [user_watching.email] - - -def test_invalid_task_import_with_extra_data(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_task_status = f.TaskStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-task", args=[project.pk]) - data = { - "subject": "Imported task", - "description": "Imported task", - "attachments": [{}], - } - - response = client.post(url, json.dumps(data), content_type="application/json") assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 - assert Task.objects.filter(subject="Imported task").count() == 0 + assert len(response.data) == 1 -def test_invalid_task_import_with_bad_choices(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_task_status = f.TaskStatusFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-task", args=[project.pk]) - data = { - "subject": "Imported task", - "description": "Imported task", - "status": "Not valid" - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 - - -def test_valid_task_with_user_story(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - project.default_task_status = f.TaskStatusFactory.create(project=project) - us = f.UserStoryFactory.create(project=project) - project.save() - client.login(user) - - url = reverse("importer-task", args=[project.pk]) - data = { - "subject": "Imported task", - "description": "Imported task", - "user_story": us.ref - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 201 - assert us.tasks.all().count() == 1 - +####################################################### +## tes api/v1/importer/wiki-page +####################################################### def test_invalid_wiki_page_import(client): user = f.UserFactory.create() @@ -860,7 +916,7 @@ def test_invalid_wiki_page_import(client): url = reverse("importer-wiki-page", args=[project.pk]) data = {} - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 @@ -875,10 +931,9 @@ def test_valid_wiki_page_import_without_extra_data(client): "slug": "imported-wiki-page", } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data - assert response_data["owner"] == user.email + assert response.data["owner"] == user.email def test_valid_wiki_page_import_with_extra_data(client): @@ -902,12 +957,11 @@ def test_valid_wiki_page_import_with_extra_data(client): "watchers": ["testing@taiga.io"] } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data - assert len(response_data["attachments"]) == 1 - assert response_data["owner"] == user.email - assert response_data["watchers"] == [user_watching.email] + assert len(response.data["attachments"]) == 1 + assert response.data["owner"] == user.email + assert response.data["watchers"] == [user_watching.email] def test_invalid_wiki_page_import_with_extra_data(client): @@ -923,13 +977,16 @@ def test_invalid_wiki_page_import_with_extra_data(client): "attachments": [{}], } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - response_data = response.data - assert len(response_data) == 1 + assert len(response.data) == 1 assert WikiPage.objects.filter(slug="imported-wiki-page").count() == 0 +####################################################### +## tes api/v1/importer/wiki-link +####################################################### + def test_invalid_wiki_link_import(client): user = f.UserFactory.create() project = f.ProjectFactory.create(owner=user) @@ -939,7 +996,7 @@ def test_invalid_wiki_link_import(client): url = reverse("importer-wiki-link", args=[project.pk]) data = {} - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 @@ -955,65 +1012,16 @@ def test_valid_wiki_link_import(client): "href": "imported-wiki-link", } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 response.data +################################################################## +## tes taiga.export_import.services.store_project_from_dict +################################################################## -def test_invalid_milestone_import(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - client.login(user) - - url = reverse("importer-milestone", args=[project.pk]) - data = {} - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - - -def test_valid_milestone_import(client): - user = f.UserFactory.create() - user_watching = f.UserFactory.create(email="testing@taiga.io") - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - client.login(user) - - url = reverse("importer-milestone", args=[project.pk]) - data = { - "name": "Imported milestone", - "estimated_start": "2014-10-10", - "estimated_finish": "2014-10-20", - "watchers": ["testing@taiga.io"] - } - - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 201 - assert response.data["watchers"] == [user_watching.email] - -def test_milestone_import_duplicated_milestone(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - f.MembershipFactory(project=project, user=user, is_admin=True) - client.login(user) - - url = reverse("importer-milestone", args=[project.pk]) - data = { - "name": "Imported milestone", - "estimated_start": "2014-10-10", - "estimated_finish": "2014-10-20", - } - # We create twice the same milestone - response = client.post(url, json.dumps(data), content_type="application/json") - response = client.post(url, json.dumps(data), content_type="application/json") - assert response.status_code == 400 - response_data = response.data - assert response_data["milestones"][0]["name"][0] == "Name duplicated for the project" - - -def test_dict_to_project_with_no_projects_slots_available(client): +def test_services_store_project_from_dict_with_no_projects_slots_available(client): user = f.UserFactory.create(max_private_projects=0) data = { @@ -1024,12 +1032,12 @@ def test_dict_to_project_with_no_projects_slots_available(client): } with pytest.raises(TaigaImportError) as excinfo: - project = dict_to_project(data, owner=user) + project = services.store_project_from_dict(data, owner=user) assert "can't have more private projects" in str(excinfo.value) -def test_dict_to_project_with_no_members_private_project_slots_available(client): +def test_services_store_project_from_dict_with_no_members_private_project_slots_available(client): user = f.UserFactory.create(max_memberships_private_projects=2) data = { @@ -1059,12 +1067,12 @@ def test_dict_to_project_with_no_members_private_project_slots_available(client) } with pytest.raises(TaigaImportError) as excinfo: - project = dict_to_project(data, owner=user) + project = services.store_project_from_dict(data, owner=user) assert "reaches your current limit of memberships for private" in str(excinfo.value) -def test_dict_to_project_with_no_members_public_project_slots_available(client): +def test_services_store_project_from_dict_with_no_members_public_project_slots_available(client): user = f.UserFactory.create(max_memberships_public_projects=2) data = { @@ -1094,11 +1102,15 @@ def test_dict_to_project_with_no_members_public_project_slots_available(client): } with pytest.raises(TaigaImportError) as excinfo: - project = dict_to_project(data, owner=user) + project = services.store_project_from_dict(data, owner=user) assert "reaches your current limit of memberships for public" in str(excinfo.value) +################################################################## +## tes api/v1/importer/load-dummp +################################################################## + def test_invalid_dump_import(client): user = f.UserFactory.create() client.login(user) @@ -1110,132 +1122,11 @@ def test_invalid_dump_import(client): response = client.post(url, {'dump': data}) assert response.status_code == 400 - response_data = response.data - assert response_data["_error_message"] == "Invalid dump format" + assert response.data["_error_message"] == "Invalid dump format" -def test_valid_dump_import_with_logo(client, settings): +def test_valid_dump_import_without_enough_public_projects_slots(client, settings): settings.CELERY_ENABLED = False - - user = f.UserFactory.create() - client.login(user) - - url = reverse("importer-load-dump") - - data = ContentFile(bytes(json.dumps({ - "slug": "valid-project", - "name": "Valid project", - "description": "Valid project desc", - "is_private": False, - "logo": { - "name": "logo.bmp", - "data": base64.b64encode(DUMMY_BMP_DATA).decode("utf-8") - } - }), "utf-8")) - data.name = "test" - - response = client.post(url, {'dump': data}) - assert response.status_code == 201 - response_data = response.data - assert "id" in response_data - assert response_data["name"] == "Valid project" - assert "logo_small_url" in response_data - assert response_data["logo_small_url"] != None - assert "logo_big_url" in response_data - assert response_data["logo_big_url"] != None - - -def test_valid_dump_import_with_celery_disabled(client, settings): - settings.CELERY_ENABLED = False - - user = f.UserFactory.create() - client.login(user) - - url = reverse("importer-load-dump") - - data = ContentFile(bytes(json.dumps({ - "slug": "valid-project", - "name": "Valid project", - "description": "Valid project desc", - "is_private": True - }), "utf-8")) - data.name = "test" - - response = client.post(url, {'dump': data}) - assert response.status_code == 201 - response_data = response.data - assert "id" in response_data - assert response_data["name"] == "Valid project" - - -def test_valid_dump_import_with_celery_enabled(client, settings): - settings.CELERY_ENABLED = True - - user = f.UserFactory.create() - client.login(user) - - url = reverse("importer-load-dump") - - data = ContentFile(bytes(json.dumps({ - "slug": "valid-project", - "name": "Valid project", - "description": "Valid project desc", - "is_private": True - }), "utf-8")) - data.name = "test" - - response = client.post(url, {'dump': data}) - assert response.status_code == 202 - response_data = response.data - assert "import_id" in response_data - - -def test_dump_import_duplicated_project(client): - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - client.login(user) - - url = reverse("importer-load-dump") - - data = ContentFile(bytes(json.dumps({ - "slug": project.slug, - "name": "Test import", - "description": "Valid project desc", - "is_private": True - }), "utf-8")) - data.name = "test" - - response = client.post(url, {'dump': data}) - assert response.status_code == 201 - response_data = response.data - assert response_data["name"] == "Test import" - assert response_data["slug"] == "{}-test-import".format(user.username) - - -def test_dump_import_throttling(client, settings): - settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["import-dump-mode"] = "1/minute" - - user = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user) - client.login(user) - - url = reverse("importer-load-dump") - - data = ContentFile(bytes(json.dumps({ - "slug": project.slug, - "name": "Test import", - "description": "Valid project desc", - "is_private": True - }), "utf-8")) - data.name = "test" - - response = client.post(url, {'dump': data}) - assert response.status_code == 201 - response = client.post(url, {'dump': data}) - assert response.status_code == 429 - - -def test_valid_dump_import_without_enough_public_projects_slots(client): user = f.UserFactory.create(max_public_projects=0) client.login(user) @@ -1257,7 +1148,8 @@ def test_valid_dump_import_without_enough_public_projects_slots(client): assert Project.objects.filter(slug="public-project-without-slots").count() == 0 -def test_valid_dump_import_without_enough_private_projects_slots(client): +def test_valid_dump_import_without_enough_private_projects_slots(client, settings): + settings.CELERY_ENABLED = False user = f.UserFactory.create(max_private_projects=0) client.login(user) @@ -1279,7 +1171,8 @@ def test_valid_dump_import_without_enough_private_projects_slots(client): assert Project.objects.filter(slug="private-project-without-slots").count() == 0 -def test_valid_dump_import_without_enough_membership_private_project_slots_one_project(client): +def test_valid_dump_import_without_enough_membership_private_project_slots_one_project(client, settings): + settings.CELERY_ENABLED = False user = f.UserFactory.create(max_memberships_private_projects=5) client.login(user) @@ -1326,7 +1219,8 @@ def test_valid_dump_import_without_enough_membership_private_project_slots_one_p assert Project.objects.filter(slug="project-without-memberships-slots").count() == 0 -def test_valid_dump_import_without_enough_membership_public_project_slots_one_project(client): +def test_valid_dump_import_without_enough_membership_public_project_slots_one_project(client, settings): + settings.CELERY_ENABLED = False user = f.UserFactory.create(max_memberships_public_projects=5) client.login(user) @@ -1424,9 +1318,8 @@ def test_valid_dump_import_with_enough_membership_private_project_slots_multiple response = client.post(url, {'dump': data}) assert response.status_code == 201 - response_data = response.data - assert "id" in response_data - assert response_data["name"] == "Valid project" + assert "id" in response.data + assert response.data["name"] == "Valid project" def test_valid_dump_import_with_enough_membership_public_project_slots_multiple_projects(client, settings): @@ -1480,30 +1373,13 @@ def test_valid_dump_import_with_enough_membership_public_project_slots_multiple_ response = client.post(url, {'dump': data}) assert response.status_code == 201 - response_data = response.data - assert "id" in response_data - assert response_data["name"] == "Valid project" + assert "id" in response.data + assert response.data["name"] == "Valid project" -def test_valid_dump_import_without_slug(client): - project = f.ProjectFactory.create(slug="existing-slug") - user = f.UserFactory.create() - client.login(user) - url = reverse("importer-load-dump") - - data = ContentFile(bytes(json.dumps({ - "name": "Project name", - "description": "Valid project desc", - "is_private": True - }), "utf-8")) - data.name = "test" - - response = client.post(url, {'dump': data}) - assert response.status_code == 201 - - -def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_private_project(client): +def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_private_project(client, settings): + settings.CELERY_ENABLED = False user = f.UserFactory.create(max_memberships_private_projects=5) client.login(user) @@ -1545,7 +1421,8 @@ def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_private_pro assert Project.objects.filter(slug="private-project-with-memberships-limit-with-you").count() == 1 -def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_public_project(client): +def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_public_project(client, settings): + settings.CELERY_ENABLED = False user = f.UserFactory.create(max_memberships_public_projects=5) client.login(user) @@ -1587,6 +1464,203 @@ def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_public_proj assert Project.objects.filter(slug="public-project-with-memberships-limit-with-you").count() == 1 +def test_valid_dump_import_with_celery_disabled(client, settings): + settings.CELERY_ENABLED = False + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "valid-project", + "name": "Valid project", + "description": "Valid project desc", + "is_private": True + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 201 + assert "id" in response.data + assert response.data["name"] == "Valid project" + + +def test_invalid_dump_import_with_celery_disabled(client, settings): + settings.CELERY_ENABLED = False + user = f.UserFactory.create(max_memberships_public_projects=5) + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "invalid-project", + "name": "Invalid project", + "description": "Valid project desc", + "is_private": False, + "memberships": [ + { + "email": user.email, + "role": "Role", + }, + { + "email": "test2@test.com", + "role": "Role", + }, + { + "email": "test3@test.com", + "role": "Role", + }, + { + "email": "test4@test.com", + "role": "Role", + }, + { + "email": "test5@test.com", + "role": "Role", + }, + ], + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 400 + + +def test_valid_dump_import_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "valid-project", + "name": "Valid project", + "description": "Valid project desc", + "is_private": True + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 202 + assert "import_id" in response.data + assert Project.objects.filter(slug="valid-project").count() == 1 + + +def test_invalid_dump_import_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + user = f.UserFactory.create(max_memberships_public_projects=5) + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "invalid-project", + "name": "Invalid project", + "description": "Valid project desc", + "is_private": False, + "memberships": [ + { + "email": user.email, + "role": "Role", + }, + { + "email": "test2@test.com", + "role": "Role", + }, + { + "email": "test3@test.com", + "role": "Role", + }, + { + "email": "test4@test.com", + "role": "Role", + }, + { + "email": "test5@test.com", + "role": "Role", + }, + ], + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 202 + assert "import_id" in response.data + assert Project.objects.filter(slug="invalid-project").count() == 0 + + +def test_dump_import_throttling(client, settings): + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["import-dump-mode"] = "1/minute" + + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": project.slug, + "name": "Test import", + "description": "Valid project desc", + "is_private": True + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 201 + response = client.post(url, {'dump': data}) + assert response.status_code == 429 + + +def test_valid_dump_import_without_slug(client): + project = f.ProjectFactory.create(slug="existing-slug") + user = f.UserFactory.create() + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "name": "Project name", + "description": "Valid project desc", + "is_private": True + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 201 + + +def test_valid_dump_import_with_logo(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": "valid-project", + "name": "Valid project", + "description": "Valid project desc", + "is_private": False, + "logo": { + "name": "logo.bmp", + "data": base64.b64encode(DUMMY_BMP_DATA).decode("utf-8") + } + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 201 + assert "id" in response.data + assert response.data["name"] == "Valid project" + assert "logo_small_url" in response.data + assert response.data["logo_small_url"] != None + assert "logo_big_url" in response.data + assert response.data["logo_big_url"] != None + + def test_valid_project_import_and_disabled_is_featured(client): user = f.UserFactory.create() client.login(user) @@ -1602,8 +1676,30 @@ def test_valid_project_import_and_disabled_is_featured(client): "is_featured": True } - response = client.post(url, json.dumps(data), content_type="application/json") + response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 - response_data = response.data - assert response_data["owner"] == user.email - assert response_data["is_featured"] == False + assert response.data["owner"] == user.email + assert response.data["is_featured"] == False + + +def test_dump_import_duplicated_project(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + client.login(user) + + url = reverse("importer-load-dump") + + data = ContentFile(bytes(json.dumps({ + "slug": project.slug, + "name": "Test import", + "description": "Valid project desc", + "is_private": True + }), "utf-8")) + data.name = "test" + + response = client.post(url, {'dump': data}) + assert response.status_code == 201 + assert response.data["name"] == "Test import" + assert response.data["slug"] == "{}-test-import".format(user.username) + + diff --git a/tests/integration/test_issues.py b/tests/integration/test_issues.py index 71f45f3f..14b01c60 100644 --- a/tests/integration/test_issues.py +++ b/tests/integration/test_issues.py @@ -433,6 +433,6 @@ def test_custom_fields_csv_generation(): data.seek(0) reader = csv.reader(data) row = next(reader) - assert row[21] == attr.name + assert row[23] == attr.name row = next(reader) - assert row[21] == "val1" + assert row[23] == "val1" diff --git a/tests/integration/test_tasks.py b/tests/integration/test_tasks.py index 2e8203fd..ee897b5f 100644 --- a/tests/integration/test_tasks.py +++ b/tests/integration/test_tasks.py @@ -64,7 +64,7 @@ def test_create_task_without_default_values(client): response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 assert response.data['status'] == None - + def test_api_update_task_tags(client): project = f.ProjectFactory.create() @@ -176,6 +176,6 @@ def test_custom_fields_csv_generation(): data.seek(0) reader = csv.reader(data) row = next(reader) - assert row[19] == attr.name + assert row[21] == attr.name row = next(reader) - assert row[19] == "val1" + assert row[21] == "val1" diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index 75b3098a..8081eefd 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -469,9 +469,9 @@ def test_custom_fields_csv_generation(): data.seek(0) reader = csv.reader(data) row = next(reader) - assert row[26] == attr.name + assert row[28] == attr.name row = next(reader) - assert row[26] == "val1" + assert row[28] == "val1" def test_update_userstory_respecting_watchers(client): diff --git a/tests/integration/test_webhooks_issues.py b/tests/integration/test_webhooks_issues.py new file mode 100644 index 00000000..95f6a4a2 --- /dev/null +++ b/tests/integration/test_webhooks_issues.py @@ -0,0 +1,248 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Anler Hernández +# 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 . + +import pytest +from unittest.mock import patch +from unittest.mock import Mock + +from .. import factories as f + +from taiga.projects.history import services + + +pytestmark = pytest.mark.django_db(transaction=True) + + +from taiga.base.utils import json + +def test_webhooks_when_create_issue(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.IssueFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "create" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + + +def test_webhooks_when_update_issue(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.IssueFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + obj.subject = "test webhook update" + obj.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["data"]["subject"] == obj.subject + assert data["change"]["comment"] == "test_comment" + assert data["change"]["diff"]["subject"]["to"] == data["data"]["subject"] + assert data["change"]["diff"]["subject"]["from"] != data["data"]["subject"] + + +def test_webhooks_when_delete_issue(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.IssueFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, delete=True) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "delete" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert "data" in data + + +def test_webhooks_when_update_issue_attachments(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.IssueFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create attachments + attachment1 = f.IssueAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + attachment2 = f.IssueAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 2 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Update attachment + attachment1.description = "new attachment description" + attachment1.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 1 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Delete attachment + attachment2.delete() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 1 + + +def test_webhooks_when_update_issue_custom_attributes(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.IssueFactory.create(project=project) + + custom_attr_1 = f.IssueCustomAttributeFactory(project=obj.project) + ct1_id = "{}".format(custom_attr_1.id) + custom_attr_2 = f.IssueCustomAttributeFactory(project=obj.project) + ct2_id = "{}".format(custom_attr_2.id) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create custom attributes + obj.custom_attributes_values.attributes_values = { + ct1_id: "test_1_updated", + ct2_id: "test_2_updated" + } + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 2 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 0 + + # Update custom attributes + obj.custom_attributes_values.attributes_values[ct1_id] = "test_2_updated" + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 1 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 0 + + # Delete custom attributes + del obj.custom_attributes_values.attributes_values[ct1_id] + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "issue" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 1 diff --git a/tests/integration/test_webhooks_milestones.py b/tests/integration/test_webhooks_milestones.py new file mode 100644 index 00000000..f720df2f --- /dev/null +++ b/tests/integration/test_webhooks_milestones.py @@ -0,0 +1,101 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Anler Hernández +# 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 . + +import pytest +from unittest.mock import patch +from unittest.mock import Mock + +from .. import factories as f + +from taiga.projects.history import services + + +pytestmark = pytest.mark.django_db(transaction=True) + + +from taiga.base.utils import json + +def test_webhooks_when_create_milestone(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.MilestoneFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "create" + assert data["type"] == "milestone" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + + +def test_webhooks_when_update_milestone(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.MilestoneFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + obj.name = "test webhook update" + obj.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "milestone" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["data"]["name"] == obj.name + assert data["change"]["comment"] == "test_comment" + assert data["change"]["diff"]["name"]["to"] == data["data"]["name"] + assert data["change"]["diff"]["name"]["from"] != data["data"]["name"] + + +def test_webhooks_when_delete_milestone(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.MilestoneFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, delete=True) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "delete" + assert data["type"] == "milestone" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert "data" in data diff --git a/tests/integration/test_webhooks.py b/tests/integration/test_webhooks_signals.py similarity index 59% rename from tests/integration/test_webhooks.py rename to tests/integration/test_webhooks_signals.py index 83ee4b28..cf9996ca 100644 --- a/tests/integration/test_webhooks.py +++ b/tests/integration/test_webhooks_signals.py @@ -18,6 +18,7 @@ import pytest from unittest.mock import patch +from unittest.mock import Mock from .. import factories as f @@ -26,7 +27,7 @@ from taiga.projects.history import services pytestmark = pytest.mark.django_db(transaction=True) -def test_new_object_with_one_webhook(settings): +def test_new_object_with_one_webhook_signal(settings): settings.WEBHOOKS_ENABLED = True project = f.ProjectFactory() f.WebhookFactory.create(project=project) @@ -38,28 +39,31 @@ def test_new_object_with_one_webhook(settings): f.WikiPageFactory.create(project=project) ] - for obj in objects: - with patch('taiga.webhooks.tasks.create_webhook') as create_webhook_mock: - services.take_snapshot(obj, user=obj.owner, comment="test") - assert create_webhook_mock.call_count == 1 + response = Mock(status_code=200, headers={}, content="ok") + response.elapsed.total_seconds.return_value = 100 for obj in objects: - with patch('taiga.webhooks.tasks.change_webhook') as change_webhook_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: + services.take_snapshot(obj, user=obj.owner, comment="test") + assert session_send_mock.call_count == 1 + + for obj in objects: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner) - assert change_webhook_mock.call_count == 0 + assert session_send_mock.call_count == 0 for obj in objects: - with patch('taiga.webhooks.tasks.change_webhook') as change_webhook_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner, comment="test") - assert change_webhook_mock.call_count == 1 + assert session_send_mock.call_count == 1 for obj in objects: - with patch('taiga.webhooks.tasks.delete_webhook') as delete_webhook_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner, comment="test", delete=True) - assert delete_webhook_mock.call_count == 1 + assert session_send_mock.call_count == 1 -def test_new_object_with_two_webhook(settings): +def test_new_object_with_two_webhook_signals(settings): settings.WEBHOOKS_ENABLED = True project = f.ProjectFactory() f.WebhookFactory.create(project=project) @@ -72,28 +76,31 @@ def test_new_object_with_two_webhook(settings): f.WikiPageFactory.create(project=project) ] - for obj in objects: - with patch('taiga.webhooks.tasks.create_webhook') as create_webhook_mock: - services.take_snapshot(obj, user=obj.owner, comment="test") - assert create_webhook_mock.call_count == 2 + response = Mock(status_code=200, headers={}, content="ok") + response.elapsed.total_seconds.return_value = 100 for obj in objects: - with patch('taiga.webhooks.tasks.change_webhook') as change_webhook_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner, comment="test") - assert change_webhook_mock.call_count == 2 + assert session_send_mock.call_count == 2 for obj in objects: - with patch('taiga.webhooks.tasks.change_webhook') as change_webhook_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: + services.take_snapshot(obj, user=obj.owner, comment="test") + assert session_send_mock.call_count == 2 + + for obj in objects: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner) - assert change_webhook_mock.call_count == 0 + assert session_send_mock.call_count == 0 for obj in objects: - with patch('taiga.webhooks.tasks.delete_webhook') as delete_webhook_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner, comment="test", delete=True) - assert delete_webhook_mock.call_count == 2 + assert session_send_mock.call_count == 2 -def test_send_request_one_webhook(settings): +def test_send_request_one_webhook_signal(settings): settings.WEBHOOKS_ENABLED = True project = f.ProjectFactory() f.WebhookFactory.create(project=project) @@ -105,12 +112,15 @@ def test_send_request_one_webhook(settings): f.WikiPageFactory.create(project=project) ] - for obj in objects: - with patch('taiga.webhooks.tasks._send_request') as _send_request_mock: - services.take_snapshot(obj, user=obj.owner, comment="test") - assert _send_request_mock.call_count == 1 + response = Mock(status_code=200, headers={}, content="ok") + response.elapsed.total_seconds.return_value = 100 for obj in objects: - with patch('taiga.webhooks.tasks._send_request') as _send_request_mock: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: + services.take_snapshot(obj, user=obj.owner, comment="test") + assert session_send_mock.call_count == 1 + + for obj in objects: + with patch("taiga.webhooks.tasks.requests.Session.send", return_value=response) as session_send_mock: services.take_snapshot(obj, user=obj.owner, comment="test", delete=True) - assert _send_request_mock.call_count == 1 + assert session_send_mock.call_count == 1 diff --git a/tests/integration/test_webhooks_tasks.py b/tests/integration/test_webhooks_tasks.py new file mode 100644 index 00000000..ba5eda8d --- /dev/null +++ b/tests/integration/test_webhooks_tasks.py @@ -0,0 +1,248 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Anler Hernández +# 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 . + +import pytest +from unittest.mock import patch +from unittest.mock import Mock + +from .. import factories as f + +from taiga.projects.history import services + + +pytestmark = pytest.mark.django_db(transaction=True) + + +from taiga.base.utils import json + +def test_webhooks_when_create_task(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.TaskFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "create" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + + +def test_webhooks_when_update_task(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.TaskFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + obj.subject = "test webhook update" + obj.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["data"]["subject"] == obj.subject + assert data["change"]["comment"] == "test_comment" + assert data["change"]["diff"]["subject"]["to"] == data["data"]["subject"] + assert data["change"]["diff"]["subject"]["from"] != data["data"]["subject"] + + +def test_webhooks_when_delete_task(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.TaskFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, delete=True) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "delete" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert "data" in data + + +def test_webhooks_when_update_task_attachments(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.TaskFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create attachments + attachment1 = f.TaskAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + attachment2 = f.TaskAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 2 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Update attachment + attachment1.description = "new attachment description" + attachment1.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 1 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Delete attachment + attachment2.delete() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 1 + + +def test_webhooks_when_update_task_custom_attributes(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.TaskFactory.create(project=project) + + custom_attr_1 = f.TaskCustomAttributeFactory(project=obj.project) + ct1_id = "{}".format(custom_attr_1.id) + custom_attr_2 = f.TaskCustomAttributeFactory(project=obj.project) + ct2_id = "{}".format(custom_attr_2.id) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create custom attributes + obj.custom_attributes_values.attributes_values = { + ct1_id: "test_1_updated", + ct2_id: "test_2_updated" + } + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 2 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 0 + + # Update custom attributes + obj.custom_attributes_values.attributes_values[ct1_id] = "test_2_updated" + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 1 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 0 + + # Delete custom attributes + del obj.custom_attributes_values.attributes_values[ct1_id] + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "task" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 1 diff --git a/tests/integration/test_webhooks_userstories.py b/tests/integration/test_webhooks_userstories.py new file mode 100644 index 00000000..716697ce --- /dev/null +++ b/tests/integration/test_webhooks_userstories.py @@ -0,0 +1,308 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Anler Hernández +# 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 . + +import pytest +from unittest.mock import patch +from unittest.mock import Mock + +from .. import factories as f + +from taiga.projects.history import services + + +pytestmark = pytest.mark.django_db(transaction=True) + + +from taiga.base.utils import json + +def test_webhooks_when_create_user_story(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.UserStoryFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "create" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + + +def test_webhooks_when_update_user_story(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.UserStoryFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + obj.subject = "test webhook update" + obj.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["data"]["subject"] == obj.subject + assert data["change"]["comment"] == "test_comment" + assert data["change"]["diff"]["subject"]["to"] == data["data"]["subject"] + assert data["change"]["diff"]["subject"]["from"] != data["data"]["subject"] + + +def test_webhooks_when_delete_user_story(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.UserStoryFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, delete=True) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "delete" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert "data" in data + + +def test_webhooks_when_update_user_story_attachments(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.UserStoryFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create attachments + attachment1 = f.UserStoryAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + attachment2 = f.UserStoryAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 2 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Update attachment + attachment1.description = "new attachment description" + attachment1.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 1 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Delete attachment + attachment2.delete() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 1 + + +def test_webhooks_when_update_user_story_custom_attributes(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.UserStoryFactory.create(project=project) + + custom_attr_1 = f.UserStoryCustomAttributeFactory(project=obj.project) + ct1_id = "{}".format(custom_attr_1.id) + custom_attr_2 = f.UserStoryCustomAttributeFactory(project=obj.project) + ct2_id = "{}".format(custom_attr_2.id) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create custom attributes + obj.custom_attributes_values.attributes_values = { + ct1_id: "test_1_updated", + ct2_id: "test_2_updated" + } + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 2 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 0 + + # Update custom attributes + obj.custom_attributes_values.attributes_values[ct1_id] = "test_2_updated" + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 1 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 0 + + # Delete custom attributes + del obj.custom_attributes_values.attributes_values[ct1_id] + obj.custom_attributes_values.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["custom_attributes"]["new"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["changed"]) == 0 + assert len(data["change"]["diff"]["custom_attributes"]["deleted"]) == 1 + + +def test_webhooks_when_update_user_story_points(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + role1 = f.RoleFactory.create(project=project) + role2 = f.RoleFactory.create(project=project) + + points1 = f.PointsFactory.create(project=project, value=None) + points2 = f.PointsFactory.create(project=project, value=1) + points3 = f.PointsFactory.create(project=project, value=2) + + obj = f.UserStoryFactory.create(project=project) + obj.role_points.all().delete() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Set points + f.RolePointsFactory.create(user_story=obj, role=role1, points=points1) + f.RolePointsFactory.create(user_story=obj, role=role2, points=points2) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "" + assert data["change"]["diff"]["points"][role1.name]["from"] == None + assert data["change"]["diff"]["points"][role1.name]["to"] == points1.name + assert data["change"]["diff"]["points"][role2.name]["from"] == None + assert data["change"]["diff"]["points"][role2.name]["to"] == points2.name + + # Change points + obj.role_points.all().update(points=points3) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "userstory" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "" + assert data["change"]["diff"]["points"][role1.name]["from"] == points1.name + assert data["change"]["diff"]["points"][role1.name]["to"] == points3.name + assert data["change"]["diff"]["points"][role2.name]["from"] == points2.name + assert data["change"]["diff"]["points"][role2.name]["to"] == points3.name diff --git a/tests/integration/test_webhooks_wikipages.py b/tests/integration/test_webhooks_wikipages.py new file mode 100644 index 00000000..5d10f233 --- /dev/null +++ b/tests/integration/test_webhooks_wikipages.py @@ -0,0 +1,170 @@ +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Anler Hernández +# 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 . + +import pytest +from unittest.mock import patch +from unittest.mock import Mock + +from .. import factories as f + +from taiga.projects.history import services + + +pytestmark = pytest.mark.django_db(transaction=True) + + +from taiga.base.utils import json + +def test_webhooks_when_create_wiki_page(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.WikiPageFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "create" + assert data["type"] == "wikipage" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + + +def test_webhooks_when_update_wiki_page(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.WikiPageFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + obj.content = "test webhook update" + obj.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "wikipage" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["data"]["content"] == obj.content + assert data["change"]["comment"] == "test_comment" + assert data["change"]["diff"]["content_html"]["from"] != data["change"]["diff"]["content_html"]["to"] + assert obj.content in data["change"]["diff"]["content_html"]["to"] + + +def test_webhooks_when_delete_wiki_page(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.WikiPageFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, delete=True) + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "delete" + assert data["type"] == "wikipage" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert "data" in data + + +def test_webhooks_when_update_wiki_page_attachments(settings): + settings.WEBHOOKS_ENABLED = True + project = f.ProjectFactory() + f.WebhookFactory.create(project=project) + f.WebhookFactory.create(project=project) + + obj = f.WikiPageFactory.create(project=project) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner) + assert send_request_mock.call_count == 2 + + # Create attachments + attachment1 = f.WikiAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + attachment2 = f.WikiAttachmentFactory(project=obj.project, content_object=obj, owner=obj.owner) + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "wikipage" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 2 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Update attachment + attachment1.description = "new attachment description" + attachment1.save() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "wikipage" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 1 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 0 + + # Delete attachment + attachment2.delete() + + with patch('taiga.webhooks.tasks._send_request') as send_request_mock: + services.take_snapshot(obj, user=obj.owner, comment="test_comment") + assert send_request_mock.call_count == 2 + + (webhook_id, url, key, data) = send_request_mock.call_args[0] + assert data["action"] == "change" + assert data["type"] == "wikipage" + assert data["by"]["id"] == obj.owner.id + assert "date" in data + assert data["data"]["id"] == obj.id + assert data["change"]["comment"] == "test_comment" + assert len(data["change"]["diff"]["attachments"]["new"]) == 0 + assert len(data["change"]["diff"]["attachments"]["changed"]) == 0 + assert len(data["change"]["diff"]["attachments"]["deleted"]) == 1 diff --git a/tests/unit/test_export.py b/tests/unit/test_export.py index d80103a3..6a4a3ff0 100644 --- a/tests/unit/test_export.py +++ b/tests/unit/test_export.py @@ -20,7 +20,7 @@ import io from .. import factories as f from taiga.base.utils import json -from taiga.export_import.service import render_project +from taiga.export_import.services import render_project pytestmark = pytest.mark.django_db