diff --git a/.gitignore b/.gitignore index 7cf4ac96..bfeb4264 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.log taiga/search settings/local.py +settings/celery_local.py database.sqlite logs media diff --git a/.travis.yml b/.travis.yml index ac145daa..dfa1521c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,15 +2,17 @@ language: python python: - "3.4" - "3.5" +addons: + postgresql: "9.4" services: - - rabbitmq # will start rabbitmq-server + - rabbitmq + - postgresql cache: - apt - pip before_install: - sudo apt-get -qq update - sudo /etc/init.d/postgresql stop - - sudo apt-get install -y postgresql-9.4 - sudo apt-get install -y postgresql-plpython-9.4 - sudo /etc/init.d/postgresql start 9.4 - psql -c 'create database taiga;' -U postgres diff --git a/CHANGELOG.md b/CHANGELOG.md index 108833e9..12de9155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,35 @@ # Changelog # +## 3.1.0 Perovskia Atriplicifolia (2017-03-10) + +### Features +- Contact with the project: if the projects have this module enabled Taiga users can contact them. +- Ability to create rich text custom fields in Epics, User Stories, Tasks and Isues. +- Full text search now use simple as tokenizer so search with non-english text are allowed. +- Duplicate project: allows creating a new project based on the structure of another (status, tags, colors, default values...) +- Add thumbnails and preview for PSD files. +- Add thumbnails and preview for SVG files (Cario lib is needed). +- i18n: + - Add japanese (ja) translation. + - Add korean (ko) translation. + - Add chinese simplified (zh-Hans) translation. +- Third party services project importers: + - Trello + - Jira 7 + - Github + - Asana + +### Misc +- API: + - Memberships API endpoints now allows using usernames and emails instead of using only emails. + - Contacts API allow full text search (by the username, full name or email). + - Filter milestones, user stories and tasks by estimated_start and estimated_finish dates. + - Add project_extra_info to epics, tasks, milestones, issues and wiki pages endpoints. +- Gogs integration: Adding new Gogs signature method. +- Lots of small and not so small bugfixes. + + ## 3.0.0 Stellaria Borealis (2016-10-02) ### Features diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..068b5851 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ +**Before adding an issue** +Please ensure that your issue is not already reported in our [issues list](https://tree.taiga.io/project/taiga/issues?order_by=-created_date). +If this issue was already reported, remember that you can upvote it to raise its importance. + +**Do you want to request a *feature* or report a *bug*?** + +**What is the current behavior?** + +**If the current behavior is a bug, please provide the steps to reproduce.** + +**What is the expected behavior?** + +**Is it happening in taiga.io or in your own instance?** + +**What browser/version are you using?** + +**Are there any console errors *(Ctrl + F12)* in red?** + +Thanks for reporting! diff --git a/README.md b/README.md index cfd015fb..f5fb30fe 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # Taiga Backend # -![Kaleidos Project](http://kaleidos.net/static/img/badge.png "Kaleidos Project") -[![Managed with Taiga.io](https://taiga.io/media/support/attachments/article-22/banner-gh.png)](https://taiga.io "Managed with Taiga.io") -[![Build Status](https://travis-ci.org/taigaio/taiga-back.svg?branch=master)](https://travis-ci.org/taigaio/taiga-back "Build Status") -[![Coverage Status](https://coveralls.io/repos/taigaio/taiga-back/badge.svg?branch=master)](https://coveralls.io/r/taigaio/taiga-back?branch=master "Coverage Status") -[![Dependency Status](https://www.versioneye.com/user/projects/561bd091a193340f32001464/badge.svg?style=flat)](https://www.versioneye.com/user/projects/561bd091a193340f32001464) +![Kaleidos Project](http://kaleidos.net/static/img/badge.svg "Kaleidos Project") +[![Managed with Taiga.io](https://img.shields.io/badge/managed%20with-TAIGA.io-709f14.svg)](https://tree.taiga.io/project/taiga/ "Managed with Taiga.io") +[![Build Status](https://img.shields.io/travis/taigaio/taiga-back.svg)](https://travis-ci.org/taigaio/taiga-back "Build Status") +[![Coverage Status](https://img.shields.io/coveralls/taigaio/taiga-back/master.svg)](https://coveralls.io/r/taigaio/taiga-back?branch=master "Coverage Status") ## Contribute to Taiga ## @@ -64,7 +63,7 @@ Currently, we have authored three main documentation hubs: #### Translation #### -We are ready now to accept your help translating Taiga. It's easy (and fun!) just access our team of translators with the link below, set up an account in Transifex and start contributing. Join us to make sure your language is covered! **[Help Taiga to translate content](https://www.transifex.com/signup/ "Help Taiga to trasnlatecontent")** +We are ready now to accept your help translating Taiga. It's easy (and fun!) just access our team of translators with the link below, set up an account in Transifex and start contributing. Join us to make sure your language is covered! **[Help Taiga to translate content](https://www.transifex.com/taiga-agile-llc/taiga-back/ "Help Taiga to trasnlatecontent")** #### Code patches #### @@ -80,7 +79,7 @@ Taiga is made for developers and designers. We care enormously about UI because There are two possible ways to contribute to our UI: - **Bugs**: If you find a bug regarding front-end, please report it as previously indicated in the Bug reports section or send a pull-request as indicated in the Code Patches section. -- **Enhancements**: If its a design or UX bug or enhancement we will love to receive your feedback. Please send us your enhancement, with the reason and, if possible, an example. Our design and UX team will review your enhancement and fix it as soon as possible. We recommend you to use our [mailing list](http://groups.google.co.uk/d/forum/taigaio){target="_blank"} so we can have a lot of different opinions and debate. +- **Enhancements**: If its a design or UX bug or enhancement we will love to receive your feedback. Please send us your enhancement, with the reason and, if possible, an example. Our design and UX team will review your enhancement and fix it as soon as possible. We recommend you to use our [mailing list](http://groups.google.co.uk/d/forum/taigaio) so we can have a lot of different opinions and debate. - **Language Localization**: We are eager to offer localized versions of Taiga. Some members of the community have already volunteered to work to provide a variety of languages. We are working to implement some changes to allow for this and expect to accept these requests in the near future. diff --git a/manage.py b/manage.py index 63a6358b..9a451d45 100755 --- a/manage.py +++ b/manage.py @@ -1,10 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/requirements-devel.txt b/requirements-devel.txt index f2c28517..ac4f42fe 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,13 +1,13 @@ -r requirements.txt factory_boy==2.8.1 -py==1.4.31 -pytest==2.8.7 -pytest-django==2.9.1 -pytest-pythonpath==0.7 +py==1.4.32 +pytest==3.0.6 +pytest-django==3.1.2 +pytest-pythonpath==0.7.1 -coverage==4.0.3 +coverage==4.3.4 coveralls==1.1 django-slowdown==0.0.1 -transifex-client==0.11.1.beta +transifex-client==0.12.4 diff --git a/requirements.txt b/requirements.txt index 46b90b60..517d0b99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,38 +1,42 @@ -Django==1.9.2 +Django==1.10.6 #djangorestframework==2.3.13 # It's not necessary since Taiga 1.7 django-picklefield==0.3.2 -django-sampledatahelper==0.4.0 -gunicorn==19.4.5 -psycopg2==2.6.1 -Pillow==3.1.1 -pytz==2015.7 +django-sampledatahelper==0.4.1 +gunicorn==19.6.0 +psycopg2==2.7 +Pillow==3.4.2 +pytz==2016.10 six==1.10.0 -amqp==1.4.9 -djmail==0.12.0.post1 -django-pgjson==0.3.1 -djorm-pgarray==1.2 # Use until Taiga 2.1. Keep compatibility with old migrations -django-jinja==2.1.2 -jinja2==2.8 -pygments==2.0.2 +amqp==2.1.4 +djmail==1.0.0 +django-jinja==2.2.2 +jinja2==2.9.5 +pygments==2.2.0 django-sites==0.9 -Markdown==2.6.5 +Markdown==2.6.8 fn==0.4.3 diff-match-patch==20121119 -requests==2.9.1 +requests==2.13.0 +requests-oauthlib==0.8.0 +webcolors==1.7 django-sr==0.0.4 easy-thumbnails==2.3 -celery==3.1.20 +celery==4.0.2 redis==2.10.5 -Unidecode==0.04.19 -raven==5.10.2 -bleach==1.4.3 -django-ipware==1.1.3 -premailer==2.9.7 +Unidecode==0.4.20 +raven==6.0.0 +bleach==1.5.0 +django-ipware==1.1.6 +premailer==3.0.1 cssutils==1.0.1 # Compatible with python 3.5 -lxml==3.5.0 -git+https://github.com/Xof/django-pglocks.git@dbb8d7375066859f897604132bd437832d2014ea -pyjwkest==1.1.5 -python-dateutil==2.4.2 -netaddr==0.7.18 +lxml==3.7.3 +git+https://github.com/Xof/django-pglocks.git +pyjwkest==1.3.2 +python-dateutil==2.6.0 +netaddr==0.7.19 serpy==0.1.1 psd-tools==1.4 +CairoSVG==2.0.1 +cryptography==1.7.1 +PyJWT==1.4.2 +asana==0.6.2 diff --git a/scripts/manage_translations.py b/scripts/manage_translations.py old mode 100644 new mode 100755 index c7a2d1ff..20ad70b1 --- a/scripts/manage_translations.py +++ b/scripts/manage_translations.py @@ -246,7 +246,7 @@ You need transifex-client, install it. or - $ pip install --upgrade transifex-client==0.11.1.beta + $ pip install --upgrade transifex-client==0.12.2 2. Create ~/.transifexrc file: diff --git a/settings/__init__.py b/settings/__init__.py index a5fbfb3e..a434dd74 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/settings/celery.py b/settings/celery.py index 82e4d92f..b22c7167 100644 --- a/settings/celery.py +++ b/settings/celery.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -16,19 +16,22 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from kombu import Exchange, Queue +from kombu import Queue -BROKER_URL = 'amqp://guest:guest@localhost:5672//' -CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' +broker_url = 'amqp://guest:guest@localhost:5672//' +result_backend = 'redis://localhost:6379/0' -CELERY_TIMEZONE = 'Europe/Madrid' -CELERY_ENABLE_UTC = True +accept_content = ['pickle',] # Values are 'pickle', 'json', 'msgpack' and 'yaml' +task_serializer = "pickle" +result_serializer = "pickle" -CELERY_DEFAULT_QUEUE = 'tasks' -CELERY_QUEUES = ( +timezone = 'Europe/Madrid' + +task_default_queue = 'tasks' +task_queues = ( Queue('tasks', routing_key='task.#'), Queue('transient', routing_key='transient.#', delivery_mode=1) ) -CELERY_DEFAULT_EXCHANGE = 'tasks' -CELERY_DEFAULT_EXCHANGE_TYPE = 'topic' -CELERY_DEFAULT_ROUTING_KEY = 'task.default' +task_default_exchange = 'tasks' +task_default_exchange_type = 'topic' +task_default_routing_key = 'task.default' diff --git a/settings/celery_local.py.example b/settings/celery_local.py.example new file mode 100644 index 00000000..a16e5ec3 --- /dev/null +++ b/settings/celery_local.py.example @@ -0,0 +1,4 @@ +from .celery import * + +# To use celery in memory +#task_always_eager = True diff --git a/settings/common.py b/settings/common.py index f72c344f..ed6b3939 100644 --- a/settings/common.py +++ b/settings/common.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -110,12 +110,12 @@ LANGUAGES = [ #("io", "IDO"), # Ido #("is", "Íslenska"), # Icelandic ("it", "Italiano"), # Italian - #("ja", "日本語"), # Japanese + ("ja", "日本語"), # Japanese #("ka", "ქართული"), # Georgian #("kk", "Қазақша"), # Kazakh #("km", "ភាសាខ្មែរ"), # Khmer #("kn", "ಕನ್ನಡ"), # Kannada - #("ko", "한국어"), # Korean + ("ko", "한국어"), # Korean #("lb", "Lëtzebuergesch"), # Luxembourgish #("lt", "Lietuvių"), # Lithuanian #("lv", "Latviešu"), # Latvian @@ -151,7 +151,7 @@ LANGUAGES = [ #("uk", "Українська"), # Ukrainian #("ur", "اردو‏"), # Urdu #("vi", "Tiếng Việt"), # Vietnamese - #("zh-hans", "中文(简体)"), # Simplified Chinese + ("zh-hans", "中文(简体)"), # Simplified Chinese ("zh-hant", "中文(香港)"), # Traditional Chinese ] @@ -282,6 +282,7 @@ INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.staticfiles", "django.contrib.sitemaps", + "django.contrib.postgres", "taiga.base", "taiga.base.api", @@ -305,6 +306,7 @@ INSTALLED_APPS = [ "taiga.projects.tasks", "taiga.projects.issues", "taiga.projects.wiki", + "taiga.projects.contact", "taiga.searches", "taiga.timeline", "taiga.mdrender", @@ -316,6 +318,7 @@ INSTALLED_APPS = [ "taiga.hooks.bitbucket", "taiga.hooks.gogs", "taiga.webhooks", + "taiga.importers", "djmail", "django_jinja", @@ -345,6 +348,10 @@ LOGGING = { "null": { "format": "%(message)s", }, + "django.server": { + "()": "django.utils.log.ServerFormatter", + "format": "[%(server_time)s] %(message)s", + }, }, "handlers": { "null": { @@ -360,7 +367,12 @@ LOGGING = { "level": "ERROR", "filters": ["require_debug_false"], "class": "django.utils.log.AdminEmailHandler", - } + }, + "django.server": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "django.server", + }, }, "loggers": { "django": { @@ -382,6 +394,11 @@ LOGGING = { "handlers": ["console"], "level": "DEBUG", "propagate": False, + }, + "django.server": { + "handlers": ["django.server"], + "level": "INFO", + "propagate": False, } } } @@ -416,16 +433,21 @@ REST_FRAMEWORK = { "taiga.external_apps.auth_backends.Token", ), "DEFAULT_THROTTLE_CLASSES": ( - "taiga.base.throttling.AnonRateThrottle", - "taiga.base.throttling.UserRateThrottle" + "taiga.base.throttling.CommonThrottle", ), "DEFAULT_THROTTLE_RATES": { - "anon": None, - "user": None, + "anon-write": None, + "user-write": None, + "anon-read": None, + "user-read": None, "import-mode": None, "import-dump-mode": "1/minute", - "create-memberships": None + "create-memberships": None, + "login-fail": None, + "register-success": None, + "user-detail": None, }, + "DEFAULT_THROTTLE_WHITELIST": [], "FILTER_BACKEND": "taiga.base.filters.FilterBackend", "EXCEPTION_HANDLER": "taiga.base.exceptions.exception_handler", "PAGINATE_BY": 30, @@ -455,8 +477,6 @@ SOUTH_MIGRATION_MODULES = { } - - THN_AVATAR_SIZE = 80 # 80x80 pixels THN_AVATAR_BIG_SIZE = 300 # 300x300 pixels THN_LOGO_SMALL_SIZE = 80 # 80x80 pixels @@ -464,6 +484,7 @@ THN_LOGO_BIG_SIZE = 300 # 300x300 pixels THN_TIMELINE_IMAGE_SIZE = 640 # 640x??? pixels THN_CARD_IMAGE_WIDTH = 300 # 300 pixels THN_CARD_IMAGE_HEIGHT = 200 # 200 pixels +THN_PREVIEW_IMAGE_WIDTH = 800 # 800 pixels THN_AVATAR_SMALL = "avatar" THN_AVATAR_BIG = "big-avatar" @@ -471,6 +492,7 @@ THN_LOGO_SMALL = "logo-small" THN_LOGO_BIG = "logo-big" THN_ATTACHMENT_TIMELINE = "timeline-image" THN_ATTACHMENT_CARD = "card-image" +THN_ATTACHMENT_PREVIEW = "preview-image" THUMBNAIL_ALIASES = { "": { @@ -480,6 +502,7 @@ THUMBNAIL_ALIASES = { THN_LOGO_BIG: {"size": (THN_LOGO_BIG_SIZE, THN_LOGO_BIG_SIZE), "crop": True}, THN_ATTACHMENT_TIMELINE: {"size": (THN_TIMELINE_IMAGE_SIZE, 0), "crop": True}, THN_ATTACHMENT_CARD: {"size": (THN_CARD_IMAGE_WIDTH, THN_CARD_IMAGE_HEIGHT), "crop": True}, + THN_ATTACHMENT_PREVIEW: {"size": (THN_PREVIEW_IMAGE_WIDTH, 0), "crop": False}, }, } @@ -538,6 +561,30 @@ MAX_PENDING_MEMBERSHIPS = 30 # Max number of unconfirmed memberships in a projec from .sr import * +IMPORTERS = { + "github": { + "active": False, + "client_id": "", + "client_secret": "", + }, + "trello": { + "active": False, + "api_key": "", + "secret_key": "", + }, + "jira": { + "active": False, + "consumer_key": "", + "cert": "", + "pub_cert": "", + }, + "asana": { + "active": False, + "callback_url": "", + "app_id": "", + "app_secret": "", + } +} # NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE TEST_RUNNER="django.test.runner.DiscoverRunner" diff --git a/settings/development.py b/settings/development.py index 8611719d..54df8eef 100644 --- a/settings/development.py +++ b/settings/development.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/settings/local.py.example b/settings/local.py.example index 7defff37..626d4911 100644 --- a/settings/local.py.example +++ b/settings/local.py.example @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -63,12 +63,23 @@ DATABASES = { ######################################### #REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = { -# "anon": "20/min", -# "user": "200/min", -# "import-mode": "20/sec", -# "import-dump-mode": "1/minute" +# "anon-write": "20/min", +# "user-write": None, +# "anon-read": None, +# "user-read": None, +# "import-mode": None, +# "import-dump-mode": "1/minute", +# "create-memberships": None, +# "login-fail": None, +# "register-success": None, +# "user-detail": None, #} +# This list should containt: +# - Tiga users IDs +# - Valid clients IP addresses (X-Forwarded-For header) +#REST_FRAMEWORK["DEFAULT_THROTTLE_WHITELIST"] = [] + ######################################### ## MAIL SYSTEM SETTINGS @@ -146,10 +157,47 @@ DATABASES = { ######################################### ## CELERY ######################################### +# Set to True to enable celery and work in async mode or False +# to disable it and work in sync mode. You can find the celery +# settings in settings/celery.py and settings/celery-local.py +#CELERY_ENABLED = True -#from .celery import * -#CELERY_ENABLED = True -# -# To use celery in memory -#CELERY_ENABLED = True -#CELERY_ALWAYS_EAGER = True + +######################################### +## IMPORTERS +######################################### + +# Configuration for the GitHub importer +# Remember to enable it in the front client too. +#IMPORTERS["github"] = { +# "active": True, # Enable or disable the importer +# "client_id": "XXXXXX_get_a_valid_client_id_from_github_XXXXXX", +# "client_secret": "XXXXXX_get_a_valid_client_secret_from_github_XXXXXX" +#} + +# Configuration for the Trello importer +# Remember to enable it in the front client too. +#IMPORTERS["trello"] = { +# "active": True, # Enable or disable the importer +# "api_key": "XXXXXX_get_a_valid_api_key_from_trello_XXXXXX", +# "secret_key": "XXXXXX_get_a_valid_secret_key_from_trello_XXXXXX" +#} + +# Configuration for the Jira importer +# Remember to enable it in the front client too. +#IMPORTERS["jira"] = { +# "active": True, # Enable or disable the importer +# "consumer_key": "XXXXXX_get_a_valid_consumer_key_from_jira_XXXXXX", +# "cert": "XXXXXX_get_a_valid_cert_from_jira_XXXXXX", +# "pub_cert": "XXXXXX_get_a_valid_pub_cert_from_jira_XXXXXX" +#} + +# Configuration for the Asane importer +# Remember to enable it in the front client too. +#IMPORTERS["asana"] = { +# "active": True, # Enable or disable the importer +# "callback_url": "{}://{}/project/new/import/asana".format(SITES["front"]["scheme"], +# SITES["front"]["domain"]), +# "app_id": "XXXXXX_get_a_valid_app_id_from_asana_XXXXXX", +# "app_secret": "XXXXXX_get_a_valid_app_secret_from_asana_XXXXXX" +#} diff --git a/settings/sr.py b/settings/sr.py index 1477e682..8139a498 100644 --- a/settings/sr.py +++ b/settings/sr.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/settings/testing.py b/settings/testing.py index c8875026..da641cf2 100644 --- a/settings/testing.py +++ b/settings/testing.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,7 +19,6 @@ from .development import * CELERY_ENABLED = False -CELERY_ALWAYS_EAGER = True MEDIA_ROOT = "/tmp" @@ -29,9 +28,20 @@ INSTALLED_APPS = INSTALLED_APPS + [ ] REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = { - "anon": None, - "user": None, + "anon-write": None, + "anon-read": None, + "user-write": None, + "user-read": None, "import-mode": None, "import-dump-mode": None, "create-memberships": None, + "login-fail": None, + "register-success": None, + "user-detail": None, } + + +IMPORTERS['github']['active'] = True +IMPORTERS['jira']['active'] = True +IMPORTERS['asana']['active'] = True +IMPORTERS['trello']['active'] = True diff --git a/settings/travis.py b/settings/travis.py index 13c0f9c2..96ad48bf 100644 --- a/settings/travis.py +++ b/settings/travis.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/__init__.py b/taiga/__init__.py index 8be9bc25..ccc27561 100644 --- a/taiga/__init__.py +++ b/taiga/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/auth/api.py b/taiga/auth/api.py index df077b52..6370f96a 100644 --- a/taiga/auth/api.py +++ b/taiga/auth/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -30,16 +30,16 @@ from taiga.base import exceptions as exc from taiga.base import response from .validators import PublicRegisterValidator -from .validators import PrivateRegisterForExistingUserValidator -from .validators import PrivateRegisterForNewUserValidator +from .validators import PrivateRegisterValidator -from .services import private_register_for_existing_user from .services import private_register_for_new_user from .services import public_register from .services import make_auth_response_data from .services import get_auth_plugins +from .services import accept_invitation_by_existing_user from .permissions import AuthPermission +from .throttling import LoginFailRateThrottle, RegisterSuccessRateThrottle def _parse_data(data:dict, *, cls): @@ -61,41 +61,13 @@ def _parse_data(data:dict, *, cls): # Parse public register data parse_public_register_data = partial(_parse_data, cls=PublicRegisterValidator) -# Parse private register data for existing user -parse_private_register_for_existing_user_data = \ - partial(_parse_data, cls=PrivateRegisterForExistingUserValidator) - # Parse private register data for new user -parse_private_register_for_new_user_data = \ - partial(_parse_data, cls=PrivateRegisterForNewUserValidator) - - -class RegisterTypeEnum(Enum): - new_user = 1 - existing_user = 2 - - -def parse_register_type(userdata:dict) -> str: - """ - Parses user data and detects that register type is. - It returns RegisterTypeEnum value. - """ - # Create adhoc inner serializer for avoid parse - # manually the user data. - class _validator(validators.Validator): - existing = serializers.BooleanField() - - instance = _validator(data=userdata) - if not instance.is_valid(): - raise exc.RequestValidationError(instance.errors) - - if instance.data["existing"]: - return RegisterTypeEnum.existing_user - return RegisterTypeEnum.new_user +parse_private_register_data = partial(_parse_data, cls=PrivateRegisterValidator) class AuthViewSet(viewsets.ViewSet): permission_classes = (AuthPermission,) + throttle_classes = (LoginFailRateThrottle, RegisterSuccessRateThrottle) def _public_register(self, request): if not settings.PUBLIC_REGISTER_ENABLED: @@ -111,14 +83,8 @@ class AuthViewSet(viewsets.ViewSet): return response.Created(data) def _private_register(self, request): - register_type = parse_register_type(request.DATA) - - if register_type is RegisterTypeEnum.existing_user: - data = parse_private_register_for_existing_user_data(request.DATA) - user = private_register_for_existing_user(**data) - else: - data = parse_private_register_for_new_user_data(request.DATA) - user = private_register_for_new_user(**data) + data = parse_private_register_data(request.DATA) + user = private_register_for_new_user(**data) data = make_auth_response_data(user) return response.Created(data) @@ -140,9 +106,12 @@ class AuthViewSet(viewsets.ViewSet): auth_plugins = get_auth_plugins() login_type = request.DATA.get("type", None) + invitation_token = request.DATA.get("invitation_token", None) if login_type in auth_plugins: data = auth_plugins[login_type]['login_func'](request) + if invitation_token: + accept_invitation_by_existing_user(invitation_token, data['id']) return response.Ok(data) raise exc.BadRequest(_("invalid login type")) diff --git a/taiga/auth/backends.py b/taiga/auth/backends.py index 7813d457..8ed19c7f 100644 --- a/taiga/auth/backends.py +++ b/taiga/auth/backends.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/auth/permissions.py b/taiga/auth/permissions.py index 854eff26..7e5ab605 100644 --- a/taiga/auth/permissions.py +++ b/taiga/auth/permissions.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh # Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/auth/services.py b/taiga/auth/services.py index a76c350f..f090edf2 100644 --- a/taiga/auth/services.py +++ b/taiga/auth/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -128,16 +128,9 @@ def public_register(username:str, password:str, email:str, full_name:str): @tx.atomic -def private_register_for_existing_user(token:str, username:str, password:str): - """ - Register works not only for register users, also serves for accept - inviatations for projects as existing user. - - Given a invitation token with parsed parameters, accept inviation - as existing user. - """ - - user = get_and_validate_user(username=username, password=password) +def accept_invitation_by_existing_user(token:str, user_id:int): + user_model = get_user_model() + user = user_model.objects.get(id=user_id) membership = get_membership_by_token(token) try: @@ -145,8 +138,6 @@ def private_register_for_existing_user(token:str, username:str, password:str): membership.save(update_fields=["user"]) except IntegrityError: raise exc.IntegrityError(_("This user is already a member of the project.")) - - send_register_email(user) return user diff --git a/taiga/auth/signals.py b/taiga/auth/signals.py index 9e375836..5e7e6ab5 100644 --- a/taiga/auth/signals.py +++ b/taiga/auth/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/auth/throttling.py b/taiga/auth/throttling.py new file mode 100644 index 00000000..f1d25dd8 --- /dev/null +++ b/taiga/auth/throttling.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base import throttling + + +class LoginFailRateThrottle(throttling.GlobalThrottlingMixin, throttling.ThrottleByActionMixin, throttling.SimpleRateThrottle): + scope = "login-fail" + throttled_actions = ["create"] + + def throttle_success(self, request, view): + return True + + def finalize(self, request, response, view): + if response.status_code == 400: + self.history.insert(0, self.now) + self.cache.set(self.key, self.history, self.duration) + + +class RegisterSuccessRateThrottle(throttling.GlobalThrottlingMixin, throttling.ThrottleByActionMixin, throttling.SimpleRateThrottle): + scope = "register-success" + throttled_actions = ["register"] + + def throttle_success(self, request, view): + return True + + def finalize(self, request, response, view): + if response.status_code == 201: + self.history.insert(0, self.now) + self.cache.set(self.key, self.history, self.duration) + diff --git a/taiga/auth/tokens.py b/taiga/auth/tokens.py index a939b748..3a2cf205 100644 --- a/taiga/auth/tokens.py +++ b/taiga/auth/tokens.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/auth/validators.py b/taiga/auth/validators.py index a18dc4bc..1847d946 100644 --- a/taiga/auth/validators.py +++ b/taiga/auth/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -48,11 +48,5 @@ class PublicRegisterValidator(BaseRegisterValidator): pass -class PrivateRegisterForNewUserValidator(BaseRegisterValidator): - token = serializers.CharField(max_length=255, required=True) - - -class PrivateRegisterForExistingUserValidator(validators.Validator): - username = serializers.CharField(max_length=255) - password = serializers.CharField(min_length=4) +class PrivateRegisterValidator(BaseRegisterValidator): token = serializers.CharField(max_length=255, required=True) diff --git a/taiga/base/__init__.py b/taiga/base/__init__.py index 5a7db2f0..c1a4cdb8 100644 --- a/taiga/base/__init__.py +++ b/taiga/base/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/__init__.py b/taiga/base/api/__init__.py index ad481185..12441516 100644 --- a/taiga/base/api/__init__.py +++ b/taiga/base/api/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/authentication.py b/taiga/base/api/authentication.py index d44c69d5..afdbcf39 100644 --- a/taiga/base/api/authentication.py +++ b/taiga/base/api/authentication.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/fields.py b/taiga/base/api/fields.py index a06a7c60..9f6a59c6 100644 --- a/taiga/base/api/fields.py +++ b/taiga/base/api/fields.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -618,13 +618,24 @@ class ChoiceField(WritableField): return value +class InvalidEmailValidationError(ValidationError): + pass + + +class InvalidDomainValidationError(ValidationError): + pass + + def validate_user_email_allowed_domains(value): - validators.validate_email(value) + try: + validators.validate_email(value) + except ValidationError as e: + raise InvalidEmailValidationError(e) domain_name = value.split("@")[1] if settings.USER_EMAIL_ALLOWED_DOMAINS and domain_name not in settings.USER_EMAIL_ALLOWED_DOMAINS: - raise ValidationError(_("You email domain is not allowed")) + raise InvalidDomainValidationError(_("You email domain is not allowed")) class EmailField(CharField): diff --git a/taiga/base/api/generics.py b/taiga/base/api/generics.py index 31823945..cb4de174 100644 --- a/taiga/base/api/generics.py +++ b/taiga/base/api/generics.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/mixins.py b/taiga/base/api/mixins.py index b01d7cf2..9df6519d 100644 --- a/taiga/base/api/mixins.py +++ b/taiga/base/api/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/negotiation.py b/taiga/base/api/negotiation.py index fd2a7028..8b8e04df 100644 --- a/taiga/base/api/negotiation.py +++ b/taiga/base/api/negotiation.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/pagination.py b/taiga/base/api/pagination.py index 636d6b80..9f2e7f21 100644 --- a/taiga/base/api/pagination.py +++ b/taiga/base/api/pagination.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/parsers.py b/taiga/base/api/parsers.py index 42d436f5..19086892 100644 --- a/taiga/base/api/parsers.py +++ b/taiga/base/api/parsers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/permissions.py b/taiga/base/api/permissions.py index ec994b0b..2dc3fedd 100644 --- a/taiga/base/api/permissions.py +++ b/taiga/base/api/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/relations.py b/taiga/base/api/relations.py index 6fbb98f5..206eb59f 100644 --- a/taiga/base/api/relations.py +++ b/taiga/base/api/relations.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/renderers.py b/taiga/base/api/renderers.py index a4ebaa3c..31c8ce5d 100644 --- a/taiga/base/api/renderers.py +++ b/taiga/base/api/renderers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/request.py b/taiga/base/api/request.py index 059ece06..516ec2f7 100644 --- a/taiga/base/api/request.py +++ b/taiga/base/api/request.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/reverse.py b/taiga/base/api/reverse.py index 4d4de867..548987a1 100644 --- a/taiga/base/api/reverse.py +++ b/taiga/base/api/reverse.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/serializers.py b/taiga/base/api/serializers.py index 2ee05db8..e991714c 100644 --- a/taiga/base/api/serializers.py +++ b/taiga/base/api/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -817,8 +817,18 @@ class ModelSerializer((six.with_metaclass(SerializerMetaclass, BaseSerializer))) else: # Reverse relationships are only included if they are explicitly # present in the `fields` option on the serializer - reverse_rels = opts.get_all_related_objects() - reverse_rels += opts.get_all_related_many_to_many_objects() + + # NOTE: Rewrite after Django 1.10 upgrade. + # See https://docs.djangoproject.com/es/1.10/ref/models/meta/#migrating-from-the-old-api + reverse_rels = [ + f for f in opts.get_fields() + if (f.one_to_many or f.one_to_one) + and f.auto_created and not f.concrete + ] + reverse_rels += [ + f for f in opts.get_fields(include_hidden=True) + if f.many_to_many and f.auto_created + ] for relation in reverse_rels: accessor_name = relation.get_accessor_name() @@ -1024,16 +1034,32 @@ class ModelSerializer((six.with_metaclass(SerializerMetaclass, BaseSerializer))) m2m_data = {} related_data = {} nested_forward_relations = {} + model = self.opts.model meta = self.opts.model._meta # Reverse fk or one-to-one relations - for (obj, model) in meta.get_all_related_objects_with_model(): + # NOTE: Rewrite after Django 1.10 upgrade. + # See https://docs.djangoproject.com/es/1.10/ref/models/meta/#migrating-from-the-old-api + related_objes_with_models = [ + (f, f.model if f.model != model else None) + for f in meta.get_fields() + if (f.one_to_many or f.one_to_one) + and f.auto_created and not f.concrete + ] + for (obj, model) in related_objes_with_models: field_name = obj.get_accessor_name() if field_name in attrs: related_data[field_name] = attrs.pop(field_name) # Reverse m2m relations - for (obj, model) in meta.get_all_related_m2m_objects_with_model(): + # NOTE: Rewrite after Django 1.10 upgrade. + # See https://docs.djangoproject.com/es/1.10/ref/models/meta/#migrating-from-the-old-api + related_m2m_objects_with_model = [ + (f, f.model if f.model != model else None) + for f in meta.get_fields(include_hidden=True) + if f.many_to_many and f.auto_created + ] + for (obj, model) in related_m2m_objects_with_model: field_name = obj.get_accessor_name() if field_name in attrs: m2m_data[field_name] = attrs.pop(field_name) diff --git a/taiga/base/api/settings.py b/taiga/base/api/settings.py index f5856146..afd1c33b 100644 --- a/taiga/base/api/settings.py +++ b/taiga/base/api/settings.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -106,6 +106,7 @@ DEFAULTS = { "user": None, "anon": None, }, + "DEFAULT_THROTTLE_WHITELIST": [], # Pagination "PAGINATE_BY": None, diff --git a/taiga/base/api/throttling.py b/taiga/base/api/throttling.py index 74178922..afef7725 100644 --- a/taiga/base/api/throttling.py +++ b/taiga/base/api/throttling.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -64,6 +64,12 @@ class BaseThrottle(object): """ raise NotImplementedError(".allow_request() must be overridden") + def finalize(self, request, response, view): + """ + Optionally, update the Trottling information based on de response. + """ + return None + def wait(self): """ Optionally, return a recommended number of seconds to wait before @@ -105,6 +111,12 @@ class SimpleRateThrottle(BaseThrottle): """ raise NotImplementedError(".get_cache_key() must be overridden") + def has_to_finalize(self, request, response, view): + """ + Determine if the finalize method must be executed. + """ + return self.rate is not None + def get_rate(self): """ Determine the string representation of the allowed request rate. diff --git a/taiga/base/api/urlpatterns.py b/taiga/base/api/urlpatterns.py index 33548a07..26f0677e 100644 --- a/taiga/base/api/urlpatterns.py +++ b/taiga/base/api/urlpatterns.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -44,7 +44,7 @@ from django.core.urlresolvers import RegexURLResolver -from django.conf.urls import patterns, url, include +from django.conf.urls import url, include from .settings import api_settings @@ -67,7 +67,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required): else: # Regular URL pattern regex = urlpattern.regex.pattern.rstrip("$") + suffix_pattern - view = urlpattern._callback or urlpattern._callback_str + view = urlpattern.callback kwargs = urlpattern.default_args name = urlpattern.name # Add in both the existing and the new urlpattern diff --git a/taiga/base/api/utils.py b/taiga/base/api/utils.py index 30318d5e..8c2bb038 100644 --- a/taiga/base/api/utils.py +++ b/taiga/base/api/utils.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/utils/__init__.py b/taiga/base/api/utils/__init__.py index b6198a45..5dd058c1 100644 --- a/taiga/base/api/utils/__init__.py +++ b/taiga/base/api/utils/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/utils/encoders.py b/taiga/base/api/utils/encoders.py index be307d25..bade2cbe 100644 --- a/taiga/base/api/utils/encoders.py +++ b/taiga/base/api/utils/encoders.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/utils/formatting.py b/taiga/base/api/utils/formatting.py index f2decc0b..bd983220 100644 --- a/taiga/base/api/utils/formatting.py +++ b/taiga/base/api/utils/formatting.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/utils/mediatypes.py b/taiga/base/api/utils/mediatypes.py index c0dc1266..55dd4f8c 100644 --- a/taiga/base/api/utils/mediatypes.py +++ b/taiga/base/api/utils/mediatypes.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/validators.py b/taiga/base/api/validators.py index 3a8d6922..f4f01497 100644 --- a/taiga/base/api/validators.py +++ b/taiga/base/api/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/api/views.py b/taiga/base/api/views.py index 8c6dfd09..cc42966b 100644 --- a/taiga/base/api/views.py +++ b/taiga/base/api/views.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -144,6 +144,8 @@ class APIView(View): # Allow dependancy injection of other settings to make testing easier. settings = api_settings + _trottle_instances = None + @classmethod def as_view(cls, **initkwargs): """ @@ -286,7 +288,9 @@ class APIView(View): """ Instantiates and returns the list of throttles that this view uses. """ - return [throttle() for throttle in self.throttle_classes] + if self._trottle_instances is None: + self._trottle_instances = [throttle() for throttle in self.throttle_classes] + return self._trottle_instances def get_content_negotiator(self): """ @@ -342,6 +346,15 @@ class APIView(View): if not throttle.allow_request(request, self): self.throttled(request, throttle.wait()) + def finalize_throttles(self, request, response): + """ + Check if request should be throttled. + Raises an appropriate exception if the request is throttled. + """ + for throttle in self.get_throttles(): + if throttle.has_to_finalize(request, response, self): + throttle.finalize(request, response, self) + # Dispatch methods def initialize_request(self, request, *args, **kwargs): @@ -391,6 +404,8 @@ class APIView(View): for key, value in self.headers.items(): response[key] = value + self.finalize_throttles(request, response) + return response def handle_exception(self, exc): diff --git a/taiga/base/api/viewsets.py b/taiga/base/api/viewsets.py index d37bfc50..aee0c19e 100644 --- a/taiga/base/api/viewsets.py +++ b/taiga/base/api/viewsets.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/apps.py b/taiga/base/apps.py index f5f1879b..e8616bff 100644 --- a/taiga/base/apps.py +++ b/taiga/base/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/connectors/exceptions.py b/taiga/base/connectors/exceptions.py index eb47c5db..cc92dae5 100644 --- a/taiga/base/connectors/exceptions.py +++ b/taiga/base/connectors/exceptions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/db/__init__.py b/taiga/base/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/base/db/models/__init__.py b/taiga/base/db/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/base/db/models/fields/__init__.py b/taiga/base/db/models/fields/__init__.py new file mode 100644 index 00000000..d87b6502 --- /dev/null +++ b/taiga/base/db/models/fields/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 .json import JSONField diff --git a/taiga/base/db/models/fields/json.py b/taiga/base/db/models/fields/json.py new file mode 100644 index 00000000..aa4fbe05 --- /dev/null +++ b/taiga/base/db/models/fields/json.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.serializers.json import DjangoJSONEncoder +from django.contrib.postgres.fields import JSONField as DjangoJSONField + +# NOTE: After upgrade Django to the future release (1.11) change +# class JSONField(FutureDjangoJSONField): +# to +# class JSONField(DjangoJSONField): +# and remove the classes JsonAdapter and FutureDjangoJSONField + +import json +from psycopg2.extras import Json +from django.core import exceptions + + +class JsonAdapter(Json): + """ + Customized psycopg2.extras.Json to allow for a custom encoder. + """ + def __init__(self, adapted, dumps=None, encoder=None): + self.encoder = encoder + super().__init__(adapted, dumps=dumps) + + def dumps(self, obj): + options = {'cls': self.encoder} if self.encoder else {} + return json.dumps(obj, **options) + + +class FutureDjangoJSONField(DjangoJSONField): + def __init__(self, verbose_name=None, name=None, encoder=None, **kwargs): + if encoder and not callable(encoder): + raise ValueError("The encoder parameter must be a callable object.") + self.encoder = encoder + super().__init__(verbose_name, name, **kwargs) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + if self.encoder is not None: + kwargs['encoder'] = self.encoder + return name, path, args, kwargs + + def get_prep_value(self, value): + if value is not None: + return JsonAdapter(value, encoder=self.encoder) + return value + + def validate(self, value, model_instance): + super().validate(value, model_instance) + options = {'cls': self.encoder} if self.encoder else {} + try: + json.dumps(value, **options) + except TypeError: + raise exceptions.ValidationError( + self.error_messages['invalid'], + code='invalid', + params={'value': value}, + ) + + +__all__ = ["JSONField"] + +class JSONField(FutureDjangoJSONField): + def __init__(self, verbose_name=None, name=None, encoder=DjangoJSONEncoder, **kwargs): + super().__init__(verbose_name, name, encoder, **kwargs) diff --git a/taiga/base/decorators.py b/taiga/base/decorators.py index 46b80b24..b0feb4df 100644 --- a/taiga/base/decorators.py +++ b/taiga/base/decorators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/exceptions.py b/taiga/base/exceptions.py index 73d277ff..12310baa 100644 --- a/taiga/base/exceptions.py +++ b/taiga/base/exceptions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/fields.py b/taiga/base/fields.py index 3b19f15f..99baefa7 100644 --- a/taiga/base/fields.py +++ b/taiga/base/fields.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -30,7 +30,7 @@ import serpy # NOTE: This should be in other place, for example taiga.base.api.serializers -class JsonField(serializers.WritableField): +class JSONField(serializers.WritableField): """ Json objects serializer. """ @@ -95,12 +95,12 @@ class I18NField(Field): return _(ret) -class I18NJsonField(Field): +class I18NJSONField(Field): """ Json objects serializer. """ def __init__(self, i18n_fields=(), *args, **kwargs): - super(I18NJsonField, self).__init__(*args, **kwargs) + super(I18NJSONField, self).__init__(*args, **kwargs) self.i18n_fields = i18n_fields def translate_values(self, d): diff --git a/taiga/base/filters.py b/taiga/base/filters.py index 5dc531b9..2533f0b8 100644 --- a/taiga/base/filters.py +++ b/taiga/base/filters.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -527,6 +527,22 @@ class FinishDateFilter(BaseDateFilter): filter_name_base = "finish_date" +class EstimatedStartFilter(BaseDateFilter): + filter_name_base = "estimated_start" + + +class EstimatedFinishFilter(BaseDateFilter): + filter_name_base = "estimated_finish" + + +class MilestoneEstimatedStartFilter(BaseDateFilter): + filter_name_base = "milestone__estimated_start" + + +class MilestoneEstimatedFinishFilter(BaseDateFilter): + filter_name_base = "milestone__estimated_finish" + + ##################################################################### # Text search filters ##################################################################### @@ -537,11 +553,11 @@ class QFilter(FilterBackend): if q: table = queryset.model._meta.db_table where_clause = (""" - to_tsvector('english_nostop', + to_tsvector('simple', coalesce({table}.subject, '') || ' ' || coalesce(array_to_string({table}.tags, ' '), '') || ' ' || coalesce({table}.ref) || ' ' || - coalesce({table}.description, '')) @@ to_tsquery('english_nostop', %s) + coalesce({table}.description, '')) @@ to_tsquery('simple', %s) """.format(table=table)) queryset = queryset.extra(where=[where_clause], params=[to_tsquery(q)]) diff --git a/taiga/base/formats/en/formats.py b/taiga/base/formats/en/formats.py index 37d64ee8..e57252da 100644 --- a/taiga/base/formats/en/formats.py +++ b/taiga/base/formats/en/formats.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/formats/es/formats.py b/taiga/base/formats/es/formats.py index f6d7dc55..d1f50260 100644 --- a/taiga/base/formats/es/formats.py +++ b/taiga/base/formats/es/formats.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/mails.py b/taiga/base/mails.py index 48e6f296..433e945e 100644 --- a/taiga/base/mails.py +++ b/taiga/base/mails.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/management/commands/test_emails.py b/taiga/base/management/commands/test_emails.py index 631bcd42..faf9414c 100644 --- a/taiga/base/management/commands/test_emails.py +++ b/taiga/base/management/commands/test_emails.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -18,8 +18,6 @@ import datetime -from optparse import make_option - from django.apps import apps from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand @@ -33,28 +31,27 @@ from taiga.projects.history.services import get_history_queryset_by_model_instan class Command(BaseCommand): - args = '' - option_list = BaseCommand.option_list + ( - make_option('--locale', '-l', default=None, dest='locale', - help='Send emails in an specific language.'), - ) - help = 'Send an example of all emails' - def handle(self, *args, **options): - if len(args) != 1: - print("Usage: ./manage.py test_emails ") - return + def add_arguments(self, parser): + parser.add_argument('--locale', '-l', + default=None, + dest='locale', + help='Send emails in an specific language.') + parser.add_argument('email', + help='Emeil address to send sample emails.') + + def handle(self, *args, **options): locale = options.get('locale') - test_email = args[0] + email_address = options.get('email') # Register email context = {"lang": locale, "user": get_user_model().objects.all().order_by("?").first(), "cancel_token": "cancel-token"} - email = mail_builder.registered_user(test_email, context) + email = mail_builder.registered_user(email_address, context) email.send() # Membership invitation @@ -63,13 +60,13 @@ class Command(BaseCommand): membership.invitation_extra_text = "Text example, Text example,\nText example,\n\nText example" context = {"lang": locale, "membership": membership} - email = mail_builder.membership_invitation(test_email, context) + email = mail_builder.membership_invitation(email_address, context) email.send() # Membership notification context = {"lang": locale, "membership": Membership.objects.order_by("?").filter(user__isnull=False).first()} - email = mail_builder.membership_notification(test_email, context) + email = mail_builder.membership_notification(email_address, context) email.send() # Feedback @@ -85,17 +82,17 @@ class Command(BaseCommand): "key2": "value2", }, } - email = mail_builder.feedback_notification(test_email, context) + email = mail_builder.feedback_notification(email_address, context) email.send() # Password recovery context = {"lang": locale, "user": get_user_model().objects.all().order_by("?").first()} - email = mail_builder.password_recovery(test_email, context) + email = mail_builder.password_recovery(email_address, context) email.send() # Change email context = {"lang": locale, "user": get_user_model().objects.all().order_by("?").first()} - email = mail_builder.change_email(test_email, context) + email = mail_builder.change_email(email_address, context) email.send() # Export/Import emails @@ -106,7 +103,7 @@ class Command(BaseCommand): "error_subject": "Error generating project dump", "error_message": "Error generating project dump", } - email = mail_builder.export_error(test_email, context) + email = mail_builder.export_error(email_address, context) email.send() context = { "lang": locale, @@ -114,7 +111,7 @@ class Command(BaseCommand): "error_subject": "Error importing project dump", "error_message": "Error importing project dump", } - email = mail_builder.import_error(test_email, context) + email = mail_builder.import_error(email_address, context) email.send() deletion_date = timezone.now() + datetime.timedelta(seconds=60*60*24) @@ -125,7 +122,7 @@ class Command(BaseCommand): "project": Project.objects.all().order_by("?").first(), "deletion_date": deletion_date, } - email = mail_builder.dump_project(test_email, context) + email = mail_builder.dump_project(email_address, context) email.send() context = { @@ -133,7 +130,7 @@ class Command(BaseCommand): "user": get_user_model().objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), } - email = mail_builder.load_dump(test_email, context) + email = mail_builder.load_dump(email_address, context) email.send() # Notification emails @@ -187,7 +184,7 @@ class Command(BaseCommand): cls = type("InlineCSSTemplateMail", (InlineCSSTemplateMail,), {"name": notification_email[1]}) email = cls() - email.send(test_email, context) + email.send(email_address, context) # Transfer Emails @@ -195,7 +192,7 @@ class Command(BaseCommand): "project": Project.objects.all().order_by("?").first(), "requester": User.objects.all().order_by("?").first(), } - email = mail_builder.transfer_request(test_email, context) + email = mail_builder.transfer_request(email_address, context) email.send() context = { @@ -204,7 +201,7 @@ class Command(BaseCommand): "token": "test-token", "reason": "Test reason" } - email = mail_builder.transfer_start(test_email, context) + email = mail_builder.transfer_start(email_address, context) email.send() context = { @@ -213,7 +210,7 @@ class Command(BaseCommand): "new_owner": User.objects.all().order_by("?").first(), "reason": "Test reason" } - email = mail_builder.transfer_accept(test_email, context) + email = mail_builder.transfer_accept(email_address, context) email.send() context = { @@ -221,5 +218,5 @@ class Command(BaseCommand): "rejecter": User.objects.all().order_by("?").first(), "reason": "Test reason" } - email = mail_builder.transfer_reject(test_email, context) + email = mail_builder.transfer_reject(email_address, context) email.send() diff --git a/taiga/base/middleware/cors.py b/taiga/base/middleware/cors.py index 3f5cbd38..2bf89b34 100644 --- a/taiga/base/middleware/cors.py +++ b/taiga/base/middleware/cors.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/neighbors.py b/taiga/base/neighbors.py index c8733ade..576f6147 100644 --- a/taiga/base/neighbors.py +++ b/taiga/base/neighbors.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/response.py b/taiga/base/response.py index 82d7794f..59d0be8a 100644 --- a/taiga/base/response.py +++ b/taiga/base/response.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/routers.py b/taiga/base/routers.py index a7ccbdc4..a6ec7ce7 100644 --- a/taiga/base/routers.py +++ b/taiga/base/routers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,7 +20,7 @@ import itertools from collections import namedtuple -from django.conf.urls import patterns, url +from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import NoReverseMatch @@ -79,7 +79,7 @@ class BaseRouter(object): @property def urls(self): if not hasattr(self, '_urls'): - self._urls = patterns('', *self.get_urls()) + self._urls = self.get_urls() return self._urls diff --git a/taiga/base/signals/cleanup_files.py b/taiga/base/signals/cleanup_files.py index e2449ce2..cb56092e 100644 --- a/taiga/base/signals/cleanup_files.py +++ b/taiga/base/signals/cleanup_files.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -18,20 +18,18 @@ from django.apps import apps from django.db import models, connection -from django.db.utils import DEFAULT_DB_ALIAS, ConnectionHandler from django.db.models.signals import pre_save, post_delete +from django.dispatch import Signal import logging + logger = logging.getLogger(__name__) -from django.dispatch import Signal - cleanup_pre_delete = Signal(providing_args=["file"]) cleanup_post_delete = Signal(providing_args=["file"]) - def _find_models_with_filefield(): result = [] for model in apps.get_models(): @@ -50,7 +48,7 @@ def _delete_file(file_obj): cleanup_post_delete.send(sender=None, file=file_obj) except Exception: logger.exception("Unexpected exception while attempting " - "to delete old file '%s'".format(file_obj.name)) + "to delete old file '%s'".format(file_obj.name)) storage = file_obj.storage if storage and storage.exists(file_obj.name): @@ -90,9 +88,6 @@ def remove_files_on_delete(sender, instance, **kwargs): def connect_cleanup_files_signals(): - connections = ConnectionHandler() - backend = connections[DEFAULT_DB_ALIAS] - for model in _find_models_with_filefield(): pre_save.connect(remove_files_on_change, sender=model) post_delete.connect(remove_files_on_delete, sender=model) diff --git a/taiga/base/signals/thumbnails.py b/taiga/base/signals/thumbnails.py index e40cba07..57f86370 100644 --- a/taiga/base/signals/thumbnails.py +++ b/taiga/base/signals/thumbnails.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/status.py b/taiga/base/status.py index c271e030..6d84797b 100644 --- a/taiga/base/status.py +++ b/taiga/base/status.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/storage.py b/taiga/base/storage.py index a0b962c4..10e26b6d 100644 --- a/taiga/base/storage.py +++ b/taiga/base/storage.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/templates/emails/base-body-html.jinja b/taiga/base/templates/emails/base-body-html.jinja index 3b779e36..9a5fb68b 100644 --- a/taiga/base/templates/emails/base-body-html.jinja +++ b/taiga/base/templates/emails/base-body-html.jinja @@ -157,7 +157,6 @@ padding-bottom:20px; padding-left:20px; padding-top:20px; - text-align:center; } /** @@ -188,6 +187,13 @@ background: #aad400; } + hr { + width: 90%; + margin: 0 auto; + border-bottom: 1px solid #e1e1e1; + border-top: 0; + } + .bodyContent img{ display:inline; height:auto; @@ -396,16 +402,25 @@ Taiga - {% block body %} - {% endblock %} + + + + + + + + + + {% block social %} {% endblock %} diff --git a/taiga/base/throttling.py b/taiga/base/throttling.py index f2484ea9..086f9660 100644 --- a/taiga/base/throttling.py +++ b/taiga/base/throttling.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -16,24 +16,183 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + from taiga.base.api import throttling +from ipware.ip import get_ip +from netaddr import all_matching_cidrs +from netaddr.core import AddrFormatError + + +class GlobalThrottlingMixin: + """ + Define the cache key based on the user IP independently if the user is + logged in or not. + """ + def get_cache_key(self, request, view): + ident = get_ip(request) + + return self.cache_format % { + "scope": self.scope, + "ident": ident + } + + +class ThrottleByActionMixin: + throttled_actions = [] + + def has_to_finalize(self, request, response, view): + if super().has_to_finalize(request, response, view): + return view.action in self.throttled_actions + return False + + def allow_request(self, request, view): + if view.action in self.throttled_actions: + return super().allow_request(request, view) + return True class AnonRateThrottle(throttling.AnonRateThrottle): scope = "anon" - throttled_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"] - - def allow_request(self, request, view): - if request.method not in self.throttled_methods: - return True - return super().allow_request(request, view) class UserRateThrottle(throttling.UserRateThrottle): scope = "user" - throttled_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"] + + +class CommonThrottle(throttling.SimpleRateThrottle): + cache_format = "throtte_%(scope)s_%(rate)s_%(ident)s" + + def __init__(self): + pass + + def has_to_finalize(self, request, response, view): + return False + + def is_whitelisted(self, ident): + for whitelisted in settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST']: + if isinstance(whitelisted, int) and whitelisted == ident: + return True + elif isinstance(whitelisted, str): + try: + if all_matching_cidrs(ident, [whitelisted]) != []: + return True + except(AddrFormatError, ValueError): + pass + return False def allow_request(self, request, view): - if request.method not in self.throttled_methods: + scope = self.get_scope(request) + ident = self.get_ident(request) + rates = self.get_rates(scope) + + if self.is_whitelisted(ident): return True - return super().allow_request(request, view) + + if rates is None or rates == []: + return True + + now = self.timer() + + waits = [] + history_writes = [] + + for rate in rates: + rate_name = rate[0] + rate_num_requests = rate[1] + rate_duration = rate[2] + + key = self.get_cache_key(ident, scope, rate_name) + history = self.cache.get(key, []) + + while history and history[-1] <= now - rate_duration: + history.pop() + + if len(history) >= rate_num_requests: + waits.append(self.wait_time(history, rate, now)) + + history_writes.append({ + "key": key, + "history": history, + "rate_duration": rate_duration, + }) + + if waits: + self._wait = max(waits) + return False + + for history_write in history_writes: + history_write['history'].insert(0, now) + self.cache.set( + history_write['key'], + history_write['history'], + history_write['rate_duration'] + ) + return True + + def get_rates(self, scope): + try: + rates = self.THROTTLE_RATES[scope] + except KeyError: + msg = "No default throttle rate set for \"%s\" scope" % scope + raise ImproperlyConfigured(msg) + + if rates is None: + return [] + elif isinstance(rates, str): + return [self.parse_rate(rates)] + elif isinstance(rates, list): + return list(map(self.parse_rate, rates)) + else: + msg = "No valid throttle rate set for \"%s\" scope" % scope + raise ImproperlyConfigured(msg) + + def parse_rate(self, rate): + """ + Given the request rate string, return a two tuple of: + , + """ + if rate is None: + return None + num, period = rate.split("/") + num_requests = int(num) + duration = {"s": 1, "m": 60, "h": 3600, "d": 86400}[period[0]] + return (rate, num_requests, duration) + + def get_scope(self, request): + scope_prefix = "user" if request.user.is_authenticated() else "anon" + scope_sufix = "write" if request.method in ["POST", "PUT", "PATCH", "DELETE"] else "read" + scope = "{}-{}".format(scope_prefix, scope_sufix) + return scope + + def get_ident(self, request): + if request.user.is_authenticated(): + return request.user.id + ident = get_ip(request) + return ident + + def get_cache_key(self, ident, scope, rate): + return self.cache_format % { "scope": scope, "ident": ident, "rate": rate } + + def wait_time(self, history, rate, now): + rate_num_requests = rate[1] + rate_duration = rate[2] + + if history: + remaining_duration = rate_duration - (now - history[-1]) + else: + remaining_duration = rate_duration + + available_requests = rate_num_requests - len(history) + 1 + if available_requests <= 0: + return remaining_duration + + return remaining_duration / float(available_requests) + + def wait(self): + return self._wait + + +class SimpleRateThrottle(throttling.SimpleRateThrottle): + pass diff --git a/taiga/base/utils/collections.py b/taiga/base/utils/collections.py index c5ca3c59..675846eb 100644 --- a/taiga/base/utils/collections.py +++ b/taiga/base/utils/collections.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/colors.py b/taiga/base/utils/colors.py index 517c8add..f187dd3d 100644 --- a/taiga/base/utils/colors.py +++ b/taiga/base/utils/colors.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/contenttypes.py b/taiga/base/utils/contenttypes.py index 252a3db2..bfdd5ab3 100644 --- a/taiga/base/utils/contenttypes.py +++ b/taiga/base/utils/contenttypes.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/db.py b/taiga/base/utils/db.py index ec7b1eb2..809c9d9c 100644 --- a/taiga/base/utils/db.py +++ b/taiga/base/utils/db.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/dicts.py b/taiga/base/utils/dicts.py index bf3d2c71..21a39cf7 100644 --- a/taiga/base/utils/dicts.py +++ b/taiga/base/utils/dicts.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/diff.py b/taiga/base/utils/diff.py index e08584ae..e8d07e67 100644 --- a/taiga/base/utils/diff.py +++ b/taiga/base/utils/diff.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/files.py b/taiga/base/utils/files.py index 1772e2af..4bfc8004 100644 --- a/taiga/base/utils/files.py +++ b/taiga/base/utils/files.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/functions.py b/taiga/base/utils/functions.py index d25d3572..6e458094 100644 --- a/taiga/base/utils/functions.py +++ b/taiga/base/utils/functions.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/iterators.py b/taiga/base/utils/iterators.py index 19d8c553..c5723fb5 100644 --- a/taiga/base/utils/iterators.py +++ b/taiga/base/utils/iterators.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/json.py b/taiga/base/utils/json.py index 6e2a0658..59a6eaa1 100644 --- a/taiga/base/utils/json.py +++ b/taiga/base/utils/json.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/signals.py b/taiga/base/utils/signals.py index 9dbef497..f648df40 100644 --- a/taiga/base/utils/signals.py +++ b/taiga/base/utils/signals.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/slug.py b/taiga/base/utils/slug.py index 239366d3..1daf149c 100644 --- a/taiga/base/utils/slug.py +++ b/taiga/base/utils/slug.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/text.py b/taiga/base/utils/text.py index 05447b45..81b631e3 100644 --- a/taiga/base/utils/text.py +++ b/taiga/base/utils/text.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/thumbnails.py b/taiga/base/utils/thumbnails.py index e28aff0c..7d704b8e 100644 --- a/taiga/base/utils/thumbnails.py +++ b/taiga/base/utils/thumbnails.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -26,12 +26,28 @@ from taiga.base.utils.urls import get_absolute_url from easy_thumbnails.files import get_thumbnailer from easy_thumbnails.exceptions import InvalidImageFormatError from PIL import Image +from PIL.PngImagePlugin import PngImageFile +from io import BytesIO +# SVG thumbnail generator +try: + from cairosvg.surface import PNGSurface + + def svg_image_factory(data, *args): + png_data = PNGSurface.convert(data.read()) + return PngImageFile(BytesIO(png_data)) + + Image.register_mime("SVG", "image/svg+xml") + Image.register_extension("SVG", ".svg") + Image.register_open("SVG", svg_image_factory) +except Exception: + pass + +# PSD thumbnail generator def psd_image_factory(data, *args): return PSDImage.from_stream(data).as_PIL() - Image.init() Image.register_open("PSD", psd_image_factory) diff --git a/taiga/base/utils/time.py b/taiga/base/utils/time.py index cd7b00c4..7012825c 100644 --- a/taiga/base/utils/time.py +++ b/taiga/base/utils/time.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/base/utils/urls.py b/taiga/base/utils/urls.py index 36239113..a145cd21 100644 --- a/taiga/base/utils/urls.py +++ b/taiga/base/utils/urls.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/celery.py b/taiga/celery.py index 2cafa1b8..26d23017 100644 --- a/taiga/celery.py +++ b/taiga/celery.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,11 +20,15 @@ import os from celery import Celery -from django.conf import settings - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') -app = Celery('taiga') +from django.conf import settings -app.config_from_object('django.conf:settings') -app.autodiscover_tasks(lambda: settings.INSTALLED_APPS, related_name="deferred") +try: + from settings import celery_local as celery_settings +except ImportError: + from settings import celery as celery_settings + +app = Celery('taiga') +app.config_from_object(celery_settings) +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) diff --git a/taiga/deferred.py b/taiga/deferred.py deleted file mode 100644 index 30a17d6b..00000000 --- a/taiga/deferred.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# 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.conf import settings - -from .celery import app - - -def _send_task(task, args, kwargs, **options): - if settings.CELERY_ALWAYS_EAGER: - return app.tasks[task].apply(args, kwargs, **options) - return app.send_task(task, args, kwargs, **options) - - -def defer(task: str, *args, **kwargs): - """Defer the execution of a task. - - Defer the execution of a task and returns a future objects with the following methods among - others: - - `failed()` Returns `True` if the task failed. - - `ready()` Returns `True` if the task has been executed. - - `forget()` Forget about the result. - - `get()` Wait until the task is ready and return its result. - - `result` When the task has been executed the result is in this attribute. - More info at Celery docs on `AsyncResult` object. - - :param task: Name of the task to execute. - - :return: A future object. - """ - return _send_task(task, args, kwargs, routing_key="transient.deferred") - - -def call_async(task: str, *args, **kwargs): - """Run a task and ignore its result. - - This is just a star argument version of `apply_async`. - - :param task: Name of the task to execute. - :param args: Arguments for the task. - :param kwargs: Keyword arguments for the task. - """ - apply_async(task, args, kwargs) - - -def apply_async(task: str, args=None, kwargs=None, **options): - """Run a task and ignore its result. - - :param task: Name of the task to execute. - :param args: Tupple of arguments for the task. - :param kwargs: Dict of keyword arguments for the task. - :param options: Celery-specific options when running the task. See Celery docs on `apply_async` - """ - _send_task(task, args, kwargs, **options) diff --git a/taiga/events/__init__.py b/taiga/events/__init__.py index b6017f3b..9d91be72 100644 --- a/taiga/events/__init__.py +++ b/taiga/events/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/events/apps.py b/taiga/events/apps.py index 7c9a7680..2792555b 100644 --- a/taiga/events/apps.py +++ b/taiga/events/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/events/backends/__init__.py b/taiga/events/backends/__init__.py index f3f0d6c3..1fbcee96 100644 --- a/taiga/events/backends/__init__.py +++ b/taiga/events/backends/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/events/backends/base.py b/taiga/events/backends/base.py index a9f0e8f2..9d67d121 100644 --- a/taiga/events/backends/base.py +++ b/taiga/events/backends/base.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2017 Andrey Antukh # 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 diff --git a/taiga/events/backends/postgresql.py b/taiga/events/backends/postgresql.py index e239d421..9504b55a 100644 --- a/taiga/events/backends/postgresql.py +++ b/taiga/events/backends/postgresql.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2017 Andrey Antukh # 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 diff --git a/taiga/events/backends/rabbitmq.py b/taiga/events/backends/rabbitmq.py index 829dcf3a..602e74ad 100644 --- a/taiga/events/backends/rabbitmq.py +++ b/taiga/events/backends/rabbitmq.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2017 Andrey Antukh # 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 @@ -17,6 +17,7 @@ import json import logging from amqp import Connection as AmqpConnection +from amqp.exceptions import AccessRefused from amqp.basic_message import Message as AmqpMessage from urllib.parse import urlparse @@ -50,17 +51,25 @@ class EventsPushBackend(base.BaseEventsPushBackend): def emit_event(self, message:str, *, routing_key:str, channel:str="events"): connection = _make_rabbitmq_connection(self.url) - try: - rchannel = connection.channel() - message = AmqpMessage(message) + connection.connect() + except ConnectionRefusedError: + err_msg = "EventsPushBackend: Unable to connect with RabbitMQ (connection refused) at {}".format( + self.url) + log.error(err_msg, exc_info=True) + except AccessRefused: + err_msg = "EventsPushBackend: Unable to connect with RabbitMQ (access refused) at {}".format( + self.url) + log.error(err_msg, exc_info=True) + else: + try: + message = AmqpMessage(message) + rchannel = connection.channel() - rchannel.exchange_declare(exchange=channel, type="topic", auto_delete=True) - rchannel.basic_publish(message, routing_key=routing_key, exchange=channel) - rchannel.close() - - except Exception: - log.error("Unhandled exception", exc_info=True) - - finally: - connection.close() + rchannel.exchange_declare(exchange=channel, type="topic", auto_delete=True) + rchannel.basic_publish(message, routing_key=routing_key, exchange=channel) + rchannel.close() + except Exception: + log.error("EventsPushBackend: Unhandled exception", exc_info=True) + finally: + connection.close() diff --git a/taiga/events/events.py b/taiga/events/events.py index bbd8735c..c8201c55 100644 --- a/taiga/events/events.py +++ b/taiga/events/events.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/events/management/commands/emit_notification_message.py b/taiga/events/management/commands/emit_notification_message.py index ed5365d2..a9e8c2bb 100644 --- a/taiga/events/management/commands/emit_notification_message.py +++ b/taiga/events/management/commands/emit_notification_message.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/events/middleware.py b/taiga/events/middleware.py index 1fa5c182..8cbde3c3 100644 --- a/taiga/events/middleware.py +++ b/taiga/events/middleware.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/events/signal_handlers.py b/taiga/events/signal_handlers.py index e310fcf1..7f8d657d 100644 --- a/taiga/events/signal_handlers.py +++ b/taiga/events/signal_handlers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/api.py b/taiga/export_import/api.py index 75644365..ac8ae462 100644 --- a/taiga/export_import/api.py +++ b/taiga/export_import/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -104,7 +104,7 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi 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_services.has_available_slot_for_import_new_project( + (enough_slots, error_message) = users_services.has_available_slot_for_new_project( self.request.user, is_private, total_memberships @@ -344,7 +344,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_services.has_available_slot_for_import_new_project( + (enough_slots, error_message) = users_services.has_available_slot_for_new_project( user, is_private, total_memberships diff --git a/taiga/export_import/exceptions.py b/taiga/export_import/exceptions.py index 9b57306f..ba94b8f7 100644 --- a/taiga/export_import/exceptions.py +++ b/taiga/export_import/exceptions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/management/commands/dump_project.py b/taiga/export_import/management/commands/dump_project.py index 3c6996f3..ea7640a7 100644 --- a/taiga/export_import/management/commands/dump_project.py +++ b/taiga/export_import/management/commands/dump_project.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/management/commands/dump_project_async.py b/taiga/export_import/management/commands/dump_project_async.py index d48a0c19..9890f3a0 100644 --- a/taiga/export_import/management/commands/dump_project_async.py +++ b/taiga/export_import/management/commands/dump_project_async.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/management/commands/load_dump.py b/taiga/export_import/management/commands/load_dump.py index c01f577f..cdbc2958 100644 --- a/taiga/export_import/management/commands/load_dump.py +++ b/taiga/export_import/management/commands/load_dump.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/mixins.py b/taiga/export_import/mixins.py index e70e674f..30e1fad3 100644 --- a/taiga/export_import/mixins.py +++ b/taiga/export_import/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/permissions.py b/taiga/export_import/permissions.py index 6d69964a..faf5e2d2 100644 --- a/taiga/export_import/permissions.py +++ b/taiga/export_import/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/renderers.py b/taiga/export_import/renderers.py index 04e28bbd..249c9ef7 100644 --- a/taiga/export_import/renderers.py +++ b/taiga/export_import/renderers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/serializers/__init__.py b/taiga/export_import/serializers/__init__.py index 5d793a87..055b5676 100644 --- a/taiga/export_import/serializers/__init__.py +++ b/taiga/export_import/serializers/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/serializers/cache.py b/taiga/export_import/serializers/cache.py index f22978f8..4dfb76ad 100644 --- a/taiga/export_import/serializers/cache.py +++ b/taiga/export_import/serializers/cache.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/serializers/fields.py b/taiga/export_import/serializers/fields.py index 29ec85aa..7c7055c0 100644 --- a/taiga/export_import/serializers/fields.py +++ b/taiga/export_import/serializers/fields.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/serializers/mixins.py b/taiga/export_import/serializers/mixins.py index 3006500f..0df95a5f 100644 --- a/taiga/export_import/serializers/mixins.py +++ b/taiga/export_import/serializers/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/serializers/serializers.py b/taiga/export_import/serializers/serializers.py index f4f46e52..1f00fbf7 100644 --- a/taiga/export_import/serializers/serializers.py +++ b/taiga/export_import/serializers/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/services/__init__.py b/taiga/export_import/services/__init__.py index 573d7a70..61980109 100644 --- a/taiga/export_import/services/__init__.py +++ b/taiga/export_import/services/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/services/render.py b/taiga/export_import/services/render.py index cb757dd0..fc79d222 100644 --- a/taiga/export_import/services/render.py +++ b/taiga/export_import/services/render.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/services/store.py b/taiga/export_import/services/store.py index e28353bc..1944912d 100644 --- a/taiga/export_import/services/store.py +++ b/taiga/export_import/services/store.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -700,7 +700,7 @@ def _validate_if_owner_have_enought_space_to_this_project(owner, data): 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_service.has_available_slot_for_new_project( owner, is_private, total_memberships diff --git a/taiga/export_import/tasks.py b/taiga/export_import/tasks.py index 5acb08a2..565bb7c6 100644 --- a/taiga/export_import/tasks.py +++ b/taiga/export_import/tasks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/throttling.py b/taiga/export_import/throttling.py index c96d6c17..972af60a 100644 --- a/taiga/export_import/throttling.py +++ b/taiga/export_import/throttling.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/validators/cache.py b/taiga/export_import/validators/cache.py index d82e943d..e6daf161 100644 --- a/taiga/export_import/validators/cache.py +++ b/taiga/export_import/validators/cache.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/export_import/validators/fields.py b/taiga/export_import/validators/fields.py index e3d33c7a..d93a3677 100644 --- a/taiga/export_import/validators/fields.py +++ b/taiga/export_import/validators/fields.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -26,7 +26,7 @@ from django.contrib.contenttypes.models import ContentType from taiga.base.api import serializers from taiga.base.exceptions import ValidationError -from taiga.base.fields import JsonField +from taiga.base.fields import JSONField from taiga.mdrender.service import render as mdrender from taiga.users import models as users_models @@ -144,7 +144,7 @@ class ProjectRelatedField(serializers.RelatedField): raise ValidationError(_("{}=\"{}\" not found in this project".format(self.slug_field, data))) -class HistoryUserField(JsonField): +class HistoryUserField(JSONField): def from_native(self, data): if data is None: return {} @@ -162,7 +162,7 @@ class HistoryUserField(JsonField): return {"pk": pk, "name": data[1]} -class HistoryValuesField(JsonField): +class HistoryValuesField(JSONField): def from_native(self, data): if data is None: return [] @@ -171,7 +171,7 @@ class HistoryValuesField(JsonField): return data -class HistoryDiffField(JsonField): +class HistoryDiffField(JSONField): def from_native(self, data): if data is None: return [] diff --git a/taiga/export_import/validators/mixins.py b/taiga/export_import/validators/mixins.py index d07334b6..44027c90 100644 --- a/taiga/export_import/validators/mixins.py +++ b/taiga/export_import/validators/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -28,13 +28,13 @@ from taiga.projects.notifications import services as notifications_services from taiga.projects.history import services as history_service from .fields import (UserRelatedField, HistoryUserField, HistoryDiffField, - JsonField, HistoryValuesField, CommentField, FileField) + JSONField, HistoryValuesField, CommentField, FileField) class HistoryExportValidator(validators.ModelValidator): user = HistoryUserField() diff = HistoryDiffField(required=False) - snapshot = JsonField(required=False) + snapshot = JSONField(required=False) values = HistoryValuesField(required=False) comment = CommentField(required=False) delete_comment_date = serializers.DateTimeField(required=False) diff --git a/taiga/export_import/validators/validators.py b/taiga/export_import/validators/validators.py index c821b531..484f2c89 100644 --- a/taiga/export_import/validators/validators.py +++ b/taiga/export_import/validators/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,7 +20,7 @@ from django.utils.translation import ugettext as _ from taiga.base.api import serializers from taiga.base.api import validators -from taiga.base.fields import JsonField, PgArrayField +from taiga.base.fields import JSONField, PgArrayField from taiga.base.exceptions import ValidationError from taiga.projects import models as projects_models @@ -133,7 +133,7 @@ class IssueCustomAttributeExportValidator(validators.ModelValidator): class BaseCustomAttributesValuesExportValidator(validators.ModelValidator): - attributes_values = JsonField(source="attributes_values", required=True) + attributes_values = JSONField(source="attributes_values", required=True) _custom_attribute_model = None _container_field = None @@ -378,7 +378,7 @@ class ProjectExportValidator(WatcheableObjectModelValidatorMixin): issue_statuses = IssueStatusExportValidator(many=True, required=False) priorities = PriorityExportValidator(many=True, required=False) severities = SeverityExportValidator(many=True, required=False) - tags_colors = JsonField(required=False) + tags_colors = JSONField(required=False) creation_template = serializers.SlugRelatedField(slug_field="slug", required=False) default_points = serializers.SlugRelatedField(slug_field="name", required=False) default_us_status = serializers.SlugRelatedField(slug_field="name", required=False) diff --git a/taiga/external_apps/admin.py b/taiga/external_apps/admin.py index 4004c3ea..b10fea5a 100644 --- a/taiga/external_apps/admin.py +++ b/taiga/external_apps/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/api.py b/taiga/external_apps/api.py index 8ded55d5..a337e2c4 100644 --- a/taiga/external_apps/api.py +++ b/taiga/external_apps/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/auth_backends.py b/taiga/external_apps/auth_backends.py index 47eba944..b812087e 100644 --- a/taiga/external_apps/auth_backends.py +++ b/taiga/external_apps/auth_backends.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/models.py b/taiga/external_apps/models.py index 9aeb2087..c87159d5 100644 --- a/taiga/external_apps/models.py +++ b/taiga/external_apps/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/permissions.py b/taiga/external_apps/permissions.py index ad37274b..a415c30f 100644 --- a/taiga/external_apps/permissions.py +++ b/taiga/external_apps/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/serializers.py b/taiga/external_apps/serializers.py index af676b5a..6bfbe921 100644 --- a/taiga/external_apps/serializers.py +++ b/taiga/external_apps/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/services.py b/taiga/external_apps/services.py index c48703a5..c512e805 100644 --- a/taiga/external_apps/services.py +++ b/taiga/external_apps/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/external_apps/validators.py b/taiga/external_apps/validators.py index 6c7de5aa..66467ec6 100644 --- a/taiga/external_apps/validators.py +++ b/taiga/external_apps/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/__init__.py b/taiga/feedback/__init__.py index ba163f3d..c8fc17fd 100644 --- a/taiga/feedback/__init__.py +++ b/taiga/feedback/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/admin.py b/taiga/feedback/admin.py index aac487ae..9a2a2548 100644 --- a/taiga/feedback/admin.py +++ b/taiga/feedback/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/api.py b/taiga/feedback/api.py index 0f573b87..39bb4469 100644 --- a/taiga/feedback/api.py +++ b/taiga/feedback/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/apps.py b/taiga/feedback/apps.py index 6485ac13..20740dee 100644 --- a/taiga/feedback/apps.py +++ b/taiga/feedback/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/models.py b/taiga/feedback/models.py index a75c27a1..b4d4413d 100644 --- a/taiga/feedback/models.py +++ b/taiga/feedback/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/permissions.py b/taiga/feedback/permissions.py index b813c567..a5c4502a 100644 --- a/taiga/feedback/permissions.py +++ b/taiga/feedback/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/routers.py b/taiga/feedback/routers.py index 9f907520..f23822c3 100644 --- a/taiga/feedback/routers.py +++ b/taiga/feedback/routers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/services.py b/taiga/feedback/services.py index 8aec0cfc..34efdd12 100644 --- a/taiga/feedback/services.py +++ b/taiga/feedback/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/feedback/validators.py b/taiga/feedback/validators.py index 7b31ec88..e03fd888 100644 --- a/taiga/feedback/validators.py +++ b/taiga/feedback/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/__init__.py b/taiga/front/sitemaps/__init__.py index 8c7adfa8..00f9f1d6 100644 --- a/taiga/front/sitemaps/__init__.py +++ b/taiga/front/sitemaps/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/base.py b/taiga/front/sitemaps/base.py index 952d754d..c75fb653 100644 --- a/taiga/front/sitemaps/base.py +++ b/taiga/front/sitemaps/base.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/epics.py b/taiga/front/sitemaps/epics.py index 81f391f6..ed7f055f 100644 --- a/taiga/front/sitemaps/epics.py +++ b/taiga/front/sitemaps/epics.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/generics.py b/taiga/front/sitemaps/generics.py index 9ddc51bc..d3db49de 100644 --- a/taiga/front/sitemaps/generics.py +++ b/taiga/front/sitemaps/generics.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/issues.py b/taiga/front/sitemaps/issues.py index 4aeeb100..6b784d3b 100644 --- a/taiga/front/sitemaps/issues.py +++ b/taiga/front/sitemaps/issues.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/milestones.py b/taiga/front/sitemaps/milestones.py index f0b7345a..ecaf7c75 100644 --- a/taiga/front/sitemaps/milestones.py +++ b/taiga/front/sitemaps/milestones.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/projects.py b/taiga/front/sitemaps/projects.py index 77785928..aa91bd3b 100644 --- a/taiga/front/sitemaps/projects.py +++ b/taiga/front/sitemaps/projects.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/tasks.py b/taiga/front/sitemaps/tasks.py index 56f3ee7e..b9314ba5 100644 --- a/taiga/front/sitemaps/tasks.py +++ b/taiga/front/sitemaps/tasks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/users.py b/taiga/front/sitemaps/users.py index cabe9417..d515276f 100644 --- a/taiga/front/sitemaps/users.py +++ b/taiga/front/sitemaps/users.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/userstories.py b/taiga/front/sitemaps/userstories.py index 7c89b0a0..1a5e36ee 100644 --- a/taiga/front/sitemaps/userstories.py +++ b/taiga/front/sitemaps/userstories.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/sitemaps/wiki.py b/taiga/front/sitemaps/wiki.py index e0ec43af..e6ccd4a1 100644 --- a/taiga/front/sitemaps/wiki.py +++ b/taiga/front/sitemaps/wiki.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/templatetags/functions.py b/taiga/front/templatetags/functions.py index 86a7f813..db8a6616 100644 --- a/taiga/front/templatetags/functions.py +++ b/taiga/front/templatetags/functions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/front/urls.py b/taiga/front/urls.py index 77d53dab..393913e3 100644 --- a/taiga/front/urls.py +++ b/taiga/front/urls.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -23,6 +23,8 @@ urls = { "login": "/login", "register": "/register", "forgot-password": "/forgot-password", + "new-project": "/project/new", + "new-project-import": "/project/new/import/{0}", "change-password": "/change-password/{0}", # user.token "change-email": "/change-email/{0}", # user.email_token diff --git a/taiga/hooks/api.py b/taiga/hooks/api.py index baf94e9d..1ec40416 100644 --- a/taiga/hooks/api.py +++ b/taiga/hooks/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/bitbucket/api.py b/taiga/hooks/bitbucket/api.py index 07e2829b..cb296fbd 100644 --- a/taiga/hooks/bitbucket/api.py +++ b/taiga/hooks/bitbucket/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/bitbucket/event_hooks.py b/taiga/hooks/bitbucket/event_hooks.py index 67ffc3fd..7021f61b 100644 --- a/taiga/hooks/bitbucket/event_hooks.py +++ b/taiga/hooks/bitbucket/event_hooks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -80,5 +80,6 @@ class PushEventHook(BaseBitBucketEventHook, BasePushEventHook): "commit_id": commit.get("hash", None), "commit_url": commit.get("links", {}).get('html', {}).get('href'), "commit_message": message.strip(), + "commit_short_message": message.split("\n")[0].strip(), }) return result diff --git a/taiga/hooks/bitbucket/services.py b/taiga/hooks/bitbucket/services.py index b2cb29c7..5c89588a 100644 --- a/taiga/hooks/bitbucket/services.py +++ b/taiga/hooks/bitbucket/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/event_hooks.py b/taiga/hooks/event_hooks.py index 93deb518..6fa574c4 100644 --- a/taiga/hooks/event_hooks.py +++ b/taiga/hooks/event_hooks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -154,7 +154,7 @@ class BasePushEventHook(BaseEventHook): ) _status_change_message = _( "{user_text} changed the status from " - "[{platform} commit]({commit_url} \"See commit '{commit_id} - {commit_message}'\")\n\n" + "[{platform} commit]({commit_url} \"See commit '{commit_id} - {commit_short_message}'\")\n\n" " - Status: **{src_status}** → **{dst_status}**" ) _simple_status_change_message = _( @@ -177,7 +177,7 @@ class BasePushEventHook(BaseEventHook): _status_change_message = _( "This {type_name} has been mentioned by {user_text} " - "in the [{platform} commit]({commit_url} \"See commit '{commit_id} - {commit_message}'\") " + "in the [{platform} commit]({commit_url} \"See commit '{commit_id} - {commit_short_message}'\") " "\"{commit_message}\"" ) _simple_status_change_message = _( diff --git a/taiga/hooks/exceptions.py b/taiga/hooks/exceptions.py index bbe68cc1..d6ced691 100644 --- a/taiga/hooks/exceptions.py +++ b/taiga/hooks/exceptions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/github/api.py b/taiga/hooks/github/api.py index f3c125ee..cd53c065 100644 --- a/taiga/hooks/github/api.py +++ b/taiga/hooks/github/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/github/event_hooks.py b/taiga/hooks/github/event_hooks.py index c4ecc300..6994f528 100644 --- a/taiga/hooks/github/event_hooks.py +++ b/taiga/hooks/github/event_hooks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -81,7 +81,8 @@ class PushEventHook(BaseGitHubEventHook, BasePushEventHook): "user_url": github_user.get('html_url', None), "commit_id": commit.get("id", None), "commit_url": commit.get("url", None), - "commit_message": commit.get("message", None), + "commit_message": commit.get("message").strip(), + "commit_short_message": commit.get("message").split("\n")[0].strip(), }) return result diff --git a/taiga/hooks/github/services.py b/taiga/hooks/github/services.py index e7286d86..1878551d 100644 --- a/taiga/hooks/github/services.py +++ b/taiga/hooks/github/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/gitlab/api.py b/taiga/hooks/gitlab/api.py index 127d7536..c24eb0cb 100644 --- a/taiga/hooks/gitlab/api.py +++ b/taiga/hooks/gitlab/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/gitlab/event_hooks.py b/taiga/hooks/gitlab/event_hooks.py index e7ada033..903daea4 100644 --- a/taiga/hooks/gitlab/event_hooks.py +++ b/taiga/hooks/gitlab/event_hooks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -85,5 +85,6 @@ class PushEventHook(BaseGitLabEventHook, BasePushEventHook): "commit_id": commit.get("id", None), "commit_url": commit.get("url", None), "commit_message": commit.get("message").strip(), + "commit_short_message": commit.get("message").split("\n")[0].strip(), }) return result diff --git a/taiga/hooks/gitlab/services.py b/taiga/hooks/gitlab/services.py index a31352ed..3040fad6 100644 --- a/taiga/hooks/gitlab/services.py +++ b/taiga/hooks/gitlab/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/hooks/gogs/api.py b/taiga/hooks/gogs/api.py index ced551de..eb3ada63 100644 --- a/taiga/hooks/gogs/api.py +++ b/taiga/hooks/gogs/api.py @@ -1,7 +1,7 @@ -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,6 +19,9 @@ from taiga.hooks.api import BaseWebhookApiViewSet from . import event_hooks +import hmac +import hashlib + class GogsViewSet(BaseWebhookApiViewSet): event_hook_classes = { @@ -26,8 +29,6 @@ class GogsViewSet(BaseWebhookApiViewSet): } def _validate_signature(self, project, request): - payload = self._get_payload(request) - if not hasattr(project, "modules_config"): return False @@ -38,7 +39,15 @@ class GogsViewSet(BaseWebhookApiViewSet): if secret is None: return False - return payload.get('secret', None) == secret + signature = request.META.get("HTTP_X_GOGS_SIGNATURE", None) + if not signature: # Old format signature support (before 0.11 version) + payload = self._get_payload(request) + return payload.get('secret', None) == secret + + secret = project.modules_config.config.get("gogs", {}).get("secret", "") + secret = bytes(secret.encode("utf-8")) + mac = hmac.new(secret, msg=request.body, digestmod=hashlib.sha256) + return hmac.compare_digest(mac.hexdigest(), signature) def _get_event_name(self, request): return "push" diff --git a/taiga/hooks/gogs/event_hooks.py b/taiga/hooks/gogs/event_hooks.py index 399fa960..e2007ed5 100644 --- a/taiga/hooks/gogs/event_hooks.py +++ b/taiga/hooks/gogs/event_hooks.py @@ -1,7 +1,7 @@ -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -47,6 +47,7 @@ class PushEventHook(BaseGogsEventHook, BasePushEventHook): "user_url": os.path.join(os.path.dirname(os.path.dirname(project_url)), user_name), "commit_id": commit.get("id", None), "commit_url": commit.get("url", None), - "commit_message": commit.get("message", None), + "commit_message": commit.get("message").strip(), + "commit_short_message": commit.get("message").split("\n")[0].strip(), }) return result diff --git a/taiga/hooks/gogs/migrations/logo.png b/taiga/hooks/gogs/migrations/logo.png index 384a58d2..34385535 100644 Binary files a/taiga/hooks/gogs/migrations/logo.png and b/taiga/hooks/gogs/migrations/logo.png differ diff --git a/taiga/hooks/gogs/services.py b/taiga/hooks/gogs/services.py index 40d06fab..dd3fa03b 100644 --- a/taiga/hooks/gogs/services.py +++ b/taiga/hooks/gogs/services.py @@ -1,7 +1,7 @@ -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/importers/__init__.py b/taiga/importers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/importers/api.py b/taiga/importers/api.py new file mode 100644 index 00000000..6afaedfb --- /dev/null +++ b/taiga/importers/api.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Taiga Agile LLC +# 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 taiga.base.api import viewsets +from taiga.base.decorators import list_route + + +class BaseImporterViewSet(viewsets.ViewSet): + @list_route(methods=["GET"]) + def list_users(self, request, *args, **kwargs): + raise NotImplementedError + + @list_route(methods=["GET"]) + def list_projects(self, request, *args, **kwargs): + raise NotImplementedError + + @list_route(methods=["POST"]) + def import_project(self, request, *args, **kwargs): + raise NotImplementedError + + @list_route(methods=["GET"]) + def auth_url(self, request, *args, **kwargs): + raise NotImplementedError + + @list_route(methods=["POST"]) + def authorize(self, request, *args, **kwargs): + raise NotImplementedError diff --git a/taiga/importers/asana/api.py b/taiga/importers/asana/api.py new file mode 100644 index 00000000..3c19b896 --- /dev/null +++ b/taiga/importers/asana/api.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Taiga Agile LLC +# 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.translation import ugettext as _ +from django.conf import settings + +from taiga.base.api import viewsets +from taiga.base import response +from taiga.base import exceptions as exc +from taiga.base.decorators import list_route +from taiga.users.services import get_user_photo_url +from taiga.users.gravatar import get_user_gravatar_id + +from taiga.importers import permissions, exceptions +from taiga.importers.services import resolve_users_bindings +from .importer import AsanaImporter +from . import tasks + + +class AsanaImporterViewSet(viewsets.ViewSet): + permission_classes = (permissions.ImporterPermission,) + + @list_route(methods=["POST"]) + def list_users(self, request, *args, **kwargs): + self.check_permissions(request, "list_users", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + importer = AsanaImporter(request.user, token) + + try: + users = importer.list_users(project_id) + except exceptions.InvalidRequest: + raise exc.BadRequest(_('Invalid Asana API request')) + except exceptions.FailedRequest: + raise exc.BadRequest(_('Failed to make the request to Asana API')) + + for user in users: + if user['detected_user']: + user['user'] = { + 'id': user['detected_user'].id, + 'full_name': user['detected_user'].get_full_name(), + 'gravatar_id': get_user_gravatar_id(user['detected_user']), + 'photo': get_user_photo_url(user['detected_user']), + } + del(user['detected_user']) + return response.Ok(users) + + @list_route(methods=["POST"]) + def list_projects(self, request, *args, **kwargs): + self.check_permissions(request, "list_projects", None) + token = request.DATA.get('token', None) + importer = AsanaImporter(request.user, token) + try: + projects = importer.list_projects() + except exceptions.InvalidRequest: + raise exc.BadRequest(_('Invalid Asana API request')) + except exceptions.FailedRequest: + raise exc.BadRequest(_('Failed to make the request to Asana API')) + return response.Ok(projects) + + @list_route(methods=["POST"]) + def import_project(self, request, *args, **kwargs): + self.check_permissions(request, "import_project", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + options = { + "name": request.DATA.get('name', None), + "description": request.DATA.get('description', None), + "template": request.DATA.get('template', "scrum"), + "users_bindings": resolve_users_bindings(request.DATA.get("users_bindings", {})), + "keep_external_reference": request.DATA.get("keep_external_reference", False), + "is_private": request.DATA.get("is_private", False), + } + + if settings.CELERY_ENABLED: + task = tasks.import_project.delay(request.user.id, token, project_id, options) + return response.Accepted({"task_id": task.id}) + + importer = AsanaImporter(request.user, token) + project = importer.import_project(project_id, options) + project_data = { + "slug": project.slug, + "my_permissions": ["view_us"], + "is_backlog_activated": project.is_backlog_activated, + "is_kanban_activated": project.is_kanban_activated, + } + + return response.Ok(project_data) + + @list_route(methods=["GET"]) + def auth_url(self, request, *args, **kwargs): + self.check_permissions(request, "auth_url", None) + + url = AsanaImporter.get_auth_url( + settings.IMPORTERS.get('asana', {}).get('app_id', None), + settings.IMPORTERS.get('asana', {}).get('app_secret', None), + settings.IMPORTERS.get('asana', {}).get('callback_url', None) + ) + + return response.Ok({"url": url}) + + @list_route(methods=["POST"]) + def authorize(self, request, *args, **kwargs): + self.check_permissions(request, "authorize", None) + + code = request.DATA.get('code', None) + if code is None: + raise exc.BadRequest(_("Code param needed")) + + try: + asana_token = AsanaImporter.get_access_token( + code, + settings.IMPORTERS.get('asana', {}).get('app_id', None), + settings.IMPORTERS.get('asana', {}).get('app_secret', None), + settings.IMPORTERS.get('asana', {}).get('callback_url', None) + ) + except exceptions.InvalidRequest: + raise exc.BadRequest(_('Invalid Asana API request')) + except exceptions.FailedRequest: + raise exc.BadRequest(_('Failed to make the request to Asana API')) + + return response.Ok({"token": asana_token}) diff --git a/taiga/importers/asana/importer.py b/taiga/importers/asana/importer.py new file mode 100644 index 00000000..039754e0 --- /dev/null +++ b/taiga/importers/asana/importer.py @@ -0,0 +1,359 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import requests +import asana +from django.core.files.base import ContentFile +from django.contrib.contenttypes.models import ContentType + +from taiga.projects.models import Project, ProjectTemplate +from taiga.projects.userstories.models import UserStory +from taiga.projects.tasks.models import Task +from taiga.projects.attachments.models import Attachment +from taiga.projects.history.services import take_snapshot +from taiga.projects.history.models import HistoryEntry +from taiga.projects.custom_attributes.models import UserStoryCustomAttribute, TaskCustomAttribute +from taiga.users.models import User +from taiga.timeline.rebuilder import rebuild_timeline +from taiga.timeline.models import Timeline +from taiga.importers import exceptions +from taiga.importers import services as import_service + + +class AsanaClient(asana.Client): + def request(self, method, path, **options): + try: + return super().request(method, path, **options) + except asana.error.AsanaError: + raise exceptions.InvalidRequest() + except Exception as e: + raise exceptions.FailedRequest() + + +class AsanaImporter: + def __init__(self, user, token, import_closed_data=False): + self._import_closed_data = import_closed_data + self._user = user + self._client = AsanaClient.oauth(token=token) + + def list_projects(self): + projects = [] + for ws in self._client.workspaces.find_all(): + for project in self._client.projects.find_all(workspace=ws['id']): + project = self._client.projects.find_by_id(project['id']) + projects.append({ + "id": project['id'], + "name": "{}/{}".format(ws['name'], project['name']), + "description": project['notes'], + "is_private": True, + }) + return projects + + def list_users(self, project_id): + users = [] + for ws in self._client.workspaces.find_all(): + for user in self._client.users.find_by_workspace(ws['id'], fields=["id", "name", "email", "photo"]): + users.append({ + "id": user["id"], + "full_name": user['name'], + "detected_user": self._get_user(user), + "avatar": user.get('photo', None) and user['photo'].get('image_60x60', None) + }) + return users + + def _get_user(self, user, default=None): + if not user: + return default + + try: + return User.objects.get(email=user['email']) + except User.DoesNotExist: + pass + + return default + + def import_project(self, project_id, options): + project = self._client.projects.find_by_id(project_id) + taiga_project = self._import_project_data(project, options) + self._import_user_stories_data(taiga_project, project, options) + Timeline.objects.filter(project=taiga_project).delete() + rebuild_timeline(None, None, taiga_project.id) + return taiga_project + + def _import_project_data(self, project, options): + users_bindings = options.get('users_bindings', {}) + project_template = ProjectTemplate.objects.get(slug=options.get('template', 'scrum')) + + project_template.us_statuses = [] + project_template.us_statuses.append({ + "name": "Open", + "slug": "open", + "is_closed": False, + "is_archived": False, + "color": "#ff8a84", + "wip_limit": None, + "order": 1, + }) + project_template.us_statuses.append({ + "name": "Closed", + "slug": "closed", + "is_closed": True, + "is_archived": False, + "color": "#669900", + "wip_limit": None, + "order": 2, + }) + project_template.default_options["us_status"] = "Open" + + project_template.task_statuses = [] + project_template.task_statuses.append({ + "name": "Open", + "slug": "open", + "is_closed": False, + "color": "#ff8a84", + "order": 1, + }) + project_template.task_statuses.append({ + "name": "Closed", + "slug": "closed", + "is_closed": True, + "color": "#669900", + "order": 2, + }) + project_template.default_options["task_status"] = "Open" + + project_template.roles.append({ + "name": "Asana", + "slug": "asana", + "computable": False, + "permissions": project_template.roles[0]['permissions'], + "order": 70, + }) + + tags_colors = [] + for tag in self._client.tags.find_by_workspace(project['workspace']['id'], fields=["name", "color"]): + name = tag['name'].lower() + color = tag['color'] + tags_colors.append([name, color]) + + taiga_project = Project.objects.create( + name=options.get('name', None) or project['name'], + description=options.get('description', None) or project['notes'], + owner=self._user, + tags_colors=tags_colors, + creation_template=project_template, + is_private=options.get('is_private', False) + ) + + import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "asana") + + UserStoryCustomAttribute.objects.create( + name="Due date", + description="Due date", + type="date", + order=1, + project=taiga_project + ) + + TaskCustomAttribute.objects.create( + name="Due date", + description="Due date", + type="date", + order=1, + project=taiga_project + ) + + return taiga_project + + def _import_user_stories_data(self, taiga_project, project, options): + users_bindings = options.get('users_bindings', {}) + tasks = self._client.tasks.find_by_project( + project['id'], + fields=["parent", "tags", "name", "notes", "tags.name", + "completed", "followers", "modified_at", "created_at", + "project", "due_on"] + ) + due_date_field = taiga_project.userstorycustomattributes.first() + + for task in tasks: + if task['parent']: + continue + + tags = [] + for tag in task['tags']: + tags.append(tag['name'].lower()) + + assigned_to = users_bindings.get(task.get('assignee', {}).get('id', None)) or None + + external_reference = None + if options.get('keep_external_reference', False): + external_url = "https://app.asana.com/0/{}/{}".format( + project['id'], + task['id'], + ) + external_reference = ["asana", external_url] + + us = UserStory.objects.create( + project=taiga_project, + owner=self._user, + assigned_to=assigned_to, + status=taiga_project.us_statuses.get(slug="closed" if task['completed'] else "open"), + kanban_order=task['id'], + sprint_order=task['id'], + backlog_order=task['id'], + subject=task['name'], + description=task.get('notes', ""), + tags=tags, + external_reference=external_reference + ) + + if task['due_on']: + us.custom_attributes_values.attributes_values = {due_date_field.id: task['due_on']} + us.custom_attributes_values.save() + + for follower in task['followers']: + follower_user = users_bindings.get(follower['id'], None) + if follower_user is not None: + us.add_watcher(follower_user) + + UserStory.objects.filter(id=us.id).update( + modified_date=task['modified_at'], + created_date=task['created_at'] + ) + + subtasks = self._client.tasks.subtasks( + task['id'], + fields=["parent", "tags", "name", "notes", "tags.name", + "completed", "followers", "modified_at", "created_at", + "due_on"] + ) + for subtask in subtasks: + self._import_task_data(taiga_project, us, project, subtask, options) + + take_snapshot(us, comment="", user=None, delete=False) + self._import_history(us, task, options) + self._import_attachments(us, task, options) + + def _import_task_data(self, taiga_project, us, assana_project, task, options): + users_bindings = options.get('users_bindings', {}) + tags = [] + for tag in task['tags']: + tags.append(tag['name'].lower()) + due_date_field = taiga_project.taskcustomattributes.first() + + assigned_to = users_bindings.get(task.get('assignee', {}).get('id', None)) or None + + external_reference = None + if options.get('keep_external_reference', False): + external_url = "https://app.asana.com/0/{}/{}".format( + assana_project['id'], + task['id'], + ) + external_reference = ["asana", external_url] + + taiga_task = Task.objects.create( + project=taiga_project, + user_story=us, + owner=self._user, + assigned_to=assigned_to, + status=taiga_project.task_statuses.get(slug="closed" if task['completed'] else "open"), + us_order=task['id'], + taskboard_order=task['id'], + subject=task['name'], + description=task.get('notes', ""), + tags=tags, + external_reference=external_reference + ) + + if task['due_on']: + taiga_task.custom_attributes_values.attributes_values = {due_date_field.id: task['due_on']} + taiga_task.custom_attributes_values.save() + + for follower in task['followers']: + follower_user = users_bindings.get(follower['id'], None) + if follower_user is not None: + taiga_task.add_watcher(follower_user) + + Task.objects.filter(id=taiga_task.id).update( + modified_date=task['modified_at'], + created_date=task['created_at'] + ) + + subtasks = self._client.tasks.subtasks( + task['id'], + fields=["parent", "tags", "name", "notes", "tags.name", + "completed", "followers", "modified_at", "created_at", + "due_on"] + ) + for subtask in subtasks: + self._import_task_data(taiga_project, us, assana_project, subtask, options) + + take_snapshot(taiga_task, comment="", user=None, delete=False) + self._import_history(taiga_task, task, options) + self._import_attachments(taiga_task, task, options) + + def _import_history(self, obj, task, options): + users_bindings = options.get('users_bindings', {}) + stories = self._client.stories.find_by_task(task['id']) + for story in stories: + if story['type'] == "comment": + snapshot = take_snapshot( + obj, + comment=story['text'], + user=users_bindings.get(story['created_by']['id'], User(full_name=story['created_by']['name'])), + delete=False + ) + HistoryEntry.objects.filter(id=snapshot.id).update(created_at=story['created_at']) + + def _import_attachments(self, obj, task, options): + attachments = self._client.attachments.find_by_task( + task['id'], + fields=['name', 'download_url', 'created_at'] + ) + for attachment in attachments: + data = requests.get(attachment['download_url']) + att = Attachment( + owner=self._user, + project=obj.project, + content_type=ContentType.objects.get_for_model(obj), + object_id=obj.id, + name=attachment['name'], + size=len(data.content), + created_date=attachment['created_at'], + is_deprecated=False, + ) + att.attached_file.save(attachment['name'], ContentFile(data.content), save=True) + + @classmethod + def get_auth_url(cls, client_id, client_secret, callback_url=None): + client = AsanaClient.oauth( + client_id=client_id, + client_secret=client_secret, + redirect_uri=callback_url + ) + (url, state) = client.session.authorization_url() + return url + + @classmethod + def get_access_token(cls, code, client_id, client_secret, callback_url=None): + client = AsanaClient.oauth( + client_id=client_id, + client_secret=client_secret, + redirect_uri=callback_url + ) + return client.session.fetch_token(code=code) diff --git a/taiga/importers/asana/tasks.py b/taiga/importers/asana/tasks.py new file mode 100644 index 00000000..bd707b0d --- /dev/null +++ b/taiga/importers/asana/tasks.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import logging +import sys + +from django.utils.translation import ugettext as _ + +from taiga.base.mails import mail_builder +from taiga.users.models import User +from taiga.celery import app +from .importer import AsanaImporter + +logger = logging.getLogger('taiga.importers.asana') + + +@app.task(bind=True) +def import_project(self, user_id, token, project_id, options): + user = User.objects.get(id=user_id) + importer = AsanaImporter(user, token) + try: + project = importer.import_project(project_id, options) + except Exception as e: + # Error + ctx = { + "user": user, + "error_subject": _("Error importing Asana project"), + "error_message": _("Error importing Asana project"), + "project": project_id, + "exception": e + } + email = mail_builder.importer_import_error(user, ctx) + email.send() + logger.error('Error importing Asana project %s (by %s)', project_id, user, exc_info=sys.exc_info()) + else: + ctx = { + "project": project, + "user": user, + } + email = mail_builder.asana_import_success(user, ctx) + email.send() diff --git a/taiga/importers/exceptions.py b/taiga/importers/exceptions.py new file mode 100644 index 00000000..0646095d --- /dev/null +++ b/taiga/importers/exceptions.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 InvalidRequest(Exception): + pass + +class InvalidAuthResult(Exception): + pass + +class FailedRequest(Exception): + pass diff --git a/taiga/importers/github/api.py b/taiga/importers/github/api.py new file mode 100644 index 00000000..56a0d061 --- /dev/null +++ b/taiga/importers/github/api.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Taiga Agile LLC +# 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.translation import ugettext as _ +from django.conf import settings + +from taiga.base.api import viewsets +from taiga.base import response +from taiga.base import exceptions as exc +from taiga.base.decorators import list_route +from taiga.users.services import get_user_photo_url +from taiga.users.gravatar import get_user_gravatar_id + +from taiga.importers import permissions +from taiga.importers import exceptions +from taiga.importers.services import resolve_users_bindings +from .importer import GithubImporter +from . import tasks + + +class GithubImporterViewSet(viewsets.ViewSet): + permission_classes = (permissions.ImporterPermission,) + + @list_route(methods=["POST"]) + def list_users(self, request, *args, **kwargs): + self.check_permissions(request, "list_users", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + importer = GithubImporter(request.user, token) + users = importer.list_users(project_id) + for user in users: + if user['detected_user']: + user['user'] = { + 'id': user['detected_user'].id, + 'full_name': user['detected_user'].get_full_name(), + 'gravatar_id': get_user_gravatar_id(user['detected_user']), + 'photo': get_user_photo_url(user['detected_user']), + } + del(user['detected_user']) + return response.Ok(users) + + @list_route(methods=["POST"]) + def list_projects(self, request, *args, **kwargs): + self.check_permissions(request, "list_projects", None) + token = request.DATA.get('token', None) + importer = GithubImporter(request.user, token) + projects = importer.list_projects() + return response.Ok(projects) + + @list_route(methods=["POST"]) + def import_project(self, request, *args, **kwargs): + self.check_permissions(request, "import_project", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + template = request.DATA.get('template', "scrum") + items_type = "user_stories" + if template == "issues": + items_type = "issues" + template = "scrum" + + options = { + "name": request.DATA.get('name', None), + "description": request.DATA.get('description', None), + "template": template, + "type": items_type, + "users_bindings": resolve_users_bindings(request.DATA.get("users_bindings", {})), + "keep_external_reference": request.DATA.get("keep_external_reference", False), + "is_private": request.DATA.get("is_private", False), + } + + if settings.CELERY_ENABLED: + task = tasks.import_project.delay(request.user.id, token, project_id, options) + return response.Accepted({"task_id": task.id}) + + importer = GithubImporter(request.user, token) + project = importer.import_project(project_id, options) + project_data = { + "slug": project.slug, + "my_permissions": ["view_us"], + "is_backlog_activated": project.is_backlog_activated, + "is_kanban_activated": project.is_kanban_activated, + } + + return response.Ok(project_data) + + @list_route(methods=["GET"]) + def auth_url(self, request, *args, **kwargs): + self.check_permissions(request, "auth_url", None) + callback_uri = request.QUERY_PARAMS.get('uri') + url = GithubImporter.get_auth_url( + settings.IMPORTERS.get('github', {}).get('client_id', None), + callback_uri + ) + return response.Ok({"url": url}) + + @list_route(methods=["POST"]) + def authorize(self, request, *args, **kwargs): + self.check_permissions(request, "authorize", None) + + code = request.DATA.get('code', None) + if code is None: + raise exc.BadRequest(_("Code param needed")) + + try: + token = GithubImporter.get_access_token( + settings.IMPORTERS.get('github', {}).get('client_id', None), + settings.IMPORTERS.get('github', {}).get('client_secret', None), + code + ) + return response.Ok({ + "token": token + }) + except exceptions.InvalidAuthResult: + raise exc.BadRequest(_("Invalid auth data")) + except exceptions.FailedRequest: + raise exc.BadRequest(_("Third party service failing")) diff --git a/taiga/importers/github/importer.py b/taiga/importers/github/importer.py new file mode 100644 index 00000000..dea516cb --- /dev/null +++ b/taiga/importers/github/importer.py @@ -0,0 +1,618 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import requests +from urllib.parse import parse_qsl +from django.core.files.base import ContentFile + +from taiga.projects.models import Project, ProjectTemplate +from taiga.projects.references.models import recalc_reference_counter +from taiga.projects.userstories.models import UserStory +from taiga.projects.issues.models import Issue +from taiga.projects.milestones.models import Milestone +from taiga.projects.history.services import take_snapshot +from taiga.projects.history.services import (make_diff_from_dicts, + make_diff_values, + make_key_from_model_object, + get_typename_for_model_class, + FrozenDiff) +from taiga.projects.history.models import HistoryEntry +from taiga.projects.history.choices import HistoryType +from taiga.timeline.rebuilder import rebuild_timeline +from taiga.timeline.models import Timeline +from taiga.users.models import User, AuthData + +from taiga.importers.exceptions import InvalidAuthResult, FailedRequest +from taiga.importers import services as import_service + + +class GithubClient: + def __init__(self, token): + self.api_url = "https://api.github.com/{}" + self.token = token + + def get(self, uri_path, query_params=None): + headers = { + "Content-Type": "application/json", + "X-GitHub-Media-Type": "github.v3" + } + if self.token: + headers['Authorization'] = 'token {}'.format(self.token) + + if query_params is None: + query_params = {} + + if uri_path[0] == '/': + uri_path = uri_path[1:] + url = self.api_url.format(uri_path) + + response = requests.get(url, params=query_params, headers=headers) + + if response.status_code == 401: + raise Exception("Unauthorized: %s at %s" % (response.text, url), response) + if response.status_code != 200: + raise Exception("Resource Unavailable: %s at %s" % (response.text, url), response) + + return response.json() + + +class GithubImporter: + def __init__(self, user, token, import_closed_data=False): + self._import_closed_data = import_closed_data + self._user = user + self._client = GithubClient(token) + self._me = self._client.get("/user") + + def list_projects(self): + projects = [] + page = 1 + while True: + repos = self._client.get("/user/repos", { + "sort": "full_name", + "page": page, + "per_page": 100 + }) + page += 1 + + for repo in repos: + projects.append({ + "id": repo['full_name'], + "name": repo['full_name'], + "description": repo['description'], + "is_private": repo['private'], + }) + + if len(repos) < 100: + break + return projects + + def list_users(self, project_full_name): + collaborators = self._client.get("/repos/{}/collaborators".format(project_full_name)) + collaborators = [self._client.get("/users/{}".format(u['login'])) for u in collaborators] + return [{"id": u['id'], + "username": u['login'], + "full_name": u.get('name', u['login']), + "avatar": u.get('avatar_url', None), + "detected_user": self._get_user(u) } for u in collaborators] + + def _get_user(self, user, default=None): + if not user: + return default + + try: + return AuthData.objects.get(key="github", value=user['id']).user + except AuthData.DoesNotExist: + pass + + try: + return User.objects.get(email=user.get('email', "not-valid")) + except User.DoesNotExist: + pass + + return default + + def import_project(self, project_full_name, options={"keep_external_reference": False, "template": "kanban", "type": "user_stories"}): + repo = self._client.get('/repos/{}'.format(project_full_name)) + project = self._import_project_data(repo, options) + if options.get('type', None) == "user_stories": + self._import_user_stories_data(project, repo, options) + elif options.get('type', None) == "issues": + self._import_issues_data(project, repo, options) + self._import_comments(project, repo, options) + self._import_history(project, repo, options) + Timeline.objects.filter(project=project).delete() + rebuild_timeline(None, None, project.id) + recalc_reference_counter(project) + return project + + def _import_project_data(self, repo, options): + users_bindings = options.get('users_bindings', {}) + project_template = ProjectTemplate.objects.get(slug=options['template']) + + if options['type'] == "user_stories": + project_template.us_statuses = [] + project_template.us_statuses.append({ + "name": "Open", + "slug": "open", + "is_closed": False, + "is_archived": False, + "color": "#ff8a84", + "wip_limit": None, + "order": 1, + }) + project_template.us_statuses.append({ + "name": "Closed", + "slug": "closed", + "is_closed": True, + "is_archived": False, + "color": "#669900", + "wip_limit": None, + "order": 2, + }) + project_template.default_options["us_status"] = "Open" + elif options['type'] == "issues": + project_template.issue_statuses = [] + project_template.issue_statuses.append({ + "name": "Open", + "slug": "open", + "is_closed": False, + "color": "#ff8a84", + "order": 1, + }) + project_template.issue_statuses.append({ + "name": "Closed", + "slug": "closed", + "is_closed": True, + "color": "#669900", + "order": 2, + }) + project_template.default_options["issue_status"] = "Open" + + project_template.roles.append({ + "name": "Github", + "slug": "github", + "computable": False, + "permissions": project_template.roles[0]['permissions'], + "order": 70, + }) + + tags_colors = [] + for label in self._client.get("/repos/{}/labels".format(repo['full_name'])): + name = label['name'].lower() + color = "#{}".format(label['color']) + tags_colors.append([name, color]) + + project = Project.objects.create( + name=options.get('name', None) or repo['full_name'], + description=options.get('description', None) or repo['description'], + owner=self._user, + tags_colors=tags_colors, + creation_template=project_template, + is_private=options.get('is_private', False), + ) + + if 'organization' in repo and repo['organization'].get('avatar_url', None): + data = requests.get(repo['organization']['avatar_url']) + project.logo.save("logo.png", ContentFile(data.content), save=True) + + import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "github") + + for milestone in self._client.get("/repos/{}/milestones".format(repo['full_name'])): + taiga_milestone = Milestone.objects.create( + name=milestone['title'], + owner=users_bindings.get(milestone.get('creator', {}).get('id', None), self._user), + project=project, + estimated_start=milestone['created_at'][:10], + estimated_finish=milestone['due_on'][:10], + ) + Milestone.objects.filter(id=taiga_milestone.id).update( + created_date=milestone['created_at'], + modified_date=milestone['updated_at'], + ) + return project + + def _import_user_stories_data(self, project, repo, options): + users_bindings = options.get('users_bindings', {}) + + page = 1 + while True: + issues = self._client.get("/repos/{}/issues".format(repo['full_name']), { + "state": "all", + "sort": "created", + "direction": "asc", + "page": page, + "per_page": 100 + }) + page += 1 + for issue in issues: + tags = [] + for label in issue['labels']: + tags.append(label['name'].lower()) + + assigned_to = users_bindings.get(issue['assignee']['id'] if issue['assignee'] else None, None) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["github", issue['html_url']] + + us = UserStory.objects.create( + ref=issue['number'], + project=project, + owner=users_bindings.get(issue['user']['id'], self._user), + milestone=project.milestones.get(name=issue['milestone']['title']) if issue['milestone'] else None, + assigned_to=assigned_to, + status=project.us_statuses.get(slug=issue['state']), + kanban_order=issue['number'], + sprint_order=issue['number'], + backlog_order=issue['number'], + subject=issue['title'], + description=issue.get("body", "") or "", + tags=tags, + external_reference=external_reference, + modified_date=issue['updated_at'], + created_date=issue['created_at'], + ) + + assignees = issue.get('assignees', []) + if len(assignees) > 1: + for assignee in assignees: + if assignee['id'] != issue.get('assignee', {}).get('id', None): + assignee_user = users_bindings.get(assignee['id'], None) + if assignee_user is not None: + us.add_watcher(assignee_user) + + UserStory.objects.filter(id=us.id).update( + ref=issue['number'], + modified_date=issue['updated_at'], + created_date=issue['created_at'] + ) + + take_snapshot(us, comment="", user=None, delete=False) + + if len(issues) < 100: + break + + def _import_issues_data(self, project, repo, options): + users_bindings = options.get('users_bindings', {}) + + page = 1 + while True: + issues = self._client.get("/repos/{}/issues".format(repo['full_name']), { + "state": "all", + "sort": "created", + "direction": "asc", + "page": page, + "per_page": 100 + }) + page += 1 + for issue in issues: + tags = [] + for label in issue['labels']: + tags.append(label['name'].lower()) + + assigned_to = users_bindings.get(issue['assignee']['id'] if issue['assignee'] else None, None) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["github", issue['html_url']] + + taiga_issue = Issue.objects.create( + ref=issue['number'], + project=project, + owner=users_bindings.get(issue['user']['id'], self._user), + assigned_to=assigned_to, + status=project.issue_statuses.get(slug=issue['state']), + subject=issue['title'], + description=issue.get('body', "") or "", + tags=tags, + external_reference=external_reference, + modified_date=issue['updated_at'], + created_date=issue['created_at'], + ) + + assignees = issue.get('assignees', []) + if len(assignees) > 1: + for assignee in assignees: + if assignee['id'] != issue.get('assignee', {}).get('id', None): + assignee_user = users_bindings.get(assignee['id'], None) + if assignee_user is not None: + taiga_issue.add_watcher(assignee_user) + + Issue.objects.filter(id=taiga_issue.id).update( + ref=issue['number'], + modified_date=issue['updated_at'], + created_date=issue['created_at'] + ) + + take_snapshot(taiga_issue, comment="", user=None, delete=False) + + if len(issues) < 100: + break + + def _import_comments(self, project, repo, options): + users_bindings = options.get('users_bindings', {}) + + page = 1 + while True: + comments = self._client.get("/repos/{}/issues/comments".format(repo['full_name']), { + "page": page, + "per_page": 100 + }) + page += 1 + + for comment in comments: + issue_id = comment['issue_url'].split("/")[-1] + if options.get('type', None) == "user_stories": + obj = UserStory.objects.get(project=project, ref=issue_id) + elif options.get('type', None) == "issues": + obj = Issue.objects.get(project=project, ref=issue_id) + + snapshot = take_snapshot( + obj, + comment=comment['body'], + user=users_bindings.get(comment['user']['id'], User(full_name=comment['user'].get('name', None) or comment['user']['login'])), + delete=False + ) + HistoryEntry.objects.filter(id=snapshot.id).update(created_at=comment['created_at']) + + if len(comments) < 100: + break + + def _import_history(self, project, repo, options): + cumulative_data = {} + page = 1 + all_events = [] + while True: + events = self._client.get("/repos/{}/issues/events".format(repo['full_name']), { + "page": page, + "per_page": 100 + }) + page += 1 + all_events = all_events + events + + if len(events) < 100: + break + + for event in sorted(all_events, key=lambda x: x['id']): + if options.get('type', None) == "user_stories": + obj = UserStory.objects.get(project=project, ref=event['issue']['number']) + elif options.get('type', None) == "issues": + obj = Issue.objects.get(project=project, ref=event['issue']['number']) + + if event['issue']['number'] in cumulative_data: + obj_cumulative_data = cumulative_data[event['issue']['number']] + else: + obj_cumulative_data = { + "tags": set(), + "assigned_to": None, + "assigned_to_github_id": None, + "assigned_to_name": None, + "milestone": None, + } + cumulative_data[event['issue']['number']] = obj_cumulative_data + self._import_event(obj, event, options, obj_cumulative_data) + + def _import_event(self, obj, event, options, cumulative_data): + typename = get_typename_for_model_class(type(obj)) + key = make_key_from_model_object(obj) + event_data = self._transform_event_data(obj, event, options, cumulative_data) + if event_data is None: + return + + change_old = event_data['change_old'] + change_new = event_data['change_new'] + user = event_data['user'] + + diff = make_diff_from_dicts(change_old, change_new) + fdiff = FrozenDiff(key, diff, {}) + values = make_diff_values(typename, fdiff) + values.update(event_data['update_values']) + entry = HistoryEntry.objects.create( + user=user, + project_id=obj.project.id, + key=key, + type=HistoryType.change, + snapshot=None, + diff=fdiff.diff, + values=values, + comment="", + comment_html="", + is_hidden=False, + is_snapshot=False, + ) + HistoryEntry.objects.filter(id=entry.id).update(created_at=event['created_at']) + return HistoryEntry.objects.get(id=entry.id) + + def _transform_event_data(self, obj, event, options, cumulative_data): + users_bindings = options.get('users_bindings', {}) + + ignored_events = ["committed", "cross-referenced", "head_ref_deleted", + "head_ref_restored", "locked", "unlocked", "merged", + "referenced", "mentioned", "subscribed", + "unsubscribed"] + + if event['event'] in ignored_events: + return None + + user = {"pk": None, "name": event['actor'].get('name', event['actor']['login'])} + taiga_user = users_bindings.get(event['actor']['id'], None) if event['actor'] else None + if taiga_user: + user = {"pk": taiga_user.id, "name": taiga_user.get_full_name()} + + result = { + "change_old": {}, + "change_new": {}, + "user": user, + "update_values": {}, + } + + if event['event'] == "renamed": + result['change_old']["subject"] = event['rename']['from'] + result['change_new']["subject"] = event['rename']['to'] + elif event['event'] == "reopened": + if isinstance(obj, Issue): + result['change_old']["status"] = obj.project.issue_statuses.get(name='Closed').id + result['change_new']["status"] = obj.project.issue_statuses.get(name='Open').id + elif isinstance(obj, UserStory): + result['change_old']["status"] = obj.project.us_statuses.get(name='Closed').id + result['change_new']["status"] = obj.project.us_statuses.get(name='Open').id + elif event['event'] == "closed": + if isinstance(obj, Issue): + result['change_old']["status"] = obj.project.issue_statuses.get(name='Open').id + result['change_new']["status"] = obj.project.issue_statuses.get(name='Closed').id + elif isinstance(obj, UserStory): + result['change_old']["status"] = obj.project.us_statuses.get(name='Open').id + result['change_new']["status"] = obj.project.us_statuses.get(name='Closed').id + elif event['event'] == "assigned": + AssignedEventHandler(result, cumulative_data, users_bindings).handle(event) + elif event['event'] == "unassigned": + UnassignedEventHandler(result, cumulative_data, users_bindings).handle(event) + elif event['event'] == "demilestoned": + if isinstance(obj, UserStory): + try: + result['change_old']["milestone"] = obj.project.milestones.get(name=event['milestone']['title']).id + except Milestone.DoesNotExist: + result['change_old']["milestone"] = 0 + result['update_values'] = {"milestone": {"0": event['milestone']['title']}} + result['change_new']["milestone"] = None + cumulative_data['milestone'] = None + elif event['event'] == "milestoned": + if isinstance(obj, UserStory): + result['update_values']["milestone"] = {} + if cumulative_data['milestone'] is not None: + result['update_values']['milestone'][str(cumulative_data['milestone'])] = cumulative_data['milestone_name'] + result['change_old']["milestone"] = cumulative_data['milestone'] + try: + taiga_milestone = obj.project.milestones.get(name=event['milestone']['title']) + cumulative_data["milestone"] = taiga_milestone.id + cumulative_data['milestone_name'] = taiga_milestone.name + except Milestone.DoesNotExist: + if cumulative_data['milestone'] == 0: + cumulative_data['milestone'] = -1 + else: + cumulative_data['milestone'] = 0 + cumulative_data['milestone_name'] = event['milestone']['title'] + result['change_new']["milestone"] = cumulative_data['milestone'] + result['update_values']['milestone'][str(cumulative_data['milestone'])] = cumulative_data['milestone_name'] + elif event['event'] == "labeled": + result['change_old']["tags"] = list(cumulative_data['tags']) + cumulative_data['tags'].add(event['label']['name'].lower()) + result['change_new']["tags"] = list(cumulative_data['tags']) + elif event['event'] == "unlabeled": + result['change_old']["tags"] = list(cumulative_data['tags']) + if event['label']['name'].lower() in cumulative_data['tags']: + cumulative_data['tags'].remove(event['label']['name'].lower()) + result['change_new']["tags"] = list(cumulative_data['tags']) + + return result + + @classmethod + def get_auth_url(cls, client_id, callback_uri=None): + if callback_uri is None: + return "https://github.com/login/oauth/authorize?client_id={}&scope=user,repo".format(client_id) + return "https://github.com/login/oauth/authorize?client_id={}&scope=user,repo&redirect_uri={}".format(client_id, callback_uri) + + @classmethod + def get_access_token(cls, client_id, client_secret, code): + try: + result = requests.post("https://github.com/login/oauth/access_token", { + "client_id": client_id, + "client_secret": client_secret, + "code": code, + }) + except Exception: + raise FailedRequest() + + if result.status_code > 299: + raise InvalidAuthResult() + else: + try: + return dict(parse_qsl(result.content))[b'access_token'].decode('utf-8') + except: + raise InvalidAuthResult() + + +class AssignedEventHandler: + def __init__(self, result, cumulative_data, users_bindings): + self.result = result + self.cumulative_data = cumulative_data + self.users_bindings = users_bindings + + def handle(self, event): + if self.cumulative_data['assigned_to_github_id'] is None: + self.result['update_values']["users"] = {} + self.generate_change_old(event) + self.generate_update_values_from_cumulative_data(event) + user = self.users_bindings.get(event['assignee']['id'], None) + self.generate_change_new(event, user) + self.update_cumulative_data(event, user) + self.generate_update_values_from_cumulative_data(event) + + def generate_change_old(self, event): + self.result['change_old']["assigned_to"] = self.cumulative_data['assigned_to'] + + def generate_update_values_from_cumulative_data(self, event): + if self.cumulative_data['assigned_to_name'] is not None: + self.result['update_values']["users"][str(self.cumulative_data['assigned_to'])] = self.cumulative_data['assigned_to_name'] + + def generate_change_new(self, event, user): + if user is None: + self.result['change_new']["assigned_to"] = 0 + else: + self.result['change_new']["assigned_to"] = user.id + + def update_cumulative_data(self, event, user): + self.cumulative_data['assigned_to_github_id'] = event['assignee']['id'] + if user is None: + self.cumulative_data['assigned_to'] = 0 + self.cumulative_data['assigned_to_name'] = event['assignee']['login'] + else: + self.cumulative_data['assigned_to'] = user.id + self.cumulative_data['assigned_to_name'] = user.get_full_name() + + +class UnassignedEventHandler: + def __init__(self, result, cumulative_data, users_bindings): + self.result = result + self.cumulative_data = cumulative_data + self.users_bindings = users_bindings + + def handle(self, event): + if self.cumulative_data['assigned_to_github_id'] == event['assignee']['id']: + self.result['update_values']["users"] = {} + + self.generate_change_old(event) + self.generate_update_values_from_cumulative_data(event) + self.generate_change_new(event) + self.update_cumulative_data(event) + self.generate_update_values_from_cumulative_data(event) + + def generate_change_old(self, event): + self.result['change_old']["assigned_to"] = self.cumulative_data['assigned_to'] + + def generate_update_values_from_cumulative_data(self, event): + if self.cumulative_data['assigned_to_name'] is not None: + self.result['update_values']["users"][str(self.cumulative_data['assigned_to'])] = self.cumulative_data['assigned_to_name'] + + def generate_change_new(self, event): + self.result['change_new']["assigned_to"] = None + + def update_cumulative_data(self, event): + self.cumulative_data['assigned_to_github_id'] = None + self.cumulative_data['assigned_to'] = None + self.cumulative_data['assigned_to_name'] = None diff --git a/taiga/importers/github/tasks.py b/taiga/importers/github/tasks.py new file mode 100644 index 00000000..b3102621 --- /dev/null +++ b/taiga/importers/github/tasks.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import logging +import sys + +from django.utils.translation import ugettext as _ + +from taiga.base.mails import mail_builder +from taiga.celery import app +from taiga.users.models import User +from .importer import GithubImporter + +logger = logging.getLogger('taiga.importers.github') + + +@app.task(bind=True) +def import_project(self, user_id, token, project_id, options): + user = User.objects.get(id=user_id) + importer = GithubImporter(user, token) + try: + project = importer.import_project(project_id, options) + except Exception as e: + # Error + ctx = { + "user": user, + "error_subject": _("Error importing GitHub project"), + "error_message": _("Error importing GitHub project"), + "project": project_id, + "exception": e + } + email = mail_builder.importer_import_error(user, ctx) + email.send() + logger.error('Error importing GitHub project %s (by %s)', project_id, user, exc_info=sys.exc_info()) + else: + ctx = { + "project": project, + "user": user, + } + email = mail_builder.github_import_success(user, ctx) + email.send() diff --git a/taiga/importers/jira/agile.py b/taiga/importers/jira/agile.py new file mode 100644 index 00000000..c58d2f46 --- /dev/null +++ b/taiga/importers/jira/agile.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import datetime + +from django.template.defaultfilters import slugify +from taiga.projects.references.models import recalc_reference_counter +from taiga.projects.models import Project, ProjectTemplate, Points +from taiga.projects.userstories.models import UserStory, RolePoints +from taiga.projects.tasks.models import Task +from taiga.projects.milestones.models import Milestone +from taiga.projects.epics.models import Epic, RelatedUserStory +from taiga.projects.history.services import take_snapshot +from taiga.timeline.rebuilder import rebuild_timeline +from taiga.timeline.models import Timeline +from .common import JiraImporterCommon +from taiga.importers import services as import_service + + +class JiraAgileImporter(JiraImporterCommon): + def list_projects(self): + return [{"id": board['id'], + "name": board['name'], + "description": "", + "is_private": True, + "importer_type": "agile"} for board in self._client.get_agile('/board')['values']] + + def list_issue_types(self, project_id): + board_project = self._client.get_agile("/board/{}/project".format(project_id))['values'][0] + statuses = self._client.get("/project/{}/statuses".format(board_project['id'])) + return statuses + + def import_project(self, project_id, options=None): + self.resolve_user_bindings(options) + project = self._import_project_data(project_id, options) + self._import_epics_data(project_id, project, options) + self._import_user_stories_data(project_id, project, options) + self._cleanup(project, options) + Timeline.objects.filter(project=project).delete() + rebuild_timeline(None, None, project.id) + recalc_reference_counter(project) + return project + + def _import_project_data(self, project_id, options): + project = self._client.get_agile("/board/{}".format(project_id)) + project_config = self._client.get_agile("/board/{}/configuration".format(project_id)) + if project['type'] == "scrum": + project_template = ProjectTemplate.objects.get(slug="scrum") + options['type'] = "scrum" + elif project['type'] == "kanban": + project_template = ProjectTemplate.objects.get(slug="kanban") + options['type'] = "kanban" + + project_template.is_epics_activated = True + project_template.epic_statuses = [] + project_template.us_statuses = [] + project_template.task_statuses = [] + project_template.issue_statuses = [] + + counter = 0 + for column in project_config['columnConfig']['columns']: + project_template.epic_statuses.append({ + "name": column['name'], + "slug": slugify(column['name']), + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": counter, + }) + project_template.us_statuses.append({ + "name": column['name'], + "slug": slugify(column['name']), + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": counter, + }) + project_template.task_statuses.append({ + "name": column['name'], + "slug": slugify(column['name']), + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": counter, + }) + project_template.issue_statuses.append({ + "name": column['name'], + "slug": slugify(column['name']), + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": counter, + }) + counter += 1 + + project_template.default_options["epic_status"] = project_template.epic_statuses[0]['name'] + project_template.default_options["us_status"] = project_template.us_statuses[0]['name'] + project_template.default_options["task_status"] = project_template.task_statuses[0]['name'] + project_template.default_options["issue_status"] = project_template.issue_statuses[0]['name'] + + project_template.points = [{ + "value": None, + "name": "?", + "order": 0, + }] + + main_permissions = project_template.roles[0]['permissions'] + project_template.roles = [{ + "name": "Main", + "slug": "main", + "computable": True, + "permissions": main_permissions, + "order": 70, + }] + + project = Project.objects.create( + name=options.get('name', None) or project['name'], + description=options.get('description', None) or project.get('description', ''), + owner=self._user, + creation_template=project_template, + is_private=options.get('is_private', False), + ) + + self._create_custom_fields(project) + import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "main") + + if project_template.slug == "scrum": + for sprint in self._client.get_agile("/board/{}/sprint".format(project_id))['values']: + start_datetime = sprint.get('startDate', None) + end_datetime = sprint.get('startDate', None) + start_date = datetime.date.today() + if start_datetime: + start_date = start_datetime[:10] + end_date = datetime.date.today() + if end_datetime: + end_date = end_datetime[:10] + + milestone = Milestone.objects.create( + name=sprint['name'], + slug=slugify(sprint['name']), + owner=self._user, + project=project, + estimated_start=start_date, + estimated_finish=end_date, + ) + Milestone.objects.filter(id=milestone.id).update( + created_date=start_datetime or datetime.datetime.now(), + modified_date=start_datetime or datetime.datetime.now(), + ) + return project + + def _import_user_stories_data(self, project_id, project, options): + users_bindings = options.get('users_bindings', {}) + project_conf = self._client.get_agile("/board/{}/configuration".format(project_id)) + if options['type'] == "scrum": + estimation_field = project_conf['estimation']['field']['fieldId'] + + counter = 0 + offset = 0 + while True: + issues = self._client.get_agile("/board/{}/issue".format(project_id), { + "startAt": offset, + "expand": "changelog", + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", self._client.get_issue_url(issue['key'])] + + try: + milestone = project.milestones.get(name=issue['fields'].get('sprint', {}).get('name', '')) + except Milestone.DoesNotExist: + milestone = None + + us = UserStory.objects.create( + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.us_statuses.get(name=issue['fields']['status']['name']), + kanban_order=counter, + sprint_order=counter, + backlog_order=counter, + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + tags=issue['fields']['labels'], + external_reference=external_reference, + milestone=milestone, + ) + + try: + epic = project.epics.get(ref=int(issue['fields'].get("epic", {}).get("key", "FAKE-0").split("-")[1])) + RelatedUserStory.objects.create( + user_story=us, + epic=epic, + order=1 + ) + except Epic.DoesNotExist: + pass + + if options['type'] == "scrum": + estimation = None + if issue['fields'].get(estimation_field, None): + estimation = float(issue['fields'].get(estimation_field)) + + (points, _) = Points.objects.get_or_create( + project=project, + value=estimation, + defaults={ + "name": str(estimation), + "order": estimation, + } + ) + RolePoints.objects.filter(user_story=us, role__slug="main").update(points_id=points.id) + + self._import_to_custom_fields(us, issue, options) + + us.ref = issue['key'].split("-")[1] + UserStory.objects.filter(id=us.id).update( + ref=us.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + take_snapshot(us, comment="", user=None, delete=False) + self._import_subtasks(project_id, project, us, issue, options) + self._import_comments(us, issue, options) + self._import_attachments(us, issue, options) + self._import_changelog(project, us, issue, options) + counter += 1 + + if len(issues['issues']) < issues['maxResults']: + break + + def _import_subtasks(self, project_id, project, us, issue, options): + users_bindings = options.get('users_bindings', {}) + + if len(issue['fields']['subtasks']) == 0: + return + + counter = 0 + offset = 0 + while True: + issues = self._client.get_agile("/board/{}/issue".format(project_id), { + "jql": "parent={}".format(issue['key']), + "startAt": offset, + "expand": "changelog", + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", self._client.get_issue_url(issue['key'])] + + task = Task.objects.create( + user_story=us, + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.task_statuses.get(name=issue['fields']['status']['name']), + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + tags=issue['fields']['labels'], + external_reference=external_reference, + milestone=us.milestone, + ) + + self._import_to_custom_fields(task, issue, options) + + task.ref = issue['key'].split("-")[1] + Task.objects.filter(id=task.id).update( + ref=task.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + take_snapshot(task, comment="", user=None, delete=False) + for subtask in issue['fields']['subtasks']: + print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key'])) + self._import_comments(task, issue, options) + self._import_attachments(task, issue, options) + self._import_changelog(project, task, issue, options) + counter += 1 + if len(issues['issues']) < issues['maxResults']: + break + + def _import_epics_data(self, project_id, project, options): + users_bindings = options.get('users_bindings', {}) + + counter = 0 + offset = 0 + while True: + issues = self._client.get_agile("/board/{}/epic".format(project_id), { + "startAt": offset, + }) + offset += issues['maxResults'] + + for epic in issues['values']: + issue = self._client.get_agile("/issue/{}".format(epic['key'])) + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", self._client.get_issue_url(issue['key'])] + + epic = Epic.objects.create( + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.epic_statuses.get(name=issue['fields']['status']['name']), + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + epics_order=counter, + tags=issue['fields']['labels'], + external_reference=external_reference, + ) + + self._import_to_custom_fields(epic, issue, options) + + epic.ref = issue['key'].split("-")[1] + Epic.objects.filter(id=epic.id).update( + ref=epic.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + + take_snapshot(epic, comment="", user=None, delete=False) + for subtask in issue['fields']['subtasks']: + print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key'])) + self._import_comments(epic, issue, options) + self._import_attachments(epic, issue, options) + issue_with_changelog = self._client.get("/issue/{}".format(issue['key']), { + "expand": "changelog" + }) + self._import_changelog(project, epic, issue_with_changelog, options) + counter += 1 + + if len(issues['values']) < issues['maxResults']: + break diff --git a/taiga/importers/jira/api.py b/taiga/importers/jira/api.py new file mode 100644 index 00000000..9c5d0b4d --- /dev/null +++ b/taiga/importers/jira/api.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Taiga Agile LLC +# 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.translation import ugettext as _ +from django.conf import settings + +from taiga.base.api import viewsets +from taiga.base import response +from taiga.base import exceptions as exc +from taiga.base.decorators import list_route +from taiga.users.models import AuthData, User +from taiga.users.services import get_user_photo_url +from taiga.users.gravatar import get_user_gravatar_id + +from taiga.importers import permissions +from taiga.importers.services import resolve_users_bindings +from .normal import JiraNormalImporter +from .agile import JiraAgileImporter +from . import tasks + + +class JiraImporterViewSet(viewsets.ViewSet): + permission_classes = (permissions.ImporterPermission,) + + def _get_token(self, request): + token_data = request.DATA.get('token', "").split(".") + + token = { + "access_token": token_data[0], + "access_token_secret": token_data[1], + "key_cert": settings.IMPORTERS.get('jira', {}).get('cert', None), + "consumer_key": settings.IMPORTERS.get('jira', {}).get('consumer_key', None) + } + return token + + @list_route(methods=["POST"]) + def list_users(self, request, *args, **kwargs): + self.check_permissions(request, "list_users", None) + + url = request.DATA.get('url', None) + token = self._get_token(request) + project_id = request.DATA.get('project', None) + + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + if not url: + raise exc.WrongArguments(_("The url param is needed")) + + importer = JiraNormalImporter(request.user, url, token) + users = importer.list_users() + for user in users: + user['user'] = None + if not user['email']: + continue + + try: + taiga_user = User.objects.get(email=user['email']) + except User.DoesNotExist: + continue + + user['user'] = { + 'id': taiga_user.id, + 'full_name': taiga_user.get_full_name(), + 'gravatar_id': get_user_gravatar_id(taiga_user), + 'photo': get_user_photo_url(taiga_user), + } + return response.Ok(users) + + @list_route(methods=["POST"]) + def list_projects(self, request, *args, **kwargs): + self.check_permissions(request, "list_projects", None) + url = request.DATA.get('url', None) + if not url: + raise exc.WrongArguments(_("The url param is needed")) + + token = self._get_token(request) + importer = JiraNormalImporter(request.user, url, token) + agile_importer = JiraAgileImporter(request.user, url, token) + projects = importer.list_projects() + boards = agile_importer.list_projects() + return response.Ok(sorted(projects + boards, key=lambda x: x['name'])) + + @list_route(methods=["POST"]) + def import_project(self, request, *args, **kwargs): + self.check_permissions(request, "import_project", None) + + url = request.DATA.get('url', None) + token = self._get_token(request) + project_id = request.DATA.get('project', None) + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + if not url: + raise exc.WrongArguments(_("The url param is needed")) + + options = { + "name": request.DATA.get('name', None), + "description": request.DATA.get('description', None), + "users_bindings": resolve_users_bindings(request.DATA.get("users_bindings", {})), + "keep_external_reference": request.DATA.get("keep_external_reference", False), + "is_private": request.DATA.get("is_private", False), + } + + importer_type = request.DATA.get('importer_type', "normal") + if importer_type == "agile": + importer = JiraAgileImporter(request.user, url, token) + else: + project_type = request.DATA.get("project_type", "scrum") + if project_type == "kanban": + options['template'] = "kanban" + else: + options['template'] = "scrum" + + importer = JiraNormalImporter(request.user, url, token) + + types_bindings = { + "epic": [], + "us": [], + "task": [], + "issue": [], + } + for issue_type in importer.list_issue_types(project_id): + if project_type in ['scrum', 'kanban']: + # Set the type bindings + if issue_type['subtask']: + types_bindings['task'].append(issue_type) + elif issue_type['name'].upper() == "EPIC": + types_bindings["epic"].append(issue_type) + elif issue_type['name'].upper() in ["US", "USERSTORY", "USER STORY"]: + types_bindings["us"].append(issue_type) + elif issue_type['name'].upper() in ["ISSUE", "BUG", "ENHANCEMENT"]: + types_bindings["issue"].append(issue_type) + else: + types_bindings["us"].append(issue_type) + elif project_type == "issues": + # Set the type bindings + if issue_type['subtask']: + continue + types_bindings["issue"].append(issue_type) + elif project_type == "issues-with-subissues": + types_bindings["issue"].append(issue_type) + else: + raise exc.WrongArguments(_("Invalid project_type {}").format(project_type)) + + options["types_bindings"] = types_bindings + + if settings.CELERY_ENABLED: + task = tasks.import_project.delay(request.user.id, url, token, project_id, options, importer_type) + return response.Accepted({"task_id": task.id}) + + project = importer.import_project(project_id, options) + project_data = { + "slug": project.slug, + "my_permissions": ["view_us"], + "is_backlog_activated": project.is_backlog_activated, + "is_kanban_activated": project.is_kanban_activated, + } + + return response.Ok(project_data) + + @list_route(methods=["GET"]) + def auth_url(self, request, *args, **kwargs): + self.check_permissions(request, "auth_url", None) + jira_url = request.QUERY_PARAMS.get('url', None) + + if not jira_url: + raise exc.WrongArguments(_("The url param is needed")) + + (oauth_token, oauth_secret, url) = JiraNormalImporter.get_auth_url( + jira_url, + settings.IMPORTERS.get('jira', {}).get('consumer_key', None), + settings.IMPORTERS.get('jira', {}).get('cert', None), + True + ) + + (auth_data, created) = AuthData.objects.get_or_create( + user=request.user, + key="jira-oauth", + defaults={ + "value": "", + "extra": {}, + } + ) + auth_data.extra = { + "oauth_token": oauth_token, + "oauth_secret": oauth_secret, + "url": jira_url, + } + auth_data.save() + + return response.Ok({"url": url}) + + @list_route(methods=["POST"]) + def authorize(self, request, *args, **kwargs): + self.check_permissions(request, "authorize", None) + + try: + oauth_data = request.user.auth_data.get(key="jira-oauth") + oauth_token = oauth_data.extra['oauth_token'] + oauth_secret = oauth_data.extra['oauth_secret'] + server_url = oauth_data.extra['url'] + oauth_data.delete() + + jira_token = JiraNormalImporter.get_access_token( + server_url, + settings.IMPORTERS.get('jira', {}).get('consumer_key', None), + settings.IMPORTERS.get('jira', {}).get('cert', None), + oauth_token, + oauth_secret, + True + ) + except Exception as e: + raise exc.WrongArguments(_("Invalid or expired auth token")) + + return response.Ok({ + "token": jira_token['access_token'] + "." + jira_token['access_token_secret'], + "url": server_url + }) diff --git a/taiga/importers/jira/common.py b/taiga/importers/jira/common.py new file mode 100644 index 00000000..f1ffa691 --- /dev/null +++ b/taiga/importers/jira/common.py @@ -0,0 +1,767 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import requests +from urllib.parse import parse_qsl +from oauthlib.oauth1 import SIGNATURE_RSA + +from requests_oauthlib import OAuth1 +from django.core.files.base import ContentFile +from django.contrib.contenttypes.models import ContentType + +from taiga.users.models import User +from taiga.projects.models import Points +from taiga.projects.userstories.models import UserStory +from taiga.projects.tasks.models import Task +from taiga.projects.issues.models import Issue +from taiga.projects.milestones.models import Milestone +from taiga.projects.epics.models import Epic +from taiga.projects.attachments.models import Attachment +from taiga.projects.history.services import take_snapshot +from taiga.projects.history.services import (make_diff_from_dicts, + make_diff_values, + make_key_from_model_object, + get_typename_for_model_class, + FrozenDiff) +from taiga.projects.custom_attributes.models import (UserStoryCustomAttribute, + TaskCustomAttribute, + IssueCustomAttribute, + EpicCustomAttribute) +from taiga.projects.history.models import HistoryEntry +from taiga.projects.history.choices import HistoryType +from taiga.mdrender.service import render as mdrender + +EPIC_COLORS = { + "ghx-label-0": "#ffffff", + "ghx-label-1": "#815b3a", + "ghx-label-2": "#f79232", + "ghx-label-3": "#d39c3f", + "ghx-label-4": "#3b7fc4", + "ghx-label-5": "#4a6785", + "ghx-label-6": "#8eb021", + "ghx-label-7": "#ac707a", + "ghx-label-8": "#654982", + "ghx-label-9": "#f15c75", +} + + +def links_to_richtext(importer, issue, links): + richtext = "" + importing_project_key = issue['key'].split("-")[0] + for link in links: + if "inwardIssue" in link: + (project_key, issue_key) = link['inwardIssue']['key'].split("-") + action = link['type']['inward'] + elif "outwardIssue" in link: + (project_key, issue_key) = link['outwardIssue']['key'].split("-") + action = link['type']['outward'] + else: + continue + + if importing_project_key == project_key: + richtext += " * This item {} #{}\n".format(action, issue_key) + else: + url = importer._client.server + "/projects/{}/issues/{}-{}".format( + project_key, + project_key, + issue_key + ) + richtext += " * This item {} [{}-{}]({})\n".format(action, project_key, issue_key, url) + + for link in links: + if "object" in link: + richtext += " * [{}]({})\n".format( + link['object']['title'] or link['object']['url'], + link['object']['url'], + ) + + return richtext + + +class JiraClient: + def __init__(self, server, oauth): + self.server = server + self.api_url = server + "/rest/agile/1.0/{}" + self.main_api_url = server + "/rest/api/2/{}" + if oauth: + self.oauth = OAuth1( + oauth['consumer_key'], + signature_method=SIGNATURE_RSA, + rsa_key=oauth['key_cert'], + resource_owner_key=oauth['access_token'], + resource_owner_secret=oauth['access_token_secret'] + ) + else: + self.oauth = None + + def get(self, uri_path, query_params=None): + headers = { + 'Content-Type': "application/json" + } + if query_params is None: + query_params = {} + + if uri_path[0] == '/': + uri_path = uri_path[1:] + url = self.main_api_url.format(uri_path) + + response = requests.get(url, params=query_params, headers=headers, auth=self.oauth) + + if response.status_code == 401: + raise Exception("Unauthorized: %s at %s" % (response.text, url), response) + if response.status_code != 200: + raise Exception("Resource Unavailable: %s at %s" % (response.text, url), response) + + return response.json() + + def get_agile(self, uri_path, query_params=None): + headers = { + 'Content-Type': "application/json" + } + if query_params is None: + query_params = {} + + if uri_path[0] == '/': + uri_path = uri_path[1:] + url = self.api_url.format(uri_path) + + response = requests.get(url, params=query_params, headers=headers, auth=self.oauth) + + if response.status_code == 401: + raise Exception("Unauthorized: %s at %s" % (response.text, url), response) + if response.status_code != 200: + raise Exception("Resource Unavailable: %s at %s" % (response.text, url), response) + + return response.json() + + def raw_get(self, absolute_uri, query_params=None): + if query_params is None: + query_params = {} + + response = requests.get(absolute_uri, params=query_params, auth=self.oauth) + + if response.status_code == 401: + raise Exception("Unauthorized: %s at %s" % (response.text, absolute_uri), response) + if response.status_code != 200: + raise Exception("Resource Unavailable: %s at %s" % (response.text, absolute_uri), response) + + return response.content + + def get_issue_url(self, key): + (project_key, issue_key) = key.split("-") + return self.server + "/projects/{}/issues/{}".format(project_key, key) + + +class JiraImporterCommon: + def __init__(self, user, server, oauth): + self._user = user + self._client = JiraClient(server=server, oauth=oauth) + + def resolve_user_bindings(self, options): + def resolve_user(user_id): + if isinstance(user_id, User): + return user_id + try: + user = User.objects.get(id=user_id) + return user + except User.DoesNotExist: + return None + + options['users_bindings'] = {k: resolve_user(v) for k,v in options['users_bindings'].items() if v is not None} + + def list_users(self): + result = [] + users = self._client.get("/user/picker", { + "query": "@", + "maxResults": 1000, + }) + for user in users['users']: + user_data = self._client.get("/user", { + "key": user['key'] + }) + result.append({ + "id": user_data['key'], + "full_name": user_data['displayName'], + "email": user_data['emailAddress'], + "avatar": user_data.get('avatarUrls', None) and user_data['avatarUrls'].get('48x48', None), + }) + return result + + def _import_comments(self, obj, issue, options): + users_bindings = options.get('users_bindings', {}) + offset = 0 + while True: + comments = self._client.get("/issue/{}/comment".format(issue['key']), {"startAt": offset}) + for comment in comments['comments']: + snapshot = take_snapshot( + obj, + comment=comment['body'], + user=users_bindings.get( + comment['author']['name'], + User(full_name=comment['author']['displayName']) + ), + delete=False + ) + HistoryEntry.objects.filter(id=snapshot.id).update(created_at=comment['created']) + + offset += len(comments['comments']) + if len(comments['comments']) <= comments['maxResults']: + break + + def _create_custom_fields(self, project): + custom_fields = [] + for model in [UserStoryCustomAttribute, TaskCustomAttribute, IssueCustomAttribute, EpicCustomAttribute]: + model.objects.create( + name="Due date", + description="Due date", + type="date", + order=1, + project=project + ) + model.objects.create( + name="Priority", + description="Priority", + type="text", + order=1, + project=project + ) + model.objects.create( + name="Resolution", + description="Resolution", + type="text", + order=1, + project=project + ) + model.objects.create( + name="Resolution date", + description="Resolution date", + type="date", + order=1, + project=project + ) + model.objects.create( + name="Environment", + description="Environment", + type="text", + order=1, + project=project + ) + model.objects.create( + name="Components", + description="Components", + type="text", + order=1, + project=project + ) + model.objects.create( + name="Affects Version/s", + description="Affects Version/s", + type="text", + order=1, + project=project + ) + model.objects.create( + name="Fix Version/s", + description="Fix Version/s", + type="text", + order=1, + project=project + ) + model.objects.create( + name="Links", + description="Links", + type="richtext", + order=1, + project=project + ) + custom_fields.append({ + "history_name": "duedate", + "jira_field_name": "duedate", + "taiga_field_name": "Due date", + }) + custom_fields.append({ + "history_name": "priority", + "jira_field_name": "priority", + "taiga_field_name": "Priority", + "transform": lambda issue, obj: obj.get('name', None) + }) + custom_fields.append({ + "history_name": "resolution", + "jira_field_name": "resolution", + "taiga_field_name": "Resolution", + "transform": lambda issue, obj: obj.get('name', None) + }) + custom_fields.append({ + "history_name": "Resolution date", + "jira_field_name": "resolutiondate", + "taiga_field_name": "Resolution date", + }) + custom_fields.append({ + "history_name": "environment", + "jira_field_name": "environment", + "taiga_field_name": "Environment", + }) + custom_fields.append({ + "history_name": "Component", + "jira_field_name": "components", + "taiga_field_name": "Components", + "transform": lambda issue, obj: ", ".join([c.get('name', None) for c in obj]) + }) + custom_fields.append({ + "history_name": "Version", + "jira_field_name": "versions", + "taiga_field_name": "Affects Version/s", + "transform": lambda issue, obj: ", ".join([c.get('name', None) for c in obj]) + }) + custom_fields.append({ + "history_name": "Fix Version", + "jira_field_name": "fixVersions", + "taiga_field_name": "Fix Version/s", + "transform": lambda issue, obj: ", ".join([c.get('name', None) for c in obj]) + }) + custom_fields.append({ + "history_name": "Link", + "jira_field_name": "issuelinks", + "taiga_field_name": "Links", + "transform": lambda issue, obj: links_to_richtext(self, issue, obj) + }) + + greenhopper_fields = {} + for custom_field in self._client.get("/field"): + if custom_field['custom']: + if custom_field['schema']['custom'] == "com.pyxis.greenhopper.jira:gh-sprint": + greenhopper_fields["sprint"] = custom_field['id'] + elif custom_field['schema']['custom'] == "com.pyxis.greenhopper.jira:gh-epic-link": + greenhopper_fields["link"] = custom_field['id'] + elif custom_field['schema']['custom'] == "com.pyxis.greenhopper.jira:gh-epic-status": + greenhopper_fields["status"] = custom_field['id'] + elif custom_field['schema']['custom'] == "com.pyxis.greenhopper.jira:gh-epic-label": + greenhopper_fields["label"] = custom_field['id'] + elif custom_field['schema']['custom'] == "com.pyxis.greenhopper.jira:gh-epic-color": + greenhopper_fields["color"] = custom_field['id'] + elif custom_field['schema']['custom'] == "com.pyxis.greenhopper.jira:gh-lexo-rank": + greenhopper_fields["rank"] = custom_field['id'] + elif ( + custom_field['name'] == "Story Points" and + custom_field['schema']['custom'] == 'com.atlassian.jira.plugin.system.customfieldtypes:float' + ): + greenhopper_fields["points"] = custom_field['id'] + else: + multiline_types = [ + "com.atlassian.jira.plugin.system.customfieldtypes:textarea" + ] + date_types = [ + "com.atlassian.jira.plugin.system.customfieldtypes:datepicker" + "com.atlassian.jira.plugin.system.customfieldtypes:datetime" + ] + if custom_field['schema']['custom'] in multiline_types: + field_type = "multiline" + elif custom_field['schema']['custom'] in date_types: + field_type = "date" + else: + field_type = "text" + + custom_field_data = { + "name": custom_field['name'][:64], + "description": custom_field['name'], + "type": field_type, + "order": 1, + "project": project + } + + UserStoryCustomAttribute.objects.get_or_create(**custom_field_data) + TaskCustomAttribute.objects.get_or_create(**custom_field_data) + IssueCustomAttribute.objects.get_or_create(**custom_field_data) + EpicCustomAttribute.objects.get_or_create(**custom_field_data) + + custom_fields.append({ + "history_name": custom_field['name'], + "jira_field_name": custom_field['id'], + "taiga_field_name": custom_field['name'][:64], + }) + + self.greenhopper_fields = greenhopper_fields + self.custom_fields = custom_fields + + def _import_to_custom_fields(self, obj, issue, options): + if isinstance(obj, Epic): + custom_att_manager = obj.project.epiccustomattributes + elif isinstance(obj, UserStory): + custom_att_manager = obj.project.userstorycustomattributes + elif isinstance(obj, Task): + custom_att_manager = obj.project.taskcustomattributes + elif isinstance(obj, Issue): + custom_att_manager = obj.project.issuecustomattributes + else: + raise NotImplementedError("Not implemented custom attributes for this object ({})".format(obj)) + + custom_attributes_values = {} + for custom_field in self.custom_fields: + data = issue['fields'].get(custom_field['jira_field_name'], None) + if data and "transform" in custom_field: + data = custom_field['transform'](issue, data) + + if data: + taiga_field = custom_att_manager.get(name=custom_field['taiga_field_name']) + custom_attributes_values[taiga_field.id] = data + + if custom_attributes_values != {}: + obj.custom_attributes_values.attributes_values = custom_attributes_values + obj.custom_attributes_values.save() + + def _import_attachments(self, obj, issue, options): + users_bindings = options.get('users_bindings', {}) + + for attachment in issue['fields']['attachment']: + try: + data = self._client.raw_get(attachment['content']) + att = Attachment( + owner=users_bindings.get(attachment['author']['name'], self._user), + project=obj.project, + content_type=ContentType.objects.get_for_model(obj), + object_id=obj.id, + name=attachment['filename'], + size=attachment['size'], + created_date=attachment['created'], + is_deprecated=False, + ) + att.attached_file.save(attachment['filename'], ContentFile(data), save=True) + except Exception: + print("ERROR getting attachment url {}".format(attachment['content'])) + + + def _import_changelog(self, project, obj, issue, options): + obj.cummulative_attachments = [] + for history in sorted(issue['changelog']['histories'], key=lambda h: h['created']): + self._import_history(project, obj, history, options) + + def _import_history(self, project, obj, history, options): + key = make_key_from_model_object(obj) + typename = get_typename_for_model_class(obj.__class__) + history_data = self._transform_history_data(project, obj, history, options) + if history_data is None: + return + + change_old = history_data['change_old'] + change_new = history_data['change_new'] + hist_type = history_data['hist_type'] + comment = history_data['comment'] + user = history_data['user'] + + diff = make_diff_from_dicts(change_old, change_new) + fdiff = FrozenDiff(key, diff, {}) + + values = make_diff_values(typename, fdiff) + values.update(history_data['update_values']) + + entry = HistoryEntry.objects.create( + user=user, + project_id=obj.project.id, + key=key, + type=hist_type, + snapshot=None, + diff=fdiff.diff, + values=values, + comment=comment, + comment_html=mdrender(obj.project, comment), + is_hidden=False, + is_snapshot=False, + ) + HistoryEntry.objects.filter(id=entry.id).update(created_at=history['created']) + return HistoryEntry.objects.get(id=entry.id) + + def _transform_history_data(self, project, obj, history, options): + users_bindings = options.get('users_bindings', {}) + + user = {"pk": None, "name": history.get('author', {}).get('displayName', None)} + taiga_user = users_bindings.get(history.get('author', {}).get('key', None), None) + if taiga_user: + user = {"pk": taiga_user.id, "name": taiga_user.get_full_name()} + + result = { + "change_old": {}, + "change_new": {}, + "update_values": {}, + "hist_type": HistoryType.change, + "comment": "", + "user": user + } + custom_fields_by_names = {f["history_name"]: f for f in self.custom_fields} + has_data = False + for history_item in history['items']: + if history_item['field'] == "Attachment": + result['change_old']["attachments"] = [] + for att in obj.cummulative_attachments: + result['change_old']["attachments"].append({ + "id": 0, + "filename": att + }) + + if history_item['from'] is not None: + try: + idx = obj.cummulative_attachments.index(history_item['fromString']) + obj.cummulative_attachments.pop(idx) + except ValueError: + print("ERROR: Removing attachment that doesn't exist in the history ({})".format(history_item['fromString'])) + if history_item['to'] is not None: + obj.cummulative_attachments.append(history_item['toString']) + + result['change_new']["attachments"] = [] + for att in obj.cummulative_attachments: + result['change_new']["attachments"].append({ + "id": 0, + "filename": att + }) + has_data = True + elif history_item['field'] == "description": + result['change_old']["description"] = history_item['fromString'] + result['change_new']["description"] = history_item['toString'] + result['change_old']["description_html"] = mdrender(obj.project, history_item['fromString'] or "") + result['change_new']["description_html"] = mdrender(obj.project, history_item['toString'] or "") + has_data = True + elif history_item['field'] == "Epic Link": + pass + elif history_item['field'] == "Workflow": + pass + elif history_item['field'] == "Link": + pass + elif history_item['field'] == "labels": + result['change_old']["tags"] = history_item['fromString'].split() + result['change_new']["tags"] = history_item['toString'].split() + has_data = True + elif history_item['field'] == "Rank": + pass + elif history_item['field'] == "RemoteIssueLink": + pass + elif history_item['field'] == "Sprint": + old_milestone = None + if history_item['fromString']: + try: + old_milestone = obj.project.milestones.get(name=history_item['fromString']).id + except Milestone.DoesNotExist: + old_milestone = -1 + + new_milestone = None + if history_item['toString']: + try: + new_milestone = obj.project.milestones.get(name=history_item['toString']).id + except Milestone.DoesNotExist: + new_milestone = -2 + + result['change_old']["milestone"] = old_milestone + result['change_new']["milestone"] = new_milestone + + if old_milestone == -1 or new_milestone == -2: + result['update_values']["milestone"] = {} + + if old_milestone == -1: + result['update_values']["milestone"]["-1"] = history_item['fromString'] + if new_milestone == -2: + result['update_values']["milestone"]["-2"] = history_item['toString'] + has_data = True + elif history_item['field'] == "status": + if isinstance(obj, Task): + try: + old_status = obj.project.task_statuses.get(name=history_item['fromString']).id + except Exception: + old_status = -1 + try: + new_status = obj.project.task_statuses.get(name=history_item['toString']).id + except Exception: + new_status = -2 + elif isinstance(obj, UserStory): + try: + old_status = obj.project.us_statuses.get(name=history_item['fromString']).id + except Exception: + old_status = -1 + try: + new_status = obj.project.us_statuses.get(name=history_item['toString']).id + except Exception: + new_status = -2 + elif isinstance(obj, Issue): + try: + old_status = obj.project.issue_statuses.get(name=history_item['fromString']).id + except Exception: + old_status = -1 + try: + new_status = obj.project.us_statuses.get(name=history_item['toString']).id + except Exception: + new_status = -2 + elif isinstance(obj, Epic): + try: + old_status = obj.project.epic_statuses.get(name=history_item['fromString']).id + except Exception: + old_status = -1 + try: + new_status = obj.project.epic_statuses.get(name=history_item['toString']).id + except Exception: + new_status = -2 + + if old_status == -1 or new_status == -2: + result['update_values']["status"] = {} + + if old_status == -1: + result['update_values']["status"]["-1"] = history_item['fromString'] + if new_status == -2: + result['update_values']["status"]["-2"] = history_item['toString'] + + result['change_old']["status"] = old_status + result['change_new']["status"] = new_status + has_data = True + elif history_item['field'] == "Story Points": + old_points = None + if history_item['fromString']: + estimation = float(history_item['fromString']) + (old_points, _) = Points.objects.get_or_create( + project=project, + value=estimation, + defaults={ + "name": str(estimation), + "order": estimation, + } + ) + old_points = old_points.id + new_points = None + if history_item['toString']: + estimation = float(history_item['toString']) + (new_points, _) = Points.objects.get_or_create( + project=project, + value=estimation, + defaults={ + "name": str(estimation), + "order": estimation, + } + ) + new_points = new_points.id + result['change_old']["points"] = {project.roles.get(slug="main").id: old_points} + result['change_new']["points"] = {project.roles.get(slug="main").id: new_points} + has_data = True + elif history_item['field'] == "summary": + result['change_old']["subject"] = history_item['fromString'] + result['change_new']["subject"] = history_item['toString'] + has_data = True + elif history_item['field'] == "Epic Color": + if isinstance(obj, Epic): + result['change_old']["color"] = EPIC_COLORS.get(history_item['fromString'], None) + result['change_new']["color"] = EPIC_COLORS.get(history_item['toString'], None) + Epic.objects.filter(id=obj.id).update( + color=EPIC_COLORS.get(history_item['toString'], "#999999") + ) + has_data = True + elif history_item['field'] == "assignee": + old_assigned_to = None + if history_item['from'] is not None: + old_assigned_to = users_bindings.get(history_item['from'], -1) + if old_assigned_to != -1: + old_assigned_to = old_assigned_to.id + + new_assigned_to = None + if history_item['to'] is not None: + new_assigned_to = users_bindings.get(history_item['to'], -2) + if new_assigned_to != -2: + new_assigned_to = new_assigned_to.id + + result['change_old']["assigned_to"] = old_assigned_to + result['change_new']["assigned_to"] = new_assigned_to + + if old_assigned_to == -1 or new_assigned_to == -2: + result['update_values']["users"] = {} + + if old_assigned_to == -1: + result['update_values']["users"]["-1"] = history_item['fromString'] + if new_assigned_to == -2: + result['update_values']["users"]["-2"] = history_item['toString'] + has_data = True + elif history_item['field'] in custom_fields_by_names: + custom_field = custom_fields_by_names[history_item['field']] + if isinstance(obj, Task): + field_obj = obj.project.taskcustomattributes.get(name=custom_field['taiga_field_name']) + elif isinstance(obj, UserStory): + field_obj = obj.project.userstorycustomattributes.get(name=custom_field['taiga_field_name']) + elif isinstance(obj, Issue): + field_obj = obj.project.issuecustomattributes.get(name=custom_field['taiga_field_name']) + elif isinstance(obj, Epic): + field_obj = obj.project.epiccustomattributes.get(name=custom_field['taiga_field_name']) + + result['change_old']["custom_attributes"] = [{ + "name": custom_field['taiga_field_name'], + "value": history_item['fromString'], + "id": field_obj.id + }] + result['change_new']["custom_attributes"] = [{ + "name": custom_field['taiga_field_name'], + "value": history_item['toString'], + "id": field_obj.id + }] + has_data = True + + if not has_data: + return None + + return result + + def _cleanup(self, project, options): + for epic_custom_field in project.epiccustomattributes.all(): + if project.epics.filter(custom_attributes_values__attributes_values__has_key=str(epic_custom_field.id)).count() == 0: + epic_custom_field.delete() + for us_custom_field in project.userstorycustomattributes.all(): + if project.user_stories.filter(custom_attributes_values__attributes_values__has_key=str(us_custom_field.id)).count() == 0: + us_custom_field.delete() + for task_custom_field in project.taskcustomattributes.all(): + if project.tasks.filter(custom_attributes_values__attributes_values__has_key=str(task_custom_field.id)).count() == 0: + task_custom_field.delete() + for issue_custom_field in project.issuecustomattributes.all(): + if project.issues.filter(custom_attributes_values__attributes_values__has_key=str(issue_custom_field.id)).count() == 0: + issue_custom_field.delete() + + @classmethod + def get_auth_url(cls, server, consumer_key, key_cert_data, verify=None): + if verify is None: + verify = server.startswith('https') + + oauth = OAuth1(consumer_key, signature_method=SIGNATURE_RSA, rsa_key=key_cert_data) + r = requests.post( + server + '/plugins/servlet/oauth/request-token', verify=verify, auth=oauth) + request = dict(parse_qsl(r.text)) + request_token = request['oauth_token'] + request_token_secret = request['oauth_token_secret'] + + return ( + request_token, + request_token_secret, + '{}/plugins/servlet/oauth/authorize?oauth_token={}'.format(server, request_token) + ) + + @classmethod + def get_access_token(cls, server, consumer_key, key_cert_data, request_token, request_token_secret, verify=False): + oauth = OAuth1( + consumer_key, + signature_method=SIGNATURE_RSA, + rsa_key=key_cert_data, + resource_owner_key=request_token, + resource_owner_secret=request_token_secret + ) + r = requests.post(server + '/plugins/servlet/oauth/access-token', verify=verify, auth=oauth) + access = dict(parse_qsl(r.text)) + + return { + 'access_token': access['oauth_token'], + 'access_token_secret': access['oauth_token_secret'], + 'consumer_key': consumer_key, + 'key_cert': key_cert_data + } diff --git a/taiga/importers/jira/normal.py b/taiga/importers/jira/normal.py new file mode 100644 index 00000000..0db32f35 --- /dev/null +++ b/taiga/importers/jira/normal.py @@ -0,0 +1,451 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 collections import OrderedDict + +from django.template.defaultfilters import slugify +from taiga.projects.references.models import recalc_reference_counter +from taiga.projects.models import Project, ProjectTemplate, Points +from taiga.projects.userstories.models import UserStory, RolePoints +from taiga.projects.tasks.models import Task +from taiga.projects.issues.models import Issue +from taiga.projects.epics.models import Epic, RelatedUserStory +from taiga.projects.history.services import take_snapshot +from taiga.timeline.rebuilder import rebuild_timeline +from taiga.timeline.models import Timeline +from .common import JiraImporterCommon +from taiga.importers import services as import_service + + +class JiraNormalImporter(JiraImporterCommon): + def list_projects(self): + return [{"id": project['id'], + "name": project['name'], + "description": project['description'], + "is_private": True, + "importer_type": "normal"} for project in self._client.get('/project', {"expand": "description"})] + + def list_issue_types(self, project_id): + statuses = self._client.get("/project/{}/statuses".format(project_id)) + return statuses + + def import_project(self, project_id, options): + self.resolve_user_bindings(options) + project = self._import_project_data(project_id, options) + self._import_user_stories_data(project_id, project, options) + self._import_epics_data(project_id, project, options) + self._link_epics_with_user_stories(project_id, project, options) + self._import_issues_data(project_id, project, options) + self._cleanup(project, options) + Timeline.objects.filter(project=project).delete() + rebuild_timeline(None, None, project.id) + recalc_reference_counter(project) + return project + + def _import_project_data(self, project_id, options): + project = self._client.get("/project/{}".format(project_id)) + project_template = ProjectTemplate.objects.get(slug=options['template']) + + epic_statuses = OrderedDict() + for issue_type in options.get('types_bindings', {}).get("epic", []): + for status in issue_type['statuses']: + epic_statuses[status['name']] = status + + us_statuses = OrderedDict() + for issue_type in options.get('types_bindings', {}).get("us", []): + for status in issue_type['statuses']: + us_statuses[status['name']] = status + + task_statuses = OrderedDict() + for issue_type in options.get('types_bindings', {}).get("task", []): + for status in issue_type['statuses']: + task_statuses[status['name']] = status + + issue_statuses = OrderedDict() + for issue_type in options.get('types_bindings', {}).get("issue", []): + for status in issue_type['statuses']: + issue_statuses[status['name']] = status + + counter = 0 + if epic_statuses: + project_template.epic_statuses = [] + project_template.is_epics_activated = True + for epic_status in epic_statuses.values(): + project_template.epic_statuses.append({ + "name": epic_status['name'], + "slug": slugify(epic_status['name']), + "is_closed": False, + "color": "#999999", + "order": counter, + }) + counter += 1 + if epic_statuses: + project_template.default_options["epic_status"] = list(epic_statuses.values())[0]['name'] + + project_template.points = [{ + "value": None, + "name": "?", + "order": 0, + }] + + main_permissions = project_template.roles[0]['permissions'] + project_template.roles = [{ + "name": "Main", + "slug": "main", + "computable": True, + "permissions": main_permissions, + "order": 70, + }] + + counter = 0 + if us_statuses: + project_template.us_statuses = [] + for us_status in us_statuses.values(): + project_template.us_statuses.append({ + "name": us_status['name'], + "slug": slugify(us_status['name']), + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": counter, + }) + counter += 1 + if us_statuses: + project_template.default_options["us_status"] = list(us_statuses.values())[0]['name'] + + counter = 0 + if task_statuses: + project_template.task_statuses = [] + for task_status in task_statuses.values(): + project_template.task_statuses.append({ + "name": task_status['name'], + "slug": slugify(task_status['name']), + "is_closed": False, + "color": "#999999", + "order": counter, + }) + counter += 1 + if task_statuses: + project_template.default_options["task_status"] = list(task_statuses.values())[0]['name'] + + counter = 0 + if issue_statuses: + project_template.issue_statuses = [] + for issue_status in issue_statuses.values(): + project_template.issue_statuses.append({ + "name": issue_status['name'], + "slug": slugify(issue_status['name']), + "is_closed": False, + "color": "#999999", + "order": counter, + }) + counter += 1 + if issue_statuses: + project_template.default_options["issue_status"] = list(issue_statuses.values())[0]['name'] + + + main_permissions = project_template.roles[0]['permissions'] + project_template.roles = [{ + "name": "Main", + "slug": "main", + "computable": True, + "permissions": main_permissions, + "order": 70, + }] + + project = Project.objects.create( + name=options.get('name', None) or project['name'], + description=options.get('description', None) or project.get('description', ''), + owner=self._user, + creation_template=project_template, + is_private=options.get('is_private', False), + ) + + self._create_custom_fields(project) + import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "main") + + return project + + def _import_user_stories_data(self, project_id, project, options): + users_bindings = options.get('users_bindings', {}) + + types = options.get('types_bindings', {}).get("us", []) + for issue_type in types: + counter = 0 + offset = 0 + while True: + issues = self._client.get("/search", { + "jql": "project={} AND issuetype={}".format(project_id, issue_type['id']), + "startAt": offset, + "fields": "*all", + "expand": "changelog,attachment", + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", issue['fields']['url']] + + + us = UserStory.objects.create( + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.us_statuses.get(name=issue['fields']['status']['name']), + kanban_order=counter, + sprint_order=counter, + backlog_order=counter, + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + tags=issue['fields']['labels'], + external_reference=external_reference, + ) + + points_value = issue['fields'].get(self.greenhopper_fields.get('points', None), None) + if points_value: + (points, _) = Points.objects.get_or_create( + project=project, + value=points_value, + defaults={ + "name": str(points_value), + "order": points_value, + } + ) + RolePoints.objects.filter(user_story=us, role__slug="main").update(points_id=points.id) + else: + points = Points.objects.get(project=project, value__isnull=True) + RolePoints.objects.filter(user_story=us, role__slug="main").update(points_id=points.id) + + self._import_to_custom_fields(us, issue, options) + + us.ref = issue['key'].split("-")[1] + UserStory.objects.filter(id=us.id).update( + ref=us.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + take_snapshot(us, comment="", user=None, delete=False) + self._import_subtasks(project_id, project, us, issue, options) + self._import_comments(us, issue, options) + self._import_attachments(us, issue, options) + self._import_changelog(project, us, issue, options) + counter += 1 + + if len(issues['issues']) < issues['maxResults']: + break + + def _import_subtasks(self, project_id, project, us, issue, options): + users_bindings = options.get('users_bindings', {}) + + if len(issue['fields']['subtasks']) == 0: + return + + counter = 0 + offset = 0 + while True: + issues = self._client.get("/search", { + "jql": "parent={}".format(issue['key']), + "startAt": offset, + "fields": "*all", + "expand": "changelog,attachment", + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", issue['fields']['url']] + + task = Task.objects.create( + user_story=us, + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.task_statuses.get(name=issue['fields']['status']['name']), + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + tags=issue['fields']['labels'], + external_reference=external_reference, + ) + + self._import_to_custom_fields(task, issue, options) + + task.ref = issue['key'].split("-")[1] + Task.objects.filter(id=task.id).update( + ref=task.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + take_snapshot(task, comment="", user=None, delete=False) + for subtask in issue['fields']['subtasks']: + print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key'])) + self._import_comments(task, issue, options) + self._import_attachments(task, issue, options) + self._import_changelog(project, task, issue, options) + counter += 1 + if len(issues['issues']) < issues['maxResults']: + break + + def _import_issues_data(self, project_id, project, options): + users_bindings = options.get('users_bindings', {}) + + types = options.get('types_bindings', {}).get("issue", []) + for issue_type in types: + counter = 0 + offset = 0 + while True: + issues = self._client.get("/search", { + "jql": "project={} AND issuetype={}".format(project_id, issue_type['id']), + "startAt": offset, + "fields": "*all", + "expand": "changelog,attachment", + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", issue['fields']['url']] + + taiga_issue = Issue.objects.create( + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.issue_statuses.get(name=issue['fields']['status']['name']), + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + tags=issue['fields']['labels'], + external_reference=external_reference, + ) + + self._import_to_custom_fields(taiga_issue, issue, options) + + taiga_issue.ref = issue['key'].split("-")[1] + Issue.objects.filter(id=taiga_issue.id).update( + ref=taiga_issue.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + take_snapshot(taiga_issue, comment="", user=None, delete=False) + for subtask in issue['fields']['subtasks']: + print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key'])) + self._import_comments(taiga_issue, issue, options) + self._import_attachments(taiga_issue, issue, options) + self._import_changelog(project, taiga_issue, issue, options) + counter += 1 + + if len(issues['issues']) < issues['maxResults']: + break + + def _import_epics_data(self, project_id, project, options): + users_bindings = options.get('users_bindings', {}) + + types = options.get('types_bindings', {}).get("epic", []) + for issue_type in types: + counter = 0 + offset = 0 + while True: + issues = self._client.get("/search", { + "jql": "project={} AND issuetype={}".format(project_id, issue_type['id']), + "startAt": offset, + "fields": "*all", + "expand": "changelog,attachment", + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key'])) + assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None) + owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["jira", issue['fields']['url']] + + epic = Epic.objects.create( + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.epic_statuses.get(name=issue['fields']['status']['name']), + subject=issue['fields']['summary'], + description=issue['fields']['description'] or '', + epics_order=counter, + tags=issue['fields']['labels'], + external_reference=external_reference, + ) + + self._import_to_custom_fields(epic, issue, options) + + epic.ref = issue['key'].split("-")[1] + Epic.objects.filter(id=epic.id).update( + ref=epic.ref, + modified_date=issue['fields']['updated'], + created_date=issue['fields']['created'] + ) + take_snapshot(epic, comment="", user=None, delete=False) + for subtask in issue['fields']['subtasks']: + print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key'])) + self._import_comments(epic, issue, options) + self._import_attachments(epic, issue, options) + issue_with_changelog = self._client.get("/issue/{}".format(issue['key']), { + "expand": "changelog" + }) + self._import_changelog(project, epic, issue_with_changelog, options) + counter += 1 + + if len(issues['issues']) < issues['maxResults']: + break + + def _link_epics_with_user_stories(self, project_id, project, options): + types = options.get('types_bindings', {}).get("us", []) + for issue_type in types: + offset = 0 + while True: + issues = self._client.get("/search", { + "jql": "project={} AND issuetype={}".format(project_id, issue_type['id']), + "startAt": offset + }) + offset += issues['maxResults'] + + for issue in issues['issues']: + epic_key = issue['fields'][self.greenhopper_fields['link']] + if epic_key: + epic = project.epics.get(ref=int(epic_key.split("-")[1])) + us = project.user_stories.get(ref=int(issue['key'].split("-")[1])) + RelatedUserStory.objects.create( + user_story=us, + epic=epic, + order=1 + ) + + if len(issues['issues']) < issues['maxResults']: + break diff --git a/taiga/importers/jira/tasks.py b/taiga/importers/jira/tasks.py new file mode 100644 index 00000000..01a4c30c --- /dev/null +++ b/taiga/importers/jira/tasks.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import logging +import sys + +from django.utils.translation import ugettext as _ + +from taiga.base.mails import mail_builder +from taiga.users.models import User +from taiga.celery import app +from .normal import JiraNormalImporter +from .agile import JiraAgileImporter + +logger = logging.getLogger('taiga.importers.jira') + + +@app.task(bind=True) +def import_project(self, user_id, url, token, project_id, options, importer_type): + user = User.objects.get(id=user_id) + + if importer_type == "agile": + importer = JiraAgileImporter(user, url, token) + else: + importer = JiraNormalImporter(user, url, token) + + try: + project = importer.import_project(project_id, options) + except Exception as e: + # Error + ctx = { + "user": user, + "error_subject": _("Error importing Jira project"), + "error_message": _("Error importing Jira project"), + "project": project_id, + "exception": e + } + email = mail_builder.importer_import_error(user, ctx) + email.send() + logger.error('Error importing Jira project %s (by %s)', project_id, user, exc_info=sys.exc_info()) + else: + ctx = { + "project": project, + "user": user, + } + email = mail_builder.jira_import_success(user, ctx) + email.send() diff --git a/taiga/importers/management/__init__.py b/taiga/importers/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/importers/management/commands/__init__.py b/taiga/importers/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/importers/management/commands/import_from_asana.py b/taiga/importers/management/commands/import_from_asana.py new file mode 100644 index 00000000..8c82771a --- /dev/null +++ b/taiga/importers/management/commands/import_from_asana.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.management.base import BaseCommand +from django.conf import settings +from django.db.models import Q + +from taiga.importers.asana.importer import AsanaImporter +from taiga.users.models import User + +import json + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('--token', dest="token", type=str, + help='Auth token') + parser.add_argument('--project-id', dest="project_id", type=str, + help='Project ID or full name (ex: taigaio/taiga-back)') + parser.add_argument('--template', dest='template', default="kanban", + help='template to use: scrum or kanban (default kanban)') + parser.add_argument('--type', dest='type', default="user_stories", + help='type of object to use: user_stories or issues (default user_stories)') + parser.add_argument('--ask-for-users', dest='ask_for_users', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True, + action="store_const", default=False, + help='Store external reference of imported data') + + def handle(self, *args, **options): + admin = User.objects.get(username="admin") + + if options.get('token', None): + token = json.loads(options.get('token')) + else: + url = AsanaImporter.get_auth_url( + settings.IMPORTERS.get('asana', {}).get('app_id', None), + settings.IMPORTERS.get('asana', {}).get('app_secret', None), + settings.IMPORTERS.get('asana', {}).get('callback_url', None) + ) + print("Go to here and come with your code (in the redirected url): {}".format(url)) + code = input("Code: ") + access_data = AsanaImporter.get_access_token( + code, + settings.IMPORTERS.get('asana', {}).get('app_id', None), + settings.IMPORTERS.get('asana', {}).get('app_secret', None), + settings.IMPORTERS.get('asana', {}).get('callback_url', None) + ) + token = access_data + + importer = AsanaImporter(admin, token) + + if options.get('project_id', None): + project_id = options.get('project_id') + else: + print("Select the project to import:") + for project in importer.list_projects(): + print("- {}: {}".format(project['id'], project['name'])) + project_id = input("Project id: ") + + users_bindings = {} + if options.get('ask_for_users', None): + print("Add the username or email for next asana users:") + + for user in importer.list_users(project_id): + while True: + if user['detected_user'] is not None: + print("User automatically detected: {} as {}".format(user['full_name'], user['detected_user'])) + users_bindings[user['id']] = user['detected_user'] + break + + if not options.get('ask_for_users', False): + break + + username_or_email = input("{}: ".format(user['full_name'] or user['username'])) + if username_or_email == "": + break + try: + users_bindings[user['id']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) + break + except User.DoesNotExist: + print("ERROR: Invalid username or email") + + options = { + "template": options.get('template'), + "type": options.get('type'), + "users_bindings": users_bindings, + "keep_external_reference": options.get('keep_external_reference') + } + + importer.import_project(project_id, options) diff --git a/taiga/importers/management/commands/import_from_github.py b/taiga/importers/management/commands/import_from_github.py new file mode 100644 index 00000000..330017f8 --- /dev/null +++ b/taiga/importers/management/commands/import_from_github.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.management.base import BaseCommand +from django.conf import settings +from django.db.models import Q + +from taiga.importers.github.importer import GithubImporter +from taiga.users.models import User + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('--token', dest="token", type=str, + help='Auth token') + parser.add_argument('--project-id', dest="project_id", type=str, + help='Project ID or full name (ex: taigaio/taiga-back)') + parser.add_argument('--template', dest='template', default="kanban", + help='template to use: scrum or kanban (default kanban)') + parser.add_argument('--type', dest='type', default="user_stories", + help='type of object to use: user_stories or issues (default user_stories)') + parser.add_argument('--ask-for-users', dest='ask_for_users', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True, + action="store_const", default=False, + help='Store external reference of imported data') + + def handle(self, *args, **options): + admin = User.objects.get(username="admin") + + if options.get('token', None): + token = options.get('token') + else: + url = GithubImporter.get_auth_url( + settings.IMPORTERS.get('github', {}).get('client_id', None) + ) + print("Go to here and come with your code (in the redirected url): {}".format(url)) + code = input("Code: ") + access_data = GithubImporter.get_access_token( + settings.IMPORTERS.get('github', {}).get('client_id', None), + settings.IMPORTERS.get('github', {}).get('client_secret', None), + code + ) + token = access_data + + importer = GithubImporter(admin, token) + + if options.get('project_id', None): + project_id = options.get('project_id') + else: + print("Select the project to import:") + for project in importer.list_projects(): + print("- {}: {}".format(project['id'], project['name'])) + project_id = input("Project id: ") + + users_bindings = {} + if options.get('ask_for_users', None): + print("Add the username or email for next github users:") + + for user in importer.list_users(project_id): + while True: + if user['detected_user'] is not None: + print("User automatically detected: {} as {}".format(user['full_name'], user['detected_user'])) + users_bindings[user['id']] = user['detected_user'] + break + + if not options.get('ask_for_users', False): + break + + username_or_email = input("{}: ".format(user['full_name'] or user['username'])) + if username_or_email == "": + break + try: + users_bindings[user['id']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) + break + except User.DoesNotExist: + print("ERROR: Invalid username or email") + + options = { + "template": options.get('template'), + "type": options.get('type'), + "users_bindings": users_bindings, + "keep_external_reference": options.get('keep_external_reference') + } + + importer.import_project(project_id, options) diff --git a/taiga/importers/management/commands/import_from_jira.py b/taiga/importers/management/commands/import_from_jira.py new file mode 100644 index 00000000..04dc0b87 --- /dev/null +++ b/taiga/importers/management/commands/import_from_jira.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.management.base import BaseCommand +from django.db.models import Q +from django.conf import settings + +from taiga.importers.jira.agile import JiraAgileImporter +from taiga.importers.jira.normal import JiraNormalImporter +from taiga.users.models import User + +import json + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('--token', dest="token", type=str, + help='Auth token') + parser.add_argument('--server', dest="server", type=str, + help='Server address (default: https://jira.atlassian.com)', + default="https://jira.atlassian.com") + parser.add_argument('--project-id', dest="project_id", type=str, + help='Project ID or full name (ex: taigaio/taiga-back)') + parser.add_argument('--project-type', dest="project_type", type=str, + help='Project type in jira: project or board') + parser.add_argument('--template', dest='template', default="scrum", + help='template to use: scrum or scrum (default scrum)') + parser.add_argument('--ask-for-users', dest='ask_for_users', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--closed-data', dest='closed_data', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True, + action="store_const", default=False, + help='Store external reference of imported data') + + def handle(self, *args, **options): + admin = User.objects.get(username="admin") + server = options.get("server") + + if options.get('token', None) == "anon": + token = None + elif options.get('token', None): + token = json.loads(options.get('token')) + else: + (rtoken, rtoken_secret, url) = JiraNormalImporter.get_auth_url( + server, + settings.IMPORTERS.get('jira', {}).get('consumer_key', None), + settings.IMPORTERS.get('jira', {}).get('cert', None), + True + ) + print(url) + input("Go to the url, allow the user and get back and press enter") + token = JiraNormalImporter.get_access_token( + server, + settings.IMPORTERS.get('jira', {}).get('consumer_key', None), + settings.IMPORTERS.get('jira', {}).get('cert', None), + rtoken, + rtoken_secret, + True + ) + print("Auth token: {}".format(json.dumps(token))) + + + if options.get('project_type', None) is None: + print("Select the type of project to import (project or board): ") + project_type = input("Project type: ") + else: + project_type = options.get('project_type') + + if project_type not in ["project", "board"]: + print("ERROR: Bad project type.") + return + + if project_type == "project": + importer = JiraNormalImporter(admin, server, token) + else: + importer = JiraAgileImporter(admin, server, token) + + if options.get('project_id', None): + project_id = options.get('project_id') + else: + print("Select the project to import:") + for project in importer.list_projects(): + print("- {}: {}".format(project['id'], project['name'])) + project_id = input("Project id or key: ") + + users_bindings = {} + if options.get('ask_for_users', None): + print("Add the username or email for next jira users:") + for user in importer.list_users(): + try: + users_bindings[user['key']] = User.objects.get(Q(email=user['email'])) + break + except User.DoesNotExist: + pass + + while True: + username_or_email = input("{}: ".format(user['full_name'])) + if username_or_email == "": + break + try: + users_bindings[user['key']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) + break + except User.DoesNotExist: + print("ERROR: Invalid username or email") + + options = { + "template": options.get('template'), + "import_closed_data": options.get("closed_data", False), + "users_bindings": users_bindings, + "keep_external_reference": options.get('keep_external_reference'), + } + + if project_type == "project": + print("Bind jira issue types to (epic, us, issue)") + types_bindings = { + "epic": [], + "us": [], + "task": [], + "issue": [], + } + + for issue_type in importer.list_issue_types(project_id): + while True: + if issue_type['subtask']: + types_bindings['task'].append(issue_type) + break + + taiga_type = input("{}: ".format(issue_type['name'])) + if taiga_type not in ['epic', 'us', 'issue']: + print("use a valid taiga type (epic, us, issue)") + continue + + types_bindings[taiga_type].append(issue_type) + break + options["types_bindings"] = types_bindings + + importer.import_project(project_id, options) diff --git a/taiga/importers/management/commands/import_from_pivotal.py b/taiga/importers/management/commands/import_from_pivotal.py new file mode 100644 index 00000000..0af57eb5 --- /dev/null +++ b/taiga/importers/management/commands/import_from_pivotal.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.management.base import BaseCommand +from django.db.models import Q + +from taiga.importers.pivotal import PivotalImporter +from taiga.users.models import User + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('--token', dest="token", type=str, + help='Auth token') + parser.add_argument('--project-id', dest="project_id", type=str, + help='Project ID or full name (ex: taigaio/taiga-back)') + parser.add_argument('--template', dest='template', default="scrum", + help='template to use: scrum or scrum (default scrum)') + parser.add_argument('--ask-for-users', dest='ask_for_users', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--closed-data', dest='closed_data', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True, + action="store_const", default=False, + help='Store external reference of imported data') + + def handle(self, *args, **options): + admin = User.objects.get(username="admin") + + if options.get('token', None): + token = options.get('token') + else: + print("You need a user token") + return + + importer = PivotalImporter(admin, token) + + if options.get('project_id', None): + project_id = options.get('project_id') + else: + print("Select the project to import:") + for project in importer.list_projects(): + print("- {}: {}".format(project['project_id'], project['project_name'])) + project_id = input("Project id: ") + + users_bindings = {} + if options.get('ask_for_users', None): + print("Add the username or email for next pivotal users:") + for user in importer.list_users(project_id): + try: + users_bindings[user['id']] = User.objects.get(Q(email=user['person'].get('email', "not-valid"))) + break + except User.DoesNotExist: + pass + + while True: + username_or_email = input("{}: ".format(user['person']['name'])) + if username_or_email == "": + break + try: + users_bindings[user['id']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) + break + except User.DoesNotExist: + print("ERROR: Invalid username or email") + + options = { + "template": options.get('template'), + "import_closed_data": options.get("closed_data", False), + "users_bindings": users_bindings, + "keep_external_reference": options.get('keep_external_reference') + } + importer.import_project(project_id, options) diff --git a/taiga/importers/management/commands/import_from_trello.py b/taiga/importers/management/commands/import_from_trello.py new file mode 100644 index 00000000..31ab0c9a --- /dev/null +++ b/taiga/importers/management/commands/import_from_trello.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.management.base import BaseCommand +from django.db.models import Q + +from taiga.importers.trello.importer import TrelloImporter +from taiga.users.models import User + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('--token', dest="token", type=str, + help='Auth token') + parser.add_argument('--project-id', dest="project_id", type=str, + help='Project ID or full name (ex: taigaio/taiga-back)') + parser.add_argument('--template', dest='template', default="kanban", + help='template to use: scrum or kanban (default kanban)') + parser.add_argument('--ask-for-users', dest='ask_for_users', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--closed-data', dest='closed_data', const=True, + action="store_const", default=False, + help='Import closed data') + parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True, + action="store_const", default=False, + help='Store external reference of imported data') + + def handle(self, *args, **options): + admin = User.objects.get(username="admin") + if options.get('token', None): + token = options.get('token') + else: + (oauth_token, oauth_token_secret, url) = TrelloImporter.get_auth_url() + print("Go to here and come with your token: {}".format(url)) + oauth_verifier = input("Code: ") + access_data = TrelloImporter.get_access_token(oauth_token, oauth_token_secret, oauth_verifier) + token = access_data['oauth_token'] + print("Access token: {}".format(token)) + importer = TrelloImporter(admin, token) + + if options.get('project_id', None): + project_id = options.get('project_id') + else: + print("Select the project to import:") + for project in importer.list_projects(): + print("- {}: {}".format(project['id'], project['name'])) + project_id = input("Project id: ") + + users_bindings = {} + if options.get('ask_for_users', None): + print("Add the username or email for next trello users:") + for user in importer.list_users(project_id): + while True: + username_or_email = input("{}: ".format(user['fullName'])) + if username_or_email == "": + break + try: + users_bindings[user['id']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) + break + except User.DoesNotExist: + print("ERROR: Invalid username or email") + + options = { + "template": options.get('template'), + "import_closed_data": options.get("closed_data", False), + "users_bindings": users_bindings, + "keep_external_reference": options.get('keep_external_reference') + } + importer.import_project(project_id, options) diff --git a/taiga/importers/permissions.py b/taiga/importers/permissions.py new file mode 100644 index 00000000..9268c2ef --- /dev/null +++ b/taiga/importers/permissions.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base.api.permissions import TaigaResourcePermission, IsAuthenticated + + +class ImporterPermission(TaigaResourcePermission): + enought_perms = IsAuthenticated() + global_perms = None + auth_url_perms = IsAuthenticated() + authorize_perms = IsAuthenticated() + list_users_perms = IsAuthenticated() + list_projects_perms = IsAuthenticated() + import_project_perms = IsAuthenticated() diff --git a/taiga/importers/pivotal/api.py b/taiga/importers/pivotal/api.py new file mode 100644 index 00000000..967ea508 --- /dev/null +++ b/taiga/importers/pivotal/api.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Taiga Agile LLC +# 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.translation import ugettext as _ +from django.conf import settings + +from taiga.base.api import viewsets +from taiga.base import response +from taiga.base import exceptions as exc +from taiga.base.decorators import list_route +from taiga.users.models import AuthData, User +from taiga.users.services import get_user_photo_url +from taiga.users.gravatar import get_user_gravatar_id + +from taiga.importers import permissions +from .importer import PivotalImporter +from . import tasks + + +class PivotalImporterViewSet(viewsets.ViewSet): + permission_classes = (permissions.ImporterPermission,) + + @list_route(methods=["POST"]) + def list_users(self, request, *args, **kwargs): + self.check_permissions(request, "list_users", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + importer = PivotalImporter(request.user, token) + users = importer.list_users(project_id) + for user in users: + user['user'] = None + if not user['email']: + continue + + try: + taiga_user = User.objects.get(email=user['email']) + except User.DoesNotExist: + continue + + user['user'] = { + 'id': taiga_user.id, + 'full_name': taiga_user.get_full_name(), + 'gravatar_id': get_user_gravatar_id(taiga_user), + 'photo': get_user_photo_url(taiga_user), + } + return response.Ok(users) + + @list_route(methods=["POST"]) + def list_projects(self, request, *args, **kwargs): + self.check_permissions(request, "list_projects", None) + token = request.DATA.get('token', None) + importer = PivotalImporter(request.user, token) + projects = importer.list_projects() + return response.Ok(projects) + + @list_route(methods=["POST"]) + def import_project(self, request, *args, **kwargs): + self.check_permissions(request, "import_project", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + options = { + "template": request.DATA.get('template', "kanban"), + "users_bindings": request.DATA.get("users_bindings", {}), + "keep_external_reference": request.DATA.get("keep_external_reference", False), + "is_private": request.DATA.get("is_private", False), + } + + if settings.CELERY_ENABLED: + task = tasks.import_project.delay(request.user.id, token, project_id, options) + return response.Accepted({"pivotal_import_id": task.id}) + + importer = PivotalImporter(request.user, token) + project = importer.import_project(project_id, options) + project_data = { + "slug": project.slug, + "my_permissions": ["view_us"], + "is_backlog_activated": project.is_backlog_activated, + "is_kanban_activated": project.is_kanban_activated, + } + + return response.Ok(project_data) + + @list_route(methods=["GET"]) + def auth_url(self, request, *args, **kwargs): + self.check_permissions(request, "auth_url", None) + + (oauth_token, oauth_secret, url) = PivotalImporter.get_auth_url() + + (auth_data, created) = AuthData.objects.get_or_create( + user=request.user, + key="pivotal-oauth", + defaults={ + "value": "", + "extra": {}, + } + ) + auth_data.extra = { + "oauth_token": oauth_token, + "oauth_secret": oauth_secret, + } + auth_data.save() + + return response.Ok({"url": url}) + + @list_route(methods=["POST"]) + def authorize(self, request, *args, **kwargs): + self.check_permissions(request, "authorize", None) + + try: + oauth_data = request.user.auth_data.get(key="pivotal-oauth") + oauth_token = oauth_data.extra['oauth_token'] + oauth_secret = oauth_data.extra['oauth_secret'] + oauth_verifier = request.DATA.get('code') + oauth_data.delete() + pivotal_token = PivotalImporter.get_access_token(oauth_token, oauth_secret, oauth_verifier)['oauth_token'] + except Exception as e: + raise exc.WrongArguments(_("Invalid or expired auth token")) + + return response.Ok({ + "token": pivotal_token + }) diff --git a/taiga/importers/pivotal/importer.py b/taiga/importers/pivotal/importer.py new file mode 100644 index 00000000..c49da4ab --- /dev/null +++ b/taiga/importers/pivotal/importer.py @@ -0,0 +1,720 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.files.base import ContentFile +from django.contrib.contenttypes.models import ContentType +import requests + +from taiga.users.models import User +from taiga.projects.references.models import recalc_reference_counter +from taiga.projects.models import Project, ProjectTemplate, Membership, Points +from taiga.projects.userstories.models import UserStory, RolePoints +from taiga.projects.tasks.models import Task +from taiga.projects.milestones.models import Milestone +from taiga.projects.epics.models import Epic, RelatedUserStory +from taiga.projects.attachments.models import Attachment +from taiga.projects.history.services import take_snapshot +from taiga.projects.history.services import (make_diff_from_dicts, + make_diff_values, + make_key_from_model_object, + get_typename_for_model_class, + FrozenDiff) +from taiga.projects.history.models import HistoryEntry +from taiga.projects.history.choices import HistoryType +from taiga.projects.custom_attributes.models import UserStoryCustomAttribute +from taiga.mdrender.service import render as mdrender +from taiga.timeline.rebuilder import rebuild_timeline +from taiga.timeline.models import Timeline + + +class PivotalClient: + def __init__(self, token): + self.api_url = "https://www.pivotaltracker.com/services/v5/{}" + self.token = token + self.me = self.get('/me') + + def get(self, uri_path, query_params=None): + headers = { + 'X-TrackerToken': self.token + } + if query_params is None: + query_params = {} + + if uri_path[0] == '/': + uri_path = uri_path[1:] + url = self.api_url.format(uri_path) + + response = requests.get(url, params=query_params, headers=headers) + + if response.status_code == 401: + raise Exception("Unauthorized: %s at %s" % (response.text, url), response) + if response.status_code != 200: + raise Exception("Resource Unavailable: %s at %s" % (response.text, url), response) + + return response.json() + + def get_attachment(self, attachment_id): + headers = { + 'X-TrackerToken': self.token + } + url = "https://www.pivotaltracker.com/file_attachments/{}/download".format(attachment_id) + response = requests.get(url, headers=headers) + return response.content + + +class PivotalImporter: + def __init__(self, user, token): + self._user = user + self._client = PivotalClient(token=token) + + def list_projects(self): + return self._client.me['projects'] + + def list_users(self, project_id): + return self._client.get("/projects/{}/memberships".format(project_id)) + + def import_project(self, project_id, options={"template": "scrum", "users_bindings": {}, "keep_external_reference": False}): + (project, project_data) = self._import_project_data(project_id, options) + self._import_epics_data(project_data, project, options) + self._import_user_stories_data(project_data, project, options) + Timeline.objects.filter(project=project).delete() + rebuild_timeline(None, None, project.id) + recalc_reference_counter(project) + + def _import_project_data(self, project_id, options): + project_data = self._client.get( + "/projects/{}".format(project_id), + { + "fields": ",".join([ + "point_scale", + "name", + "description", + "labels(name)", + ]) + } + ) + project_data['iterations'] = self._client.get( + "/projects/{}/iterations".format(project_id), + { + "fields": ",".join([ + "number", + "start", + "finish", + "stories", + ]) + } + ) + project_data['epics'] = self._client.get( + "/projects/{}/epics".format(project_data['id']), + { + "fields": ",".join([ + "name", + "label", + "description", + "comments(text,file_attachments,google_attachments,person,created_at)", + "follower_ids", + "created_at", + "updated_at", + "url", + ]) + } + ) + + project_template = ProjectTemplate.objects.get(slug=options['template']) + project_template.is_epics_activated = True + project_template.us_statuses = [] + project_template.points = [{ + "value": None, + "name": "?", + "order": 1, + }] + + counter = 2 + for points in project_data['point_scale'].split(","): + project_template.points.append({ + "value": int(points), + "name": points, + "order": counter + }) + counter += 1 + + project_template.us_statuses.append({ + "name": "Unscheduled", + "slug": "unscheduled", + "is_closed": True, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": 1, + }) + project_template.us_statuses.append({ + "name": "Unstarted", + "slug": "unstarted", + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": 2, + }) + project_template.us_statuses.append({ + "name": "Planned", + "slug": "planned", + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": 3, + }) + project_template.us_statuses.append({ + "name": "Started", + "slug": "started", + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": 4, + }) + project_template.us_statuses.append({ + "name": "Finished", + "slug": "finished", + "is_closed": False, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": 5, + }) + project_template.us_statuses.append({ + "name": "Delivered", + "slug": "delivered", + "is_closed": True, + "is_archived": False, + "color": "#999999", + "wip_limit": None, + "order": 6, + }) + project_template.us_statuses.append({ + "name": "Rejected", + "slug": "rejected", + "is_closed": True, + "is_archived": True, + "color": "#999999", + "wip_limit": None, + "order": 7, + }) + project_template.us_statuses.append({ + "name": "Accepted", + "slug": "accepted", + "is_closed": True, + "is_archived": True, + "color": "#999999", + "wip_limit": None, + "order": 8, + }) + project_template.default_options["us_status"] = "Unscheduled" + + project_template.task_statuses = [] + project_template.task_statuses.append({ + "name": "Incomplete", + "slug": "incomplete", + "is_closed": False, + "color": "#ff8a84", + "order": 1, + }) + project_template.task_statuses.append({ + "name": "Complete", + "slug": "complete", + "is_closed": True, + "color": "#669900", + "order": 2, + }) + project_template.default_options["task_status"] = "Incomplete" + + main_permissions = project_template.roles[0]['permissions'] + project_template.roles = [{ + "name": "Main", + "slug": "main", + "computable": True, + "permissions": main_permissions, + "order": 70, + }] + + tags_colors = [] + for label in project_data['labels']: + name = label['name'].lower() + tags_colors.append([name, None]) + + project = Project.objects.create( + name=project_data['name'], + description=project_data.get('description', ''), + owner=self._user, + tags_colors=tags_colors, + creation_template=project_template + ) + + UserStoryCustomAttribute.objects.create( + name="Due date", + description="Due date", + type="date", + order=1, + project=project + ) + UserStoryCustomAttribute.objects.create( + name="Type", + description="Story type", + type="text", + order=2, + project=project + ) + for user in options.get('users_bindings', {}).values(): + if user != self._user: + Membership.objects.get_or_create( + user=user, + project=project, + role=project.get_roles().get(slug="main"), + is_admin=False, + ) + + for iteration in project_data['iterations']: + milestone = Milestone.objects.create( + name="Sprint {}".format(iteration['number']), + slug="sprint-{}".format(iteration['number']), + owner=self._user, + project=project, + estimated_start=iteration['start'][:10], + estimated_finish=iteration['finish'][:10], + ) + Milestone.objects.filter(id=milestone.id).update( + created_date=iteration['start'], + modified_date=iteration['start'], + ) + return (project, project_data) + + def _import_user_stories_data(self, project_data, project, options): + users_bindings = options.get('users_bindings', {}) + epics = {e['label']['id']: e for e in project_data['epics']} + due_date_field = project.userstorycustomattributes.get(name="Due date") + story_type_field = project.userstorycustomattributes.get(name="Type") + story_milestone_binding = {} + for iteration in project_data['iterations']: + for story in iteration['stories']: + story_milestone_binding[story['id']] = Milestone.objects.get( + project=project, + slug="sprint-{}".format(iteration['number']) + ) + + counter = 0 + offset = 0 + while True: + stories = self._client.get("/projects/{}/stories".format(project_data['id']), { + "envelope": "true", + "limit": 300, + "offset": offset, + "fields": ",".join([ + "name", + "description", + "estimate", + "story_type", + "current_state", + "deadline", + "requested_by_id", + "owner_ids", + "labels(id,name)", + "comments(text,file_attachments,google_attachments,person,created_at)", + "tasks(id,description,position,complete,created_at,updated_at)", + "follower_ids", + "created_at", + "updated_at", + "url", + ])}) + offset += 300 + for story in stories['data']: + tags = [] + for label in story['labels']: + tags.append(label['name']) + + assigned_to = None + if len(story['owner_ids']) > 0: + assigned_to = users_bindings.get(story['owner_ids'][0], None) + + owner = users_bindings.get(story['requested_by_id'], self._user) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["pivotal", story['url']] + + us = UserStory.objects.create( + project=project, + owner=owner, + assigned_to=assigned_to, + status=project.us_statuses.get(slug=story['current_state']), + kanban_order=counter, + sprint_order=counter, + backlog_order=counter, + subject=story['name'], + description=story.get('description', ''), + tags=tags, + external_reference=external_reference, + milestone=story_milestone_binding.get(story['id'], None) + ) + + points = Points.objects.get(project=project, value=story.get('estimate', None)) + RolePoints.objects.filter(user_story=us, role__slug="main").update(points_id=points.id) + + if len(story['owner_ids']) > 1: + watchers = list(set(story['owner_ids'][1:] + story['follower_ids'])) + else: + watchers = story['follower_ids'] + + for watcher in watchers: + watcher_user = users_bindings.get(watcher, None) + if watcher_user: + us.add_watcher(watcher_user) + + if story.get('deadline', None): + us.custom_attributes_values.attributes_values = {due_date_field.id: story['deadline']} + us.custom_attributes_values.save() + if story.get('story_type', None): + us.custom_attributes_values.attributes_values = {story_type_field.id: story['story_type']} + us.custom_attributes_values.save() + + UserStory.objects.filter(id=us.id).update( + ref=story['id'], + modified_date=story['updated_at'], + created_date=story['created_at'] + ) + take_snapshot(us, comment="", user=None, delete=False) + + for label in story['labels']: + if epics.get(label['id'], None): + RelatedUserStory.objects.create( + epic=Epic.objects.get(project=project, ref=epics.get(label['id'])['id']), + user_story=us, + order=us.backlog_order + ) + self._import_tasks(project_data, us, story) + self._import_user_story_activity(project_data, us, story, options) + self._import_comments(project_data, us, story, options) + counter += 1 + + if len(stories['data']) < 300: + break + + def _import_epics_data(self, project_data, project, options): + users_bindings = options.get('users_bindings', {}) + counter = 0 + + for epic in project_data['epics']: + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["pivotal", epic['url']] + + taiga_epic = Epic.objects.create( + project=project, + owner=self._user, + status=project.epic_statuses.get(slug="new"), + epics_order=counter, + subject=epic['name'], + description=epic.get('description', ''), + tags=[], + external_reference=external_reference + ) + + Epic.objects.filter(id=taiga_epic.id).update( + ref=epic['id'], + modified_date=epic['updated_at'], + created_date=epic['created_at'] + ) + + for watcher in epic['follower_ids']: + watcher_user = users_bindings.get(watcher, None) + if watcher_user: + taiga_epic.add_watcher(watcher_user) + + take_snapshot(taiga_epic, comment="", user=None, delete=False) + self._import_comments(project_data, taiga_epic, epic, options) + self._import_epic_activity(project_data, taiga_epic, epic, options) + counter += 1 + + def _import_tasks(self, project_data, us, story): + for task in story['tasks']: + taiga_task = Task.objects.create( + subject=task['description'], + status=us.project.task_statuses.get(slug="complete" if task['complete'] else "incomplete"), + project=us.project, + us_order=task['position'], + taskboard_order=task['position'], + user_story=us + ) + + Task.objects.filter(id=taiga_task.id).update( + ref=task['id'], + modified_date=task['updated_at'], + created_date=task['created_at'] + ) + take_snapshot(taiga_task, comment="", user=None, delete=False) + + def _import_attachment(self, obj, attachment_id, attachment_name, created_at, person_id, options): + users_bindings = options.get('users_bindings', {}) + + data = self._client.get_attachment(attachment_id) + att = Attachment( + owner=users_bindings.get(person_id, self._user), + project=obj.project, + content_type=ContentType.objects.get_for_model(obj), + object_id=obj.id, + name=attachment_name, + size=len(data), + created_date=created_at, + is_deprecated=False, + ) + att.attached_file.save(attachment_name, ContentFile(data), save=True) + + def _import_comments(self, project_data, obj, story, options): + users_bindings = options.get('users_bindings', {}) + + for comment in story['comments']: + if 'text' in comment: + snapshot = take_snapshot( + obj, + comment=comment['text'], + user=users_bindings.get(comment['person']['id'], User(full_name=comment['person']['name'])), + delete=False + ) + HistoryEntry.objects.filter(id=snapshot.id).update(created_at=comment['created_at']) + for attachment in comment['file_attachments']: + self._import_attachment( + obj, + attachment['id'], + attachment['filename'], + comment['created_at'], + comment['person']['id'], + options + ) + + def _import_user_story_activity(self, project_data, us, story, options): + offset = 0 + while True: + activities = self._client.get( + "/projects/{}/stories/{}/activity".format( + project_data['id'], + story['id'], + ), + {"envelope": "true", "limit": 300, "offset": offset} + ) + offset += 300 + for activity in activities['data']: + self._import_activity(us, activity, options) + + if len(activities['data']) < 300: + break + + def _import_epic_activity(self, project_data, taiga_epic, epic, options): + offset = 0 + while True: + activities = self._client.get( + "/projects/{}/epics/{}/activity".format( + project_data['id'], + epic['id'], + ), + {"envelope": "true", "limit": 300, "offset": offset} + ) + offset += 300 + for activity in activities['data']: + self._import_activity(taiga_epic, activity, options) + + if len(activities['data']) < 300: + break + + def _import_activity(self, obj, activity, options): + activity_data = self._transform_activity_data(obj, activity, options) + if activity_data is None: + return + + change_old = activity_data['change_old'] + change_new = activity_data['change_new'] + hist_type = activity_data['hist_type'] + comment = activity_data['comment'] + user = activity_data['user'] + + key = make_key_from_model_object(activity_data['obj']) + typename = get_typename_for_model_class(type(activity_data['obj'])) + + diff = make_diff_from_dicts(change_old, change_new) + fdiff = FrozenDiff(key, diff, {}) + + entry = HistoryEntry.objects.create( + user=user, + project_id=obj.project.id, + key=key, + type=hist_type, + snapshot=None, + diff=fdiff.diff, + values=make_diff_values(typename, fdiff), + comment=comment, + comment_html=mdrender(obj.project, comment), + is_hidden=False, + is_snapshot=False, + ) + HistoryEntry.objects.filter(id=entry.id).update(created_at=activity['occurred_at']) + return HistoryEntry.objects.get(id=entry.id) + + def _transform_activity_data(self, obj, activity, options): + users_bindings = options.get('users_bindings', {}) + due_date_field = obj.project.userstorycustomattributes.get(name="Due date") + story_type_field = obj.project.userstorycustomattributes.get(name="Type") + + user = {"pk": None, "name": activity.get('performed_by', {}).get('name', None)} + taiga_user = users_bindings.get(activity.get('performed_by', {}).get('id', None), None) + if taiga_user: + user = {"pk": taiga_user.id, "name": taiga_user.get_full_name()} + + result = { + "change_old": {}, + "change_new": {}, + "hist_type": HistoryType.change, + "comment": "", + "user": user, + "obj": obj + } + + if activity['kind'] == "story_create_activity": + UserStory.objects.filter(id=obj.id, created_date__gt=activity['occurred_at']).update( + created_date=activity['occurred_at'], + owner=users_bindings.get(activity["performed_by"]["id"], self._user) + ) + return None + elif activity['kind'] == "epic_create_activity": + Epic.objects.filter(id=obj.id, created_date__gt=activity['occurred_at']).update( + created_date=activity['occurred_at'], + owner=users_bindings.get(activity["performed_by"]["id"], self._user) + ) + return None + elif activity['kind'] in ["story_update_activity", "epic_update_activity"]: + for change in activity['changes']: + if change['change_type'] != "update" or change['kind'] not in ["story", "epic"]: + continue + + if 'description' in change['new_values']: + result['change_old']["description"] = str(change['original_values']['description']) + result['change_new']["description"] = str(change['new_values']['description']) + result['change_old']["description_html"] = mdrender(obj.project, str(change['original_values']['description'])) + result['change_new']["description_html"] = mdrender(obj.project, str(change['new_values']['description'])) + + if 'estimate' in change['new_values']: + old_points = None + if change['original_values']['estimate']: + estimation = change['original_values']['estimate'] + (old_points, _) = Points.objects.get_or_create( + project=obj.project, + value=estimation, + defaults={ + "name": str(estimation), + "order": estimation, + } + ) + old_points = old_points.id + new_points = None + if change['new_values']['estimate']: + estimation = change['new_values']['estimate'] + (new_points, _) = Points.objects.get_or_create( + project=obj.project, + value=estimation, + defaults={ + "name": str(estimation), + "order": estimation, + } + ) + new_points = new_points.id + result['change_old']["points"] = {obj.project.roles.get(slug="main").id: old_points} + result['change_new']["points"] = {obj.project.roles.get(slug="main").id: new_points} + + if 'name' in change['new_values']: + result['change_old']["subject"] = change['original_values']['name'] + result['change_new']["subject"] = change['new_values']['name'] + + if 'labels' in change['new_values']: + result['change_old']["tags"] = [l.lower() for l in change['original_values']['labels']] + result['change_new']["tags"] = [l.lower() for l in change['new_values']['labels']] + + if 'current_state' in change['new_values']: + result['change_old']["status"] = obj.project.us_statuses.get(slug=change['original_values']['current_state']).id + result['change_new']["status"] = obj.project.us_statuses.get(slug=change['new_values']['current_state']).id + + if 'story_type' in change['new_values']: + if "custom_attributes" not in result['change_old']: + result['change_old']["custom_attributes"] = [] + if "custom_attributes" not in result['change_new']: + result['change_new']["custom_attributes"] = [] + + result['change_old']["custom_attributes"].append({ + "name": "Type", + "value": change['original_values']['story_type'], + "id": story_type_field.id + }) + result['change_new']["custom_attributes"].append({ + "name": "Type", + "value": change['new_values']['story_type'], + "id": story_type_field.id + }) + + if 'deadline' in change['new_values']: + if "custom_attributes" not in result['change_old']: + result['change_old']["custom_attributes"] = [] + if "custom_attributes" not in result['change_new']: + result['change_new']["custom_attributes"] = [] + + result['change_old']["custom_attributes"].append({ + "name": "Due date", + "value": change['original_values']['deadline'], + "id": due_date_field.id + }) + result['change_new']["custom_attributes"].append({ + "name": "Due date", + "value": change['new_values']['deadline'], + "id": due_date_field.id + }) + + # TODO: Process owners_ids + + elif activity['kind'] == "task_create_activity": + return None + elif activity['kind'] == "task_update_activity": + for change in activity['changes']: + if change['change_type'] != "update" or change['kind'] != "task": + continue + + try: + task = Task.objects.get(project=obj.project, ref=change['id']) + if 'description' in change['new_values']: + result['change_old']["subject"] = change['original_values']['description'] + result['change_new']["subject"] = change['new_values']['description'] + result['obj'] = task + if 'complete' in change['new_values']: + result['change_old']["status"] = obj.project.task_statuses.get(slug="complete" if change['original_values']['complete'] else "incomplete").id + result['change_new']["status"] = obj.project.task_statuses.get(slug="complete" if change['new_values']['complete'] else "incomplete").id + result['obj'] = task + except Task.DoesNotExist: + return None + + elif activity['kind'] == "comment_create_activity": + return None + elif activity['kind'] == "comment_update_activity": + return None + elif activity['kind'] == "story_move_activity": + return None + return result diff --git a/taiga/importers/pivotal/tasks.py b/taiga/importers/pivotal/tasks.py new file mode 100644 index 00000000..2cb87902 --- /dev/null +++ b/taiga/importers/pivotal/tasks.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import logging +import sys + +from django.utils.translation import ugettext as _ + +from taiga.base.mails import mail_builder +from taiga.users.models import User +from taiga.celery import app +from .importer import PivotalImporter + +logger = logging.getLogger('taiga.importers.pivotal') + + +@app.task(bind=True) +def import_project(self, user_id, token, project_id, options): + user = User.object.get(id=user_id) + importer = PivotalImporter(user, token) + try: + project = importer.import_project(project_id, options) + except Exception as e: + # Error + ctx = { + "user": user, + "error_subject": _("Error importing PivotalTracker project"), + "error_message": _("Error importing PivotalTracker project"), + "project": project_id, + "exception": e + } + email = mail_builder.importer_import_error(user, ctx) + email.send() + logger.error('Error importing PivotalTracker project %s (by %s)', project_id, user, exc_info=sys.exc_info()) + else: + ctx = { + "project": project, + "user": user, + } + email = mail_builder.pivotal_import_success(user, ctx) + email.send() diff --git a/taiga/importers/services.py b/taiga/importers/services.py new file mode 100644 index 00000000..1f99e446 --- /dev/null +++ b/taiga/importers/services.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.users.models import User +from taiga.projects.models import Membership + + +def resolve_users_bindings(users_bindings): + new_users_bindings = {} + for key,value in users_bindings.items(): + try: + user_key = int(key) + except ValueError: + user_key = key + + if isinstance(value, str): + try: + new_users_bindings[user_key] = User.objects.get(email__iexact=value) + except User.MultipleObjectsReturned: + new_users_bindings[user_key] = User.objects.get(email=value) + except User.DoesNotExist: + new_users_bindings[user_key] = None + else: + new_users_bindings[user_key] = User.objects.get(id=value) + return new_users_bindings + +def create_memberships(users_bindings, project, creator, role_name): + for user in users_bindings.values(): + if Membership.objects.filter(project=project, user=user).count() > 0: + continue + Membership.objects.create( + user=user, + project=project, + role=project.get_roles().get(slug=role_name), + is_admin=False, + invited_by=creator, + ) diff --git a/taiga/importers/templates/emails/asana_import_success-body-html.jinja b/taiga/importers/templates/emails/asana_import_success-body-html.jinja new file mode 100644 index 00000000..d14d1cb2 --- /dev/null +++ b/taiga/importers/templates/emails/asana_import_success-body-html.jinja @@ -0,0 +1,11 @@ +{% extends "emails/base-body-html.jinja" %} + +{% block body %} + {% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +

Asana Project imported

+

Hello {{ user }},

+

Your Asana project has been correctly imported.

+ Go to {{ project }} +

The Taiga Team

+ {% endtrans %} +{% endblock %} diff --git a/taiga/importers/templates/emails/asana_import_success-body-text.jinja b/taiga/importers/templates/emails/asana_import_success-body-text.jinja new file mode 100644 index 00000000..a66c54f3 --- /dev/null +++ b/taiga/importers/templates/emails/asana_import_success-body-text.jinja @@ -0,0 +1,12 @@ +{% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +Hello {{ user }}, + +Your Asana project has been correctly imported. + +You can see the project {{ project }} here: + +{{ url }} + +--- +The Taiga Team +{% endtrans %} diff --git a/taiga/importers/templates/emails/asana_import_success-subject.jinja b/taiga/importers/templates/emails/asana_import_success-subject.jinja new file mode 100644 index 00000000..57f7766a --- /dev/null +++ b/taiga/importers/templates/emails/asana_import_success-subject.jinja @@ -0,0 +1 @@ +{% trans project=project.name|safe %}[{{ project }}] Your Asana project has been imported{% endtrans %} diff --git a/taiga/importers/templates/emails/github_import_success-body-html.jinja b/taiga/importers/templates/emails/github_import_success-body-html.jinja new file mode 100644 index 00000000..6f2d31d3 --- /dev/null +++ b/taiga/importers/templates/emails/github_import_success-body-html.jinja @@ -0,0 +1,11 @@ +{% extends "emails/base-body-html.jinja" %} + +{% block body %} + {% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +

GitHub Project imported

+

Hello {{ user }},

+

Your GitHub project has been correctly imported.

+ Go to {{ project }} +

The Taiga Team

+ {% endtrans %} +{% endblock %} diff --git a/taiga/importers/templates/emails/github_import_success-body-text.jinja b/taiga/importers/templates/emails/github_import_success-body-text.jinja new file mode 100644 index 00000000..c1229238 --- /dev/null +++ b/taiga/importers/templates/emails/github_import_success-body-text.jinja @@ -0,0 +1,12 @@ +{% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +Hello {{ user }}, + +Your GitHub project has been correctly imported. + +You can see the project {{ project }} here: + +{{ url }} + +--- +The Taiga Team +{% endtrans %} diff --git a/taiga/importers/templates/emails/github_import_success-subject.jinja b/taiga/importers/templates/emails/github_import_success-subject.jinja new file mode 100644 index 00000000..dbc2d890 --- /dev/null +++ b/taiga/importers/templates/emails/github_import_success-subject.jinja @@ -0,0 +1 @@ +{% trans project=project.name|safe %}[{{ project }}] Your GitHub project has been imported{% endtrans %} diff --git a/taiga/importers/templates/emails/importer_import_error-body-html.jinja b/taiga/importers/templates/emails/importer_import_error-body-html.jinja new file mode 100644 index 00000000..52afe388 --- /dev/null +++ b/taiga/importers/templates/emails/importer_import_error-body-html.jinja @@ -0,0 +1,12 @@ +{% extends "emails/base-body-html.jinja" %} + +{% block body %} + {% trans user=user.get_full_name(), error_message=error_message, support_email=sr("support.email") %} +

{{ error_message }}

+

Hello {{ user }},

+

Your project has not been importer correctly.

+

The Taiga system administrators have been informed.
Please, try it again or contact with the support team at + {{ support_email }}

+

The Taiga Team

+ {% endtrans %} +{% endblock %} diff --git a/taiga/importers/templates/emails/importer_import_error-body-text.jinja b/taiga/importers/templates/emails/importer_import_error-body-text.jinja new file mode 100644 index 00000000..7a9d782e --- /dev/null +++ b/taiga/importers/templates/emails/importer_import_error-body-text.jinja @@ -0,0 +1,14 @@ +{% trans user=user.get_full_name(), error_message=error_message, support_email=sr("support.email") %} +Hello {{ user }}, + +{{ error_message }} + +Your project has not been importer correctly. + +The Taiga system administrators have been informed. + +Please, try it again or contact with the support team at {{ support_email }} + +--- +The Taiga Team +{% endtrans %} diff --git a/taiga/importers/templates/emails/importer_import_error-subject.jinja b/taiga/importers/templates/emails/importer_import_error-subject.jinja new file mode 100644 index 00000000..329983d4 --- /dev/null +++ b/taiga/importers/templates/emails/importer_import_error-subject.jinja @@ -0,0 +1 @@ +{% trans error_subject=error_subject|safe %}[Taiga] {{ error_subject }}{% endtrans %} diff --git a/taiga/importers/templates/emails/jira_import_success-body-html.jinja b/taiga/importers/templates/emails/jira_import_success-body-html.jinja new file mode 100644 index 00000000..51da9de9 --- /dev/null +++ b/taiga/importers/templates/emails/jira_import_success-body-html.jinja @@ -0,0 +1,11 @@ +{% extends "emails/base-body-html.jinja" %} + +{% block body %} + {% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +

Jira Project imported

+

Hello {{ user }},

+

Your Jira project has been correctly imported.

+ Go to {{ project }} +

The Taiga Team

+ {% endtrans %} +{% endblock %} diff --git a/taiga/importers/templates/emails/jira_import_success-body-text.jinja b/taiga/importers/templates/emails/jira_import_success-body-text.jinja new file mode 100644 index 00000000..b2b04ffe --- /dev/null +++ b/taiga/importers/templates/emails/jira_import_success-body-text.jinja @@ -0,0 +1,12 @@ +{% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +Hello {{ user }}, + +Your Jira project has been correctly imported. + +You can see the project {{ project }} here: + +{{ url }} + +--- +The Taiga Team +{% endtrans %} diff --git a/taiga/importers/templates/emails/jira_import_success-subject.jinja b/taiga/importers/templates/emails/jira_import_success-subject.jinja new file mode 100644 index 00000000..406abc00 --- /dev/null +++ b/taiga/importers/templates/emails/jira_import_success-subject.jinja @@ -0,0 +1 @@ +{% trans project=project.name|safe %}[{{ project }}] Your Jira project has been imported{% endtrans %} diff --git a/taiga/importers/templates/emails/trello_import_success-body-html.jinja b/taiga/importers/templates/emails/trello_import_success-body-html.jinja new file mode 100644 index 00000000..d154666b --- /dev/null +++ b/taiga/importers/templates/emails/trello_import_success-body-html.jinja @@ -0,0 +1,11 @@ +{% extends "emails/base-body-html.jinja" %} + +{% block body %} + {% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +

Trello Project imported

+

Hello {{ user }},

+

Your Trello project has been correctly imported.

+ Go to {{ project }} +

The Taiga Team

+ {% endtrans %} +{% endblock %} diff --git a/taiga/importers/templates/emails/trello_import_success-body-text.jinja b/taiga/importers/templates/emails/trello_import_success-body-text.jinja new file mode 100644 index 00000000..9faf38b6 --- /dev/null +++ b/taiga/importers/templates/emails/trello_import_success-body-text.jinja @@ -0,0 +1,12 @@ +{% trans user=user.get_full_name(), url=resolve_front_url("project", project.slug), project=project.name %} +Hello {{ user }}, + +Your Trello project has been correctly imported. + +You can see the project {{ project }} here: + +{{ url }} + +--- +The Taiga Team +{% endtrans %} diff --git a/taiga/importers/templates/emails/trello_import_success-subject.jinja b/taiga/importers/templates/emails/trello_import_success-subject.jinja new file mode 100644 index 00000000..2e86948c --- /dev/null +++ b/taiga/importers/templates/emails/trello_import_success-subject.jinja @@ -0,0 +1 @@ +{% trans project=project.name|safe %}[{{ project }}] Your Trello project has been imported{% endtrans %} diff --git a/taiga/importers/trello/api.py b/taiga/importers/trello/api.py new file mode 100644 index 00000000..8cda25b7 --- /dev/null +++ b/taiga/importers/trello/api.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Taiga Agile LLC +# 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 uuid + +from django.utils.translation import ugettext as _ +from django.conf import settings + +from taiga.base.api import viewsets +from taiga.base import response +from taiga.base import exceptions as exc +from taiga.base.decorators import list_route +from taiga.users.models import AuthData, User +from taiga.users.services import get_user_photo_url +from taiga.users.gravatar import get_user_gravatar_id + +from .importer import TrelloImporter +from taiga.importers import permissions +from taiga.importers.services import resolve_users_bindings +from . import tasks + + +class TrelloImporterViewSet(viewsets.ViewSet): + permission_classes = (permissions.ImporterPermission,) + + @list_route(methods=["POST"]) + def list_users(self, request, *args, **kwargs): + self.check_permissions(request, "list_users", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + importer = TrelloImporter(request.user, token) + users = importer.list_users(project_id) + for user in users: + user['user'] = None + if not user['email']: + continue + + try: + taiga_user = User.objects.get(email=user['email']) + except User.DoesNotExist: + continue + + user['user'] = { + 'id': taiga_user.id, + 'full_name': taiga_user.get_full_name(), + 'gravatar_id': get_user_gravatar_id(taiga_user), + 'photo': get_user_photo_url(taiga_user), + } + return response.Ok(users) + + @list_route(methods=["POST"]) + def list_projects(self, request, *args, **kwargs): + self.check_permissions(request, "list_projects", None) + token = request.DATA.get('token', None) + importer = TrelloImporter(request.user, token) + projects = importer.list_projects() + return response.Ok(projects) + + @list_route(methods=["POST"]) + def import_project(self, request, *args, **kwargs): + self.check_permissions(request, "import_project", None) + + token = request.DATA.get('token', None) + project_id = request.DATA.get('project', None) + if not project_id: + raise exc.WrongArguments(_("The project param is needed")) + + options = { + "name": request.DATA.get('name', None), + "description": request.DATA.get('description', None), + "template": request.DATA.get('template', "kanban"), + "users_bindings": resolve_users_bindings(request.DATA.get("users_bindings", {})), + "keep_external_reference": request.DATA.get("keep_external_reference", False), + "is_private": request.DATA.get("is_private", False), + } + + if settings.CELERY_ENABLED: + task = tasks.import_project.delay(request.user.id, token, project_id, options) + return response.Accepted({"task_id": task.id}) + + importer = TrelloImporter(request.user, token) + project = importer.import_project(project_id, options) + project_data = { + "slug": project.slug, + "my_permissions": ["view_us"], + "is_backlog_activated": project.is_backlog_activated, + "is_kanban_activated": project.is_kanban_activated, + } + + return response.Ok(project_data) + + @list_route(methods=["GET"]) + def auth_url(self, request, *args, **kwargs): + self.check_permissions(request, "auth_url", None) + + (oauth_token, oauth_secret, url) = TrelloImporter.get_auth_url() + + (auth_data, created) = AuthData.objects.get_or_create( + user=request.user, + key="trello-oauth", + defaults={ + "value": uuid.uuid4().hex, + "extra": {}, + } + ) + auth_data.extra = { + "oauth_token": oauth_token, + "oauth_secret": oauth_secret, + } + auth_data.save() + + return response.Ok({"url": url}) + + @list_route(methods=["POST"]) + def authorize(self, request, *args, **kwargs): + self.check_permissions(request, "authorize", None) + + try: + oauth_data = request.user.auth_data.get(key="trello-oauth") + oauth_token = oauth_data.extra['oauth_token'] + oauth_secret = oauth_data.extra['oauth_secret'] + oauth_verifier = request.DATA.get('code') + oauth_data.delete() + trello_token = TrelloImporter.get_access_token(oauth_token, oauth_secret, oauth_verifier)['oauth_token'] + except Exception as e: + raise exc.WrongArguments(_("Invalid or expired auth token")) + + return response.Ok({ + "token": trello_token + }) diff --git a/taiga/importers/trello/importer.py b/taiga/importers/trello/importer.py new file mode 100644 index 00000000..e84515ea --- /dev/null +++ b/taiga/importers/trello/importer.py @@ -0,0 +1,538 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.translation import ugettext as _ + +from requests_oauthlib import OAuth1Session, OAuth1 +from django.conf import settings +from django.core.files.base import ContentFile +from django.contrib.contenttypes.models import ContentType +import requests +import webcolors + +from django.template.defaultfilters import slugify +from taiga.base import exceptions as exc +from taiga.projects.services import projects as projects_service +from taiga.projects.models import Project, ProjectTemplate +from taiga.projects.userstories.models import UserStory +from taiga.projects.tasks.models import Task +from taiga.projects.attachments.models import Attachment +from taiga.projects.history.services import (make_diff_from_dicts, + make_diff_values, + make_key_from_model_object, + get_typename_for_model_class, + FrozenDiff) +from taiga.projects.history.models import HistoryEntry +from taiga.projects.history.choices import HistoryType +from taiga.projects.custom_attributes.models import UserStoryCustomAttribute +from taiga.mdrender.service import render as mdrender +from taiga.timeline.rebuilder import rebuild_timeline +from taiga.timeline.models import Timeline +from taiga.front.templatetags.functions import resolve as resolve_front_url +from taiga.importers import services as import_service + +from taiga.base import exceptions + + +class TrelloClient: + def __init__(self, api_key, api_secret, token): + self.api_key = api_key + self.api_secret = api_secret + self.token = token + if self.token: + self.oauth = OAuth1( + client_key=self.api_key, + client_secret=self.api_secret, + resource_owner_key=self.token + ) + else: + self.oauth = None + + def get(self, uri_path, query_params=None): + headers = {'Accept': 'application/json'} + if query_params is None: + query_params = {} + + if uri_path[0] == '/': + uri_path = uri_path[1:] + url = 'https://api.trello.com/1/%s' % uri_path + + response = requests.get(url, params=query_params, headers=headers, auth=self.oauth) + + if response.status_code == 400: + raise exc.WrongArguments(_("Invalid Request: %s at %s") % (response.text, url)) + if response.status_code == 401: + raise exc.AuthenticationFailed(_("Unauthorized: %s at %s") % (response.text, url)) + if response.status_code == 403: + raise exc.PermissionDenied(_("Unauthorized: %s at %s") % (response.text, url)) + if response.status_code == 404: + raise exc.NotFound(_("Resource Unavailable: %s at %s") % (response.text, url)) + if response.status_code != 200: + raise exc.WrongArguments(_("Resource Unavailable: %s at %s") % (response.text, url)) + + return response.json() + + +class TrelloImporter: + def __init__(self, user, token): + self._user = user + self._cached_orgs = {} + self._client = TrelloClient( + api_key=settings.IMPORTERS.get('trello', {}).get('api_key', None), + api_secret=settings.IMPORTERS.get('trello', {}).get('secret_key', None), + token=token, + ) + + def list_projects(self): + projects_data = self._client.get("/members/me/boards", { + "fields": "id,name,desc,prefs,idOrganization", + "organization": "true", + "organization_fields": "prefs", + }) + projects = [] + for project in projects_data: + is_private = False + if project['prefs']['permissionLevel'] == "private": + is_private = True + + if project['prefs']['permissionLevel'] == "org": + if 'organization' not in project: + is_private = True + elif project['organization']['prefs']['permissionLevel'] == "private": + is_private = True + + projects.append({ + "id": project['id'], + "name": project['name'], + "description": project['desc'], + "is_private": is_private, + }) + return projects + + def list_users(self, project_id): + members = [] + for member in self._client.get("/board/{}/members/all".format(project_id), {"fields": "id"}): + user = self._client.get("/member/{}".format(member['id']), {"fields": "id,fullName,email,avatarSource,avatarHash,gravatarHash"}) + print(user) + if user['avatarSource'] == "gravatar": + avatar = 'https://www.gravatar.com/avatar/' + user['gravatarHash'] + '.jpg?s=50' + else: + avatar = 'https://trello-avatars.s3.amazonaws.com/' + user['avatarHash'] + '/50.png' + + members.append({ + "id": user['id'], + "full_name": user['fullName'], + "email": user['email'], + "avatar": avatar + }) + return members + + def import_project(self, project_id, options): + data = self._client.get( + "/board/{}".format(project_id), + { + "fields": "name,desc", + "cards": "all", + "card_fields": "closed,labels,idList,desc,due,name,pos,dateLastActivity,idChecklists,idMembers,url", + "card_attachments": "true", + "labels": "all", + "labels_limit": "1000", + "lists": "all", + "list_fields": "closed,name,pos", + "members": "none", + "checklists": "all", + "checklist_fields": "name", + "organization": "true", + "organization_fields": "logoHash", + } + ) + + project = self._import_project_data(data, options) + self._import_user_stories_data(data, project, options) + self._cleanup(project, options) + Timeline.objects.filter(project=project).delete() + rebuild_timeline(None, None, project.id) + return project + + def _import_project_data(self, data, options): + board = data + labels = board['labels'] + statuses = board['lists'] + project_template = ProjectTemplate.objects.get(slug=options.get('template', "kanban")) + project_template.us_statuses = [] + counter = 0 + for us_status in statuses: + if counter == 0: + project_template.default_options["us_status"] = us_status['name'] + + counter += 1 + if us_status['name'] not in [s['name'] for s in project_template.us_statuses]: + project_template.us_statuses.append({ + "name": us_status['name'], + "slug": slugify(us_status['name']), + "is_closed": False, + "is_archived": True if us_status['closed'] else False, + "color": "#999999", + "wip_limit": None, + "order": us_status['pos'], + }) + + project_template.task_statuses = [] + project_template.task_statuses.append({ + "name": "Incomplete", + "slug": "incomplete", + "is_closed": False, + "color": "#ff8a84", + "order": 1, + }) + project_template.task_statuses.append({ + "name": "Complete", + "slug": "complete", + "is_closed": True, + "color": "#669900", + "order": 2, + }) + project_template.default_options["task_status"] = "Incomplete" + project_template.roles.append({ + "name": "Trello", + "slug": "trello", + "computable": False, + "permissions": project_template.roles[0]['permissions'], + "order": 70, + }) + + tags_colors = [] + for label in labels: + name = label['name'] + if not name: + name = label['color'] + name = name.lower() + color = self._ensure_hex_color(label['color']) + tags_colors.append([name, color]) + + project = Project( + name=options.get('name', None) or board['name'], + description=options.get('description', None) or board['desc'], + owner=self._user, + tags_colors=tags_colors, + creation_template=project_template, + is_private=options.get('is_private', False), + ) + (can_create, error_message) = projects_service.check_if_project_can_be_created_or_updated(project) + if not can_create: + raise exceptions.NotEnoughSlotsForProject(project.is_private, 1, error_message) + project.save() + + if board.get('organization', None): + trello_avatar_template = "https://trello-logos.s3.amazonaws.com/{}/170.png" + project_logo_url = trello_avatar_template.format(board['organization']['logoHash']) + data = requests.get(project_logo_url) + project.logo.save("logo.png", ContentFile(data.content), save=True) + + UserStoryCustomAttribute.objects.create( + name="Due", + description="Due date", + type="date", + order=1, + project=project + ) + import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "trello") + return project + + def _import_user_stories_data(self, data, project, options): + users_bindings = options.get('users_bindings', {}) + statuses = {s['id']: s for s in data['lists']} + cards = data['cards'] + due_date_field = project.userstorycustomattributes.first() + + for card in cards: + if card['closed'] and not options.get("import_closed_data", False): + continue + if statuses[card['idList']]['closed'] and not options.get("import_closed_data", False): + continue + + tags = [] + for tag in card['labels']: + name = tag['name'] + if not name: + name = tag['color'] + name = name.lower() + tags.append(name) + + assigned_to = None + if len(card['idMembers']) > 0: + assigned_to = users_bindings.get(card['idMembers'][0], None) + + external_reference = None + if options.get('keep_external_reference', False): + external_reference = ["trello", card['url']] + + us = UserStory.objects.create( + project=project, + owner=self._user, + assigned_to=assigned_to, + status=project.us_statuses.get(name=statuses[card['idList']]['name']), + kanban_order=card['pos'], + sprint_order=card['pos'], + backlog_order=card['pos'], + subject=card['name'], + description=card['desc'], + tags=tags, + external_reference=external_reference + ) + + if len(card['idMembers']) > 1: + for watcher in card['idMembers'][1:]: + watcher_user = users_bindings.get(watcher, None) + if watcher_user: + us.add_watcher(watcher_user) + + if card['due']: + us.custom_attributes_values.attributes_values = {due_date_field.id: card['due']} + us.custom_attributes_values.save() + + UserStory.objects.filter(id=us.id).update( + modified_date=card['dateLastActivity'], + created_date=card['dateLastActivity'] + ) + self._import_attachments(us, card, options) + self._import_tasks(data, us, card) + self._import_actions(us, card, statuses, options) + + def _import_tasks(self, data, us, card): + checklists_by_id = {c['id']: c for c in data['checklists']} + for checklist_id in card['idChecklists']: + for item in checklists_by_id.get(checklist_id, {}).get('checkItems', []): + Task.objects.create( + subject=item['name'], + status=us.project.task_statuses.get(slug=item['state']), + project=us.project, + user_story=us + ) + + def _import_attachments(self, us, card, options): + users_bindings = options.get('users_bindings', {}) + for attachment in card['attachments']: + if attachment['bytes'] is None: + continue + data = requests.get(attachment['url']) + att = Attachment( + owner=users_bindings.get(attachment['idMember'], self._user), + project=us.project, + content_type=ContentType.objects.get_for_model(UserStory), + object_id=us.id, + name=attachment['name'], + size=attachment['bytes'], + created_date=attachment['date'], + is_deprecated=False, + ) + att.attached_file.save(attachment['name'], ContentFile(data.content), save=True) + + UserStory.objects.filter(id=us.id, created_date__gt=attachment['date']).update( + created_date=attachment['date'] + ) + + def _import_actions(self, us, card, statuses, options): + included_actions = [ + "addAttachmentToCard", "addMemberToCard", "commentCard", + "convertToCardFromCheckItem", "copyCommentCard", "createCard", + "deleteAttachmentFromCard", "deleteCard", "removeMemberFromCard", + "updateCard", + ] + + actions = self._client.get( + "/card/{}/actions".format(card['id']), + { + "filter": ",".join(included_actions), + "limit": "1000", + "memberCreator": "true", + "memberCreator_fields": "fullName", + } + ) + + while actions: + for action in actions: + self._import_action(us, action, statuses, options) + actions = self._client.get( + "/card/{}/actions".format(card['id']), + { + "filter": ",".join(included_actions), + "limit": "1000", + "since": "lastView", + "before": action['date'], + "memberCreator": "true", + "memberCreator_fields": "fullName", + } + ) + + def _import_action(self, us, action, statuses, options): + key = make_key_from_model_object(us) + typename = get_typename_for_model_class(UserStory) + action_data = self._transform_action_data(us, action, statuses, options) + if action_data is None: + return + + change_old = action_data['change_old'] + change_new = action_data['change_new'] + hist_type = action_data['hist_type'] + comment = action_data['comment'] + user = action_data['user'] + + diff = make_diff_from_dicts(change_old, change_new) + fdiff = FrozenDiff(key, diff, {}) + + entry = HistoryEntry.objects.create( + user=user, + project_id=us.project.id, + key=key, + type=hist_type, + snapshot=None, + diff=fdiff.diff, + values=make_diff_values(typename, fdiff), + comment=comment, + comment_html=mdrender(us.project, comment), + is_hidden=False, + is_snapshot=False, + ) + HistoryEntry.objects.filter(id=entry.id).update(created_at=action['date']) + return HistoryEntry.objects.get(id=entry.id) + + def _transform_action_data(self, us, action, statuses, options): + users_bindings = options.get('users_bindings', {}) + due_date_field = us.project.userstorycustomattributes.first() + + ignored_actions = ["addAttachmentToCard", "addMemberToCard", + "deleteAttachmentFromCard", "deleteCard", + "removeMemberFromCard"] + + if action['type'] in ignored_actions: + return None + + user = {"pk": None, "name": action.get('memberCreator', {}).get('fullName', None)} + taiga_user = users_bindings.get(action.get('memberCreator', {}).get('id', None), None) + if taiga_user: + user = {"pk": taiga_user.id, "name": taiga_user.get_full_name()} + + result = { + "change_old": {}, + "change_new": {}, + "hist_type": HistoryType.change, + "comment": "", + "user": user + } + + if action['type'] == "commentCard": + result['comment'] = str(action['data']['text']) + elif action['type'] == "convertToCardFromCheckItem": + UserStory.objects.filter(id=us.id, created_date__gt=action['date']).update( + created_date=action['date'], + owner=users_bindings.get(action["idMemberCreator"], self._user) + ) + result['hist_type'] = HistoryType.create + elif action['type'] == "copyCommentCard": + UserStory.objects.filter(id=us.id, created_date__gt=action['date']).update( + created_date=action['date'], + owner=users_bindings.get(action["idMemberCreator"], self._user) + ) + result['hist_type'] = HistoryType.create + elif action['type'] == "createCard": + UserStory.objects.filter(id=us.id, created_date__gt=action['date']).update( + created_date=action['date'], + owner=users_bindings.get(action["idMemberCreator"], self._user) + ) + result['hist_type'] = HistoryType.create + elif action['type'] == "updateCard": + if 'desc' in action['data']['old']: + result['change_old']["description"] = str(action['data']['old'].get('desc', '')) + result['change_new']["description"] = str(action['data']['card'].get('desc', '')) + result['change_old']["description_html"] = mdrender(us.project, str(action['data']['old'].get('desc', ''))) + result['change_new']["description_html"] = mdrender(us.project, str(action['data']['card'].get('desc', ''))) + if 'idList' in action['data']['old']: + old_status_name = statuses[action['data']['old']['idList']]['name'] + result['change_old']["status"] = us.project.us_statuses.get(name=old_status_name).id + new_status_name = statuses[action['data']['card']['idList']]['name'] + result['change_new']["status"] = us.project.us_statuses.get(name=new_status_name).id + if 'name' in action['data']['old']: + result['change_old']["subject"] = action['data']['old']['name'] + result['change_new']["subject"] = action['data']['card']['name'] + if 'due' in action['data']['old']: + result['change_old']["custom_attributes"] = [{ + "name": "Due", + "value": action['data']['old']['due'], + "id": due_date_field.id + }] + result['change_new']["custom_attributes"] = [{ + "name": "Due", + "value": action['data']['card']['due'], + "id": due_date_field.id + }] + + if result['change_old'] == {}: + return None + return result + + @classmethod + def get_auth_url(cls): + request_token_url = 'https://trello.com/1/OAuthGetRequestToken' + authorize_url = 'https://trello.com/1/OAuthAuthorizeToken' + return_url = resolve_front_url("new-project-import", "trello") + expiration = "1day" + scope = "read,write,account" + trello_key = settings.IMPORTERS.get('trello', {}).get('api_key', None) + trello_secret = settings.IMPORTERS.get('trello', {}).get('secret_key', None) + name = "Taiga" + + session = OAuth1Session(client_key=trello_key, client_secret=trello_secret) + response = session.fetch_request_token(request_token_url) + oauth_token, oauth_token_secret = response.get('oauth_token'), response.get('oauth_token_secret') + + return ( + oauth_token, + oauth_token_secret, + "{authorize_url}?oauth_token={oauth_token}&scope={scope}&expiration={expiration}&name={name}&return_url={return_url}".format( + authorize_url=authorize_url, + oauth_token=oauth_token, + expiration=expiration, + scope=scope, + name=name, + return_url=return_url, + ) + ) + + @classmethod + def get_access_token(cls, oauth_token, oauth_token_secret, oauth_verifier): + api_key = settings.IMPORTERS.get('trello', {}).get('api_key', None) + api_secret = settings.IMPORTERS.get('trello', {}).get('secret_key', None) + access_token_url = 'https://trello.com/1/OAuthGetAccessToken' + session = OAuth1Session(client_key=api_key, client_secret=api_secret, + resource_owner_key=oauth_token, resource_owner_secret=oauth_token_secret, + verifier=oauth_verifier) + access_token = session.fetch_access_token(access_token_url) + return access_token + + def _ensure_hex_color(self, color): + if color is None: + return None + try: + return webcolors.name_to_hex(color) + except ValueError: + return color + + def _cleanup(self, project, options): + if not options.get("import_closed_data", False): + project.us_statuses.filter(is_archived=True).delete() diff --git a/taiga/importers/trello/tasks.py b/taiga/importers/trello/tasks.py new file mode 100644 index 00000000..770aecab --- /dev/null +++ b/taiga/importers/trello/tasks.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import logging +import sys + +from django.utils.translation import ugettext as _ + +from taiga.base.mails import mail_builder +from taiga.users.models import User +from taiga.celery import app +from .importer import TrelloImporter + +logger = logging.getLogger('taiga.importers.trello') + + +@app.task(bind=True) +def import_project(self, user_id, token, project_id, options): + user = User.objects.get(id=user_id) + importer = TrelloImporter(user, token) + try: + project = importer.import_project(project_id, options) + except Exception as e: + # Error + ctx = { + "user": user, + "error_subject": _("Error importing Trello project"), + "error_message": _("Error importing Trello project"), + "project": project_id, + "exception": e + } + email = mail_builder.importer_import_error(user, ctx) + email.send() + logger.error('Error importing Trello project %s (by %s)', project_id, user, exc_info=sys.exc_info()) + else: + ctx = { + "project": project, + "user": user, + } + email = mail_builder.trello_import_success(user, ctx) + email.send() diff --git a/taiga/locale/api.py b/taiga/locale/api.py index d9add761..48b4d31a 100644 --- a/taiga/locale/api.py +++ b/taiga/locale/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/locale/ca/LC_MESSAGES/django.po b/taiga/locale/ca/LC_MESSAGES/django.po index e8e00410..df4a1ab4 100644 --- a/taiga/locale/ca/LC_MESSAGES/django.po +++ b/taiga/locale/ca/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Catalan (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/ca/)\n" @@ -20,15 +20,15 @@ msgstr "" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "El registre públic està deshabilitat" -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "Sistema de registre invàlid" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "Sistema de login invàlid" @@ -48,17 +48,17 @@ msgstr "El token no s'ajusta a cap invitació vàlida" msgid "User is already registered." msgstr "Aquest usuari ja està registrat" -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Error creant un nou usuari." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Token invàlid" @@ -79,95 +79,95 @@ msgstr "Aquest camp es obligatori" msgid "Invalid value." msgstr "Valor invàlid" -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' valor deu ser Verdader o Fals" -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "Introdueix un 'slug' vàlid: lletres, nombres, barra baixa o guió." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Selecciona una opció vàlida. %(value)s no es una opció vàlida." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Introdueix una adreça de correu vàlida-" -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "La data te un format erroni. Utilitza un del següents formats: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "La data te un format erroni. Utilitza un del següents formats: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "L'hora te un format erroni. Utilitza un del següents formats: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Introdueix un nombre complet." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Asegurat que aquest valor es inferior i igual a %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Asegurat que aquest valor es superior o igual a %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" deu ser un float." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Introdueix un nombre." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Asegurat que no hi ha més de %s digits en total." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Asegurat que no hi ha més de %s posicions decimals." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Asegurat que no hi ha més de %s dígits abans del decimal." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Cap fitxer enviat. Comprova el tipus de codificació en el formulari." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Cap fitxer enviat." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "El fitxer enviat està buit." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -175,11 +175,11 @@ msgstr "" "Asegurat que el nom del fitxer te un màxim de %(max)d caràcters (te " "%(length)d)" -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Per favor envia un fitxer o cancela el checkbox, pero no ambdós." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -188,25 +188,25 @@ msgstr "" "està corrupte." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "La página no es 'last' ni pot ser convertida a un 'int'" -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Pàgina invàlida (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "" @@ -270,7 +270,7 @@ msgstr "" msgid "Permission denied" msgstr "" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "" @@ -349,11 +349,11 @@ msgstr "Precondició errònia." msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "" -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "" @@ -362,43 +362,43 @@ msgstr "" msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Segueix-nos a Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Aconsegueix el codi a Github" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Visita la nostra pàgina web" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -658,6 +658,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -673,6 +674,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -691,6 +693,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -744,7 +747,7 @@ msgid "It contain invalid custom fields." msgstr "Conté camps personalitzats invàlids." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "" @@ -755,13 +758,13 @@ msgstr "" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "Nom" @@ -773,12 +776,12 @@ msgstr "" msgid "web" msgstr "" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "Descripció" @@ -787,36 +790,34 @@ msgstr "Descripció" msgid "Next url" msgstr "" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "Nom complet" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "Adreça d'email" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "Comentari" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -888,9 +889,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "El payload no és un arxiu json vàlid" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "El projecte no existeix" @@ -978,6 +979,221 @@ msgstr "L'element referenciat no existeix" msgid "The status doesn't exist" msgstr "L'estatus no existeix." +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Veure projecte" @@ -1146,89 +1362,89 @@ msgstr "Administrar rols" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "Amo" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Arguments incomplets." -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Format d'image invàlid" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "No tens permisos per a veure açò." @@ -1244,18 +1460,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "Projecte" @@ -1269,9 +1485,9 @@ msgstr "Id d'objecte" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1289,14 +1505,18 @@ msgstr "" msgid "is deprecated" msgstr "està obsolet " -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "Ordre" @@ -1332,19 +1552,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 -msgid "Text" +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" msgstr "" #: taiga/projects/custom_attributes/choices.py:29 -msgid "Multi-Line Text" +msgid "Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:30 -msgid "Date" +msgid "Multi-Line Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1378,54 +1650,59 @@ msgstr "incidéncia" msgid "Already exists one with the same name." msgstr "Ja existix altre amb el matex nom." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "estatus" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "tema" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "color" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "assignada a" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "requeriment de client" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "requeriment d'equip" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "referència externa" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1458,102 +1735,102 @@ msgstr "Crea" msgid "Delete" msgstr "Borra" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s punts de rol" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "De" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "a" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Afegir nou arxiu" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Arxiu actualitzat" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "Obsolet" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "No obsolet" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Arxiu borrat" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "Afegit" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "Borrat" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Sense assignar" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-borrat-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "a:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "desde:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Afegit" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Canviat" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Borrat" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "afegit:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "borrat:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Desde:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "A:" @@ -1571,23 +1848,23 @@ msgstr "nota de bloqueig" msgid "sprint" msgstr "" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "No tens permissos per a ficar aquest sprint a aquesta incidència" -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "No tens permissos per a ficar aquest status a aquesta tasca" -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "No tens permissos per a ficar aquesta severitat a aquesta tasca" -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "No tens permissos per a ficar aquesta prioritat a aquesta incidència" -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "No tens permissos per a ficar aquest tipus a aquesta incidència" @@ -1608,11 +1885,6 @@ msgstr "fita" msgid "finished date" msgstr "Data de finalització" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "referència externa" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "M'agrada" @@ -1621,33 +1893,33 @@ msgstr "M'agrada" msgid "Likes" msgstr "Fans" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "Data estimada d'inici" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "Data estimada de finalització" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "està tancat" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibilitat" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "" @@ -1659,6 +1931,14 @@ msgstr "" msgid "is blocked" msgstr "està bloquejat" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1668,236 +1948,260 @@ msgstr "" msgid "'project' parameter is mandatory" msgstr "" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "email" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "text extra d'invitació" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "L'usuari ja es membre del projecte" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "estatus d'història d'usuai per defecte" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "Points per defecte" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "Estatus de tasca per defecte" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "Prioritat per defecte" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "Severitat per defecte" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "Status d'incidència per defecte" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "Tipus d'incidència per defecte" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "membres" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "total de fites" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "total de punts d'història" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "activa panell de backlog" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "activa panell de kanban" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "activa panell de wiki" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "activa panell d'incidències" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "sistema de videoconferència" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "template de creació" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "es privat" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "permisos d'anònims" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "permisos d'usuaris" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "Actualitzada data" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "configuració de mòdules" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "està arxivat" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "limit de treball en progrés" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "valor" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "rol d'amo per defecte" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "opcions per defecte" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "status d'històries d'usuari" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "punts" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "status de tasques" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "status d'incidències" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "tipus d'incidències" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "prioritats" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "severitats" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "rols" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "" @@ -1932,7 +2236,7 @@ msgstr "" msgid "Notify exists for specified user and project" msgstr "" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "" @@ -2531,36 +2835,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2575,8 +2883,8 @@ msgstr "" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Token invàlid" @@ -2626,15 +2934,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "" @@ -2650,31 +2958,31 @@ msgstr "ordre de taskboard" msgid "is iocaine" msgstr "es iocaina" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3268,37 +3576,29 @@ msgstr "" msgid "Stakeholder" msgstr "" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rol" @@ -3327,87 +3627,91 @@ msgstr "generat desde incidéncia" msgid "There's no user story with that id" msgstr "No hi ha cap història d'usuari amb eixe id" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "No hi ha cap projecte amb eixe id" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Aquest e-mail ja està en ús" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Rol invàlid per al projecte" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Opcions per defecte" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Estatus d'històries d'usuari" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Punts" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Estatus de tasques" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Estatus d'incidéncies" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Tipus d'incidéncies" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioritats" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Severitats" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Rols" @@ -3436,7 +3740,7 @@ msgstr "últim a modificar" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "" @@ -3476,53 +3780,53 @@ msgstr "" msgid "Important dates" msgstr "Dates importants" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Email duplicat" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Email no vàlid" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Nom d'usuari o email invàlid" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Correu enviat satisfactòriament" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Paràmetre de password actual requerit" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Paràmetre de password requerit" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Password invàlid, al menys 6 caràcters requerits" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Password actual invàlid" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Invàlid. Estás segur que el token es correcte i que no l'has usat abans?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Invàlid. Estás segur que el token es correcte?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "estatus de superusuari" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -3530,24 +3834,24 @@ msgstr "" "Designa que aquest usuari te tots els permisos sense asignarli-los " "explícitament." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "mot d'usuari" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Requerit. 30 caràcters o menys. Lletres, nombres i caràcters /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Introdueix un nom d'usuari vàlid" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "actiu" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3555,59 +3859,59 @@ msgstr "" "Designa si aquest usuari ha de se tractac com actiu. Deselecciona açó en " "lloc de borrar el compte." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografia" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "foto" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "data d'unió" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "llenguatge per defecte" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "zona horaria per defecte" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "coloritza tags" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "token de correu" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "nova adreça de correu" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "permissos" diff --git a/taiga/locale/de/LC_MESSAGES/django.po b/taiga/locale/de/LC_MESSAGES/django.po index dd4e9ac4..b3565d1d 100644 --- a/taiga/locale/de/LC_MESSAGES/django.po +++ b/taiga/locale/de/LC_MESSAGES/django.po @@ -1,8 +1,9 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: +# Carsten Tornow , 2016 # Chris , 2015 # M S, 2015 # Guido Brand, 2015 @@ -20,8 +21,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: German (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/de/)\n" @@ -31,15 +32,15 @@ msgstr "" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Die Registrierung ist für die Öffentlichkeit gesperrrt." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "Ungültige Registrierungsart" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "Ungültige Loginart" @@ -59,17 +60,17 @@ msgstr "Das Token kann keiner gültigen Einladung zugeordnet werden." msgid "User is already registered." msgstr "Der Benutzer ist schon registriert." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Dieser Benutzer ist schon ein Mitglied des Projektes." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Fehler bei der Erstellung des neuen Benutzers." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Ungültiges Token" @@ -92,113 +93,113 @@ msgstr "Das ist ein Pflichtfeld." msgid "Invalid value." msgstr "Ungültiger Wert." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "Der Wert für '%s' muss entweder True/Wahr oder False/Falsch sein." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Geben Sie einen gültigen 'slug' ein, bestehend aus Buchstaben, Zahlen, " "Unterstrichen oder Bindestrichen." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Bitte machen Sie eine gültige Auswahl. %(value)s ist nicht verfügbar." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" -msgstr "" +msgstr "Ihre E-Mail Adresse ist nicht erlaubt." -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Geben Sie bitte eine gültige E-Mail Adresse an." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "" "Das Datum hat das falsche Format. Bitte verwenden Sie eines der folgenden " "Formate: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "" "Der Datentyp 'Datetime' hat ein falsches Format. Bitte verwenden Sie eines " "der folgenden Formate: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "" "Die Zeit hat ein falsches Format. Bitte verwenden Sie eines der folgenden " "Formate: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Geben Sie bitte eine ganze Zahl ein." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" "Stellen Sie sicher, dass dieser Wert niedriger oder gleich ist wie " "%(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" "Stellen Sie sicher, dass dieser Wert höher oder gleich ist wie " "%(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "Der Wert für '%s' muss eine Fließkommazahl sein." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Bitte geben Sie eine Zahl ein." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "" "Bitte stellen Sie sicher, dass nicht mehr als %s insgesamt vorhanden sind. " -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "" "Bitte stellen Sie sicher, dass nicht mehr als %s Dezimalstellen vorhanden " "sind." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "" "Stellen Sie sicher, dass nicht mehr als %s Ziffern vor dem Dezimalpunkt " "vorhanden sind." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Es wurde keine Datei übergeben. Prüfen Sie die Kodierung der HTML-Form." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Es wurde keine Datei eingereicht." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Die eingereichte Datei ist leer." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -206,13 +207,13 @@ msgstr "" "Stellen Sie sicher, dass dieser Dateiname höchstens %(max)d Zeichen hat (er " "hat %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Bitte senden Sie entweder eine Datei oder markieren Sie \"Löschen\", nicht " "beides." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -221,25 +222,25 @@ msgstr "" "haben, ist entweder kein Bild oder defekt." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Blockiertes Element" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Seite ist nicht 'letzte', noch kann diese konvertiert werden." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Ungültige Seite (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Ungültige Berechtigungsdefinition" @@ -305,7 +306,7 @@ msgstr "Nicht gefunden." msgid "Permission denied" msgstr "Zugriff verweigert" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Fehler bei der Serveranmeldung" @@ -384,11 +385,11 @@ msgstr "Voraussetzungsfehler" msgid "No room left for more projects." msgstr "Kein Raum für weitere Projekte." -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Fehler in Filter Parameter Typen." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' muss ein Integer-Wert sein." @@ -397,43 +398,43 @@ msgstr "'project' muss ein Integer-Wert sein." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Folgen Sie uns auf Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Holen Sie sich den Source-Code auf GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Besuchen Sie unsere Webseite" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -562,7 +563,7 @@ msgstr "Fehler beim Importieren der User-Stories" #: taiga/export_import/services/store.py:790 msgid "error importing epics" -msgstr "" +msgstr "Fehler beim Importieren der Epics" #: taiga/export_import/services/store.py:794 msgid "error importing tasks" @@ -762,6 +763,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -788,6 +790,7 @@ msgstr "" " " #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -820,6 +823,7 @@ msgstr "" "Das Taiga Team\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -894,7 +898,7 @@ msgid "It contain invalid custom fields." msgstr "Enthält ungültige Benutzerfelder." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Der Name für das Projekt ist doppelt vergeben" @@ -905,13 +909,13 @@ msgstr "Authentifizierung erforderlich" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "Name" @@ -923,12 +927,12 @@ msgstr "Icon URL" msgid "web" msgstr "Web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "Beschreibung" @@ -937,36 +941,34 @@ msgstr "Beschreibung" msgid "Next url" msgstr "Nächste URL" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "Geheimer Schlüssel für Verschlüsselung der Anwensungs-Token" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "Benutzer" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "Applikation" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "vollständiger Name" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "E-Mail Adresse" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "Kommentar" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -1038,9 +1040,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Die Nutzlast ist kein gültiges json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Das Projekt existiert nicht" @@ -1128,6 +1130,221 @@ msgstr "Das referenzierte Element existiert nicht" msgid "The status doesn't exist" msgstr "Der Status existiert nicht" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Projekt ansehen" @@ -1138,7 +1355,7 @@ msgstr "Meilensteine ansehen" #: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 msgid "View epic" -msgstr "" +msgstr "Epic ansehen." #: taiga/permissions/choices.py:26 msgid "View user stories" @@ -1174,19 +1391,19 @@ msgstr "Meilenstein löschen" #: taiga/permissions/choices.py:42 msgid "Add epic" -msgstr "" +msgstr "Epic hinzufügen." #: taiga/permissions/choices.py:43 msgid "Modify epic" -msgstr "" +msgstr "Epic ändern." #: taiga/permissions/choices.py:44 msgid "Comment epic" -msgstr "" +msgstr "Epic kommentieren." #: taiga/permissions/choices.py:45 msgid "Delete epic" -msgstr "" +msgstr "Epic löschen." #: taiga/permissions/choices.py:47 msgid "View user story" @@ -1202,7 +1419,7 @@ msgstr "User-Story ändern" #: taiga/permissions/choices.py:50 msgid "Comment user story" -msgstr "" +msgstr "User-Story komentieren." #: taiga/permissions/choices.py:51 msgid "Delete user story" @@ -1218,7 +1435,7 @@ msgstr "Aufgabe ändern" #: taiga/permissions/choices.py:56 msgid "Comment task" -msgstr "" +msgstr "Aufgabe kommentieren." #: taiga/permissions/choices.py:57 msgid "Delete task" @@ -1234,7 +1451,7 @@ msgstr "Ticket ändern" #: taiga/permissions/choices.py:62 msgid "Comment issue" -msgstr "" +msgstr "Ticket kommentieren." #: taiga/permissions/choices.py:63 msgid "Delete issue" @@ -1250,7 +1467,7 @@ msgstr "Wiki Seite ändern" #: taiga/permissions/choices.py:68 msgid "Comment wiki page" -msgstr "" +msgstr "Wiki Seite kommentieren." #: taiga/permissions/choices.py:69 msgid "Delete wiki page" @@ -1296,83 +1513,83 @@ msgstr "Administrator-Rollen" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "Besitzer" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Unvollständige Argumente" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Ungültiges Bildformat" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Unglültiger Templatename" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Ungültige Templatebeschreibung" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "Ungültige Benutzer-Id" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "Der Benutzer existiert nicht" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "Der Benutzer muss bereits Mitglied des Projektes sein" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" @@ -1380,7 +1597,7 @@ msgstr "" "Das Projekt muss einen Eigentümer haben und mindestens ein Benutzer muss ein " "aktiver Administrator sein" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Sie haben keine Berechtigungen für diese Ansicht" @@ -1396,18 +1613,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Nr. unterschreidet sich zwischen dem Objekt und dem Projekt" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "Projekt" @@ -1421,9 +1638,9 @@ msgstr "Objekt Nr." #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1441,14 +1658,18 @@ msgstr "SHA1" msgid "is deprecated" msgstr "wurde verworfen" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "Reihenfolge" @@ -1484,19 +1705,71 @@ msgstr "Dieses Projekt ist blockiert, weil es der Eigentümer verlassen hat." msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Text" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Mehrzeiliger Text" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Datum" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1511,7 +1784,7 @@ msgstr "Werte" #: taiga/projects/custom_attributes/models.py:105 msgid "epic" -msgstr "" +msgstr "Epic" #: taiga/projects/custom_attributes/models.py:121 #: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 @@ -1530,53 +1803,58 @@ msgstr "Ticket" msgid "Already exists one with the same name." msgstr "Dieser Name wird schon verwendet." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "Status" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "Betreff" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "Farbe" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "zugewiesen an" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "ist Kundenanforderung" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "ist Teamanforderung" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" -msgstr "" +msgstr "User-Stories" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "externe Referenz" #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" @@ -1610,102 +1888,102 @@ msgstr "Erzeugen" msgid "Delete" msgstr "Löschen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s Rollenpunkte " -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "Von" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "An" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Neuen Anhang hinzugefügt" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Anhang aktualisiert" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "verworfen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "nicht verworfen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Gelöschter Anhang" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "hinzugefügt" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "entfernt" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Nicht zugewiesen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-gelöscht-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "An:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "Von:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Hinzugefügt" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Geändert" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Gelöscht" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "hinzugefügt:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "entfernt:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Von:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "An:" @@ -1723,27 +2001,27 @@ msgstr "Blockierungsgrund" msgid "sprint" msgstr "Sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "" "Sie haben nicht die Berechtigung, das Ticket auf diesen Sprint zu setzen." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "" "Sie haben nicht die Berechtigung, das Ticket auf diesen Status zu setzen. " -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "" "Sie haben nicht die Berechtigung, das Ticket auf diese Gewichtung zu setzen." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "" "Sie haben nicht die Berechtigung, das Ticket auf diese Priorität zu setzen. " -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Sie haben nicht die Berechtigung, das Ticket auf diese Art zu setzen." @@ -1764,11 +2042,6 @@ msgstr "Meilenstein" msgid "finished date" msgstr "Datum der Fertigstellung" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "externe Referenz" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Like" @@ -1777,33 +2050,33 @@ msgstr "Like" msgid "Likes" msgstr "Likes" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "Slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "geschätzter Starttermin" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "geschätzter Endtermin" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "ist geschlossen" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "Verfügbarkeit" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "Der erwartete Beginn muss vor dem erwarteten Ende liegen. " @@ -1815,6 +2088,14 @@ msgstr "" msgid "is blocked" msgstr "wird blockiert" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1824,236 +2105,260 @@ msgstr "'{param}' Parameter ist ein Pflichtfeld" msgid "'project' parameter is mandatory" msgstr "Der 'project' Parameter ist ein Pflichtfeld" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "E-Mail" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "erstellt am " -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "Token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "Einladung Zusatztext " -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "Benutzerreihenfolge" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Der Benutzer ist bereits Mitglied dieses Projekts" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "voreingesteller User-Story Status " -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "voreingestellte Punkte" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "voreingestellter Aufgabenstatus" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "voreingestellte Priorität " -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "voreingestellte Gewichtung " -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "voreingestellter Ticket Status" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "voreingestellter Ticket Typ" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "Logo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "Mitglieder" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "Meilensteine Gesamt" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "Story Punkte insgesamt" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "aktives Backlog Panel" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "aktives Kanban Panel" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "aktives Wiki Panel" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "aktives Tickets Panel" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "Videokonferenzsystem" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "Zusatzdaten Videokonferenz" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "Vorlage erstellen" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "ist privat" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "Rechte für anonyme Nutzer" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "Rechte für registrierte Nutzer" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "ist gekennzeichnet" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "sucht nach Mitarbeitern" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "Hinweis für Mitarbeitersuche" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "" -#: taiga/projects/models.py:218 +#: taiga/projects/models.py:222 msgid "project transfer token" msgstr "Projekt-Transfer-Token" -#: taiga/projects/models.py:222 +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "Blockierter Code" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "Aktualisierungsdatum" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "Count" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "Unterstützer letzte Woche" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "Unterstützer letzten Monat" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "Unterstützer letztes Jahr" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "Aktivitäten letzte Woche" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "Aktivitäten letzten Monat" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "Aktivitäten letztes Jahr" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "Module konfigurieren" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "ist archiviert" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "Ausführungslimit" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "Wert" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "voreingestellte Besitzerrolle" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "Vorgabe Optionen" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "User-Story Status " -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "Punkte" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "Aufgaben Status" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "Ticket Status" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "Ticket Arten" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "Prioritäten" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "Gewichtung" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "Rollen" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Beteiligt" @@ -2088,7 +2393,7 @@ msgstr "Beobachtet" msgid "Notify exists for specified user and project" msgstr "Benachrichtigung für bestimmte Benutzer und Projekt aktiviert" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Ungültiger Wert für Benachrichtigungslevel" @@ -2967,40 +3272,44 @@ msgstr "" "Sie können das Projekt nicht verlassen, wenn Sie der Eigentümer sind oder " "wenn keine weiteren Administratoren vorhanden sind." -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" "Sie haben Ihr aktuelles Limit für die Mitgliederanzahl für private Projekte " "erreicht" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" "Sie haben Ihr aktuelles Limit für die Mitgliederanzahl für öffentliche " "Projekte erreicht" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "Sie können nicht mehr private Projekte haben" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "Sie können nicht mehr öffentliche Projekte haben." -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -3015,8 +3324,8 @@ msgstr "Projektende" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Token ist ungültig" @@ -3066,18 +3375,18 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "" "Sie haben nicht die Berechtigung, diesen Sprint auf diese Aufgabe zu setzen" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" "Sie haben nicht die Berechtigung, diese User-Story auf diese Aufgabe zu " "setzen" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "" "Sie haben nicht die Berechtigung, diesen Status auf diese Aufgabe zu setzen." @@ -3094,31 +3403,31 @@ msgstr "Taskboard Befehl " msgid "is iocaine" msgstr "ist Iocaine" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3759,41 +4068,33 @@ msgstr "Projekteigentümer " msgid "Stakeholder" msgstr "Stakeholder" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Sie haben nicht die Berechtigung, diesen Sprint auf diese User-Story zu " "setzen." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "Sie haben nicht die Berechtigung, diesen Status auf diese User-Story zu " "setzen." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Erstelle die User-Story #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "Rolle" @@ -3822,88 +4123,92 @@ msgstr "erzeugt von Ticket" msgid "There's no user story with that id" msgstr "Es gibt keine User-Story mit dieser id" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Es gibt kein Projekt mit dieser id" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Die E-Mailadresse ist bereits vergeben" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Ungültige Rolle für dieses Projekt" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "Der Projekteigentümer muss Administrator sein." -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" "Mindestens ein Benutzer muss ein aktiver Administrator des Projektes sein." -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Voreingestellte Optionen" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Status für User-Stories" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Punkte" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Aufgaben Status" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Ticket Status" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Ticket Arten" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioritäten" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Gewichtung" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Rollen" @@ -3932,7 +4237,7 @@ msgstr "letzte Änderung" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Prüfe die API der Historie auf Übereinstimmung" @@ -3972,54 +4277,54 @@ msgstr "Einschränkungen" msgid "Important dates" msgstr "Wichtige Termine" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Doppelte E-Mail" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Ungültige E-Mail" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Ungültiger Benutzername oder E-Mail" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "E-Mail erfolgreich gesendet." -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Aktueller Passwort Parameter wird benötigt" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Neuer Passwort Parameter benötigt" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Ungültige Passwortlänge, mindestens 6 Zeichen erforderlich" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Ungültiges aktuelles Passwort" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Ungültig. Sind Sie sicher, dass das Token korrekt ist und Sie es nicht " "bereits verwendet haben?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Ungültig. Sind Sie sicher, dass das Token korrekt ist?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "Superuser Status" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -4027,25 +4332,25 @@ msgstr "" "Dieser Benutzer soll alle Berechtigungen erhalten, ohne dass diese zuvor " "zugewiesen werden müssen. " -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "Benutzername" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" "Benötigt. 30 Zeichen oder weniger.. Buchstaben, Zahlen und /./-/_ Zeichen" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Geben Sie einen gültigen Benuzternamen ein." -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "aktiv" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -4053,59 +4358,59 @@ msgstr "" "Kennzeichnet den Benutzer als aktiv. Deaktiviere die Option anstelle einen " "Benutzer zu löschen." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "Über mich" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "Foto" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "Beitrittsdatum" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "Vorgegebene Sprache" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "Standard-Theme" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "Vorgegebene Zeitzone" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "Tag-Farben" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "E-Mail Token" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "neue E-Mail Adresse" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "Berechtigungen" diff --git a/taiga/locale/en/LC_MESSAGES/django.po b/taiga/locale/en/LC_MESSAGES/django.po index 674cde40..52aa6596 100644 --- a/taiga/locale/en/LC_MESSAGES/django.po +++ b/taiga/locale/en/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # FIRST AUTHOR , YEAR. # @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" "PO-Revision-Date: 2015-03-25 20:09+0100\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Taiga Dev Team \n" @@ -16,15 +16,15 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "" -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "" @@ -44,17 +44,17 @@ msgstr "" msgid "User is already registered." msgstr "" -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "" #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "" @@ -75,130 +75,130 @@ msgstr "" msgid "Invalid value." msgstr "" -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "" -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "" -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "" -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "" -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "" -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "" -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "" -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "" -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "" -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "" -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "" -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "" -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "" @@ -262,7 +262,7 @@ msgstr "" msgid "Permission denied" msgstr "" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "" @@ -341,11 +341,11 @@ msgstr "" msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "" -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "" @@ -354,43 +354,43 @@ msgstr "" msgid "Taiga" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -647,6 +647,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -662,6 +663,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -680,6 +682,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "" @@ -733,7 +736,7 @@ msgid "It contain invalid custom fields." msgstr "" #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "" @@ -744,13 +747,13 @@ msgstr "" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "" @@ -762,12 +765,12 @@ msgstr "" msgid "web" msgstr "" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "" @@ -776,36 +779,34 @@ msgstr "" msgid "Next url" msgstr "" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -861,9 +862,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "" @@ -951,6 +952,221 @@ msgstr "" msgid "The status doesn't exist" msgstr "" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "" @@ -1119,89 +1335,89 @@ msgstr "" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "" @@ -1217,18 +1433,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "" @@ -1242,9 +1458,9 @@ msgstr "" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1262,14 +1478,18 @@ msgstr "" msgid "is deprecated" msgstr "" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "" @@ -1305,19 +1525,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 -msgid "Text" +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" msgstr "" #: taiga/projects/custom_attributes/choices.py:29 -msgid "Multi-Line Text" +msgid "Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:30 -msgid "Date" +msgid "Multi-Line Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1351,54 +1623,59 @@ msgstr "" msgid "Already exists one with the same name." msgstr "" -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1431,102 +1708,102 @@ msgstr "" msgid "Delete" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "" @@ -1544,23 +1821,23 @@ msgstr "" msgid "sprint" msgstr "" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "" -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "" -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "" -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "" -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "" @@ -1581,11 +1858,6 @@ msgstr "" msgid "finished date" msgstr "" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "" @@ -1594,33 +1866,33 @@ msgstr "" msgid "Likes" msgstr "" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "" @@ -1632,6 +1904,14 @@ msgstr "" msgid "is blocked" msgstr "" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1641,236 +1921,260 @@ msgstr "" msgid "'project' parameter is mandatory" msgstr "" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "" @@ -1905,7 +2209,7 @@ msgstr "" msgid "Notify exists for specified user and project" msgstr "" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "" @@ -2498,36 +2802,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2542,8 +2850,8 @@ msgstr "" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "" @@ -2593,15 +2901,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "" @@ -2617,31 +2925,31 @@ msgstr "" msgid "is iocaine" msgstr "" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3217,37 +3525,29 @@ msgstr "" msgid "Stakeholder" msgstr "" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "" @@ -3276,87 +3576,91 @@ msgstr "" msgid "There's no user story with that id" msgstr "" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "" @@ -3385,7 +3689,7 @@ msgstr "" msgid "href" msgstr "" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "" @@ -3425,133 +3729,133 @@ msgstr "" msgid "Important dates" msgstr "" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "" -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "" diff --git a/taiga/locale/es/LC_MESSAGES/django.po b/taiga/locale/es/LC_MESSAGES/django.po index 7340cd07..dcffe647 100644 --- a/taiga/locale/es/LC_MESSAGES/django.po +++ b/taiga/locale/es/LC_MESSAGES/django.po @@ -1,14 +1,16 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: -# David Barragán , 2015-2016 +# David Barragán , 2015-2017 # Esther Moreno , 2015 # Gustavo Díaz Jaimes , 2015 # Hector Colina , 2015 # Jesus Marin , 2015 # Jorge Sanchez , 2016 +# José Alejandro Díaz Carmona , 2016 +# Kiko Fernandez-Reyes , 2016 # Luis Sebastian Urrutia Fuentes , 2016 # Renelis Abreu Ramirez , 2016 # Taiga Dev Team , 2015-2016 @@ -17,9 +19,9 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" -"Last-Translator: Taiga Dev Team \n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 17:04+0000\n" +"Last-Translator: David Barragán \n" "Language-Team: Spanish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/es/)\n" "MIME-Version: 1.0\n" @@ -28,15 +30,15 @@ msgstr "" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "El registro público está deshabilitado." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "Tipo de registro inválido" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "Tipo de login inválido" @@ -56,17 +58,17 @@ msgstr "El token no pertenece a ninguna invitación válida." msgid "User is already registered." msgstr "Este usuario ya está registrado." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Este usuario ya es miembro del proyecto." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Error al crear un nuevo usuario " #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Token inválido" @@ -87,107 +89,107 @@ msgstr "Este campo es requerido." msgid "Invalid value." msgstr "Valor inválido." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "El valor para '%s' debe ser True o False." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Escribe un slug válido que esté formado por letras, números o los símbolos " "de guión o subrayado." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Seleccione una opción válida. %(value)s no es una de las opciones " "disponibles." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" -msgstr "" +msgstr "El dominio de tú email no es válido" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Introduzca una dirección de email válida." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "" "La fecha posee un formato inválido. Utiliza alguno de los siguientes " "formatos: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "" "La fecha y hora poseen un formato inválido. Utiliza alguno de los siguientes " "formatos: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "" "El tiempo indicado posee un formato inválido. Utiliza alguno de los " "siguientes formatos: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Introduce un número entero" -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Asegúrate de que el valor es menor o igual a %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Asegúrate de que el valor es mayor o igual a %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "El valor \"%s\" debe ser un número en coma flotante." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Introduce un número." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Asegúrate de que no haya más de %s dígitos en total." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Asegúrate de que no haya más de %s decimales." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "" "Asegúrate de que no haya más de %s dígitos en la parte entera del número." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "No se ha adjuntado ningún archivo. Comprueba el encoding en el formulario." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "No se envió el archivo" -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "El archivo enviado está vacío." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -195,36 +197,36 @@ msgstr "" "Asegúrate de que el nombre del fichero contiene menos de %(max)d caracteres " "(ahora tiene %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Por favor, adjunta un fichero o marca la casilla de vacío, no ambos." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Adjunta una imagen válida. El fichero no es una imagen o está dañada." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Elemento bloqueado" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "La página no es 'last' o no es un número." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Página no válida (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Definición de permiso inválida." @@ -291,7 +293,7 @@ msgstr "No encontrado" msgid "Permission denied" msgstr "Permiso denegado." -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Error en la aplicación del servidor." @@ -370,11 +372,11 @@ msgstr "Error por incumplimiento de precondición" msgid "No room left for more projects." msgstr "No hay espacio para mas proyectos" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Error en los típos de parámetros de filtrado" -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' debe ser un valor entero." @@ -383,43 +385,43 @@ msgstr "'project' debe ser un valor entero." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Síguenos en Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Copia el código en GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Visita nuestra web" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -445,6 +447,27 @@ msgid "" " \n" " " msgstr "" +"\n" +" Ayuda de Taiga:\n" +" %(support_url)s\n" +"
\n" +" Contacta con " +"nosotros:\n" +" \n" +" %(support_email)s\n" +" \n" +"
\n" +" Lista de correo:" +"\n" +" \n" +" %(mailing_list_url)s\n" +" \n" +" " #: taiga/base/templates/emails/hero-body-html.jinja:6 msgid "You have been Taigatized" @@ -547,7 +570,7 @@ msgstr "error importando las historias de usuario" #: taiga/export_import/services/store.py:790 msgid "error importing epics" -msgstr "" +msgstr "error importando epics" #: taiga/export_import/services/store.py:794 msgid "error importing tasks" @@ -563,7 +586,7 @@ msgstr "error importando los enlaces del wiki" #: taiga/export_import/services/store.py:806 msgid "error importing tags" -msgstr "error importando las etiquetas" +msgstr "error importando los tags" #: taiga/export_import/services/store.py:810 msgid "error importing timelines" @@ -747,6 +770,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -771,6 +795,7 @@ msgstr "" "

El Equipo de Taiga

" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -803,6 +828,7 @@ msgstr "" "El Equipo de Taiga\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -874,7 +900,7 @@ msgid "It contain invalid custom fields." msgstr "Contiene attributos personalizados inválidos." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Nombre duplicado para el proyecto" @@ -885,13 +911,13 @@ msgstr "Se requiere autenticación" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "nombre" @@ -903,12 +929,12 @@ msgstr "URL del icono" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "descripción" @@ -917,36 +943,34 @@ msgstr "descripción" msgid "Next url" msgstr "Siguiente URL" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "clave secreta para cifrar los tokens de aplicación" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "usuario" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "aplicación" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "nombre completo" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "dirección de email" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "comentario" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -1017,9 +1041,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "El payload no es un json válido" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "El proyecto no existe" @@ -1035,6 +1059,11 @@ msgid "" "\n" "\"{comment_message}\"" msgstr "" +"[@{user_name}]({user_url} \"Ver el perfil de @{user_name} en {platform}\") " +"dice en el comentario [{platform}#{number}]({comment_url} \"Ir al comentario" +"\"):\n" +"\n" +"\"{comment_message}\"" #: taiga/hooks/event_hooks.py:71 #, python-brace-format @@ -1043,6 +1072,9 @@ msgid "" "\n" "> {comment_message}" msgstr "" +"Comentario desde {platform}:\n" +"\n" +"> {comment_message}" #: taiga/hooks/event_hooks.py:84 msgid "Invalid issue comment information" @@ -1054,11 +1086,14 @@ msgid "" "Issue created by [@{user_name}]({user_url} \"See @{user_name}'s {platform} " "profile\") from [{platform}#{number}]({url} \"Go to issue\")." msgstr "" +"Petición creada por [@{user_name}]({user_url} \"Ver el perfil de " +"@{user_name} en {platform}\") desde [{platform}#{number}]({url} \"Ir a la " +"petición\")." #: taiga/hooks/event_hooks.py:107 #, python-brace-format msgid "Issue created from {platform}." -msgstr "" +msgstr "Petición creada desde {platform}." #: taiga/hooks/event_hooks.py:120 msgid "Invalid issue information" @@ -1066,7 +1101,7 @@ msgstr "Información inválida de Issue" #: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 msgid "unknown user" -msgstr "" +msgstr "usuario desconocido" #: taiga/hooks/event_hooks.py:156 #, python-brace-format @@ -1076,6 +1111,10 @@ msgid "" "\n" " - Status: **{src_status}** → **{dst_status}**" msgstr "" +"{user_text} cambió el estado desde un [commit en {platform}]({commit_url} " +"\"Vel el commit '{commit_id} - {commit_message}'\")\n" +"\n" +" - Estado: **{src_status}** → **{dst_status}**" #: taiga/hooks/event_hooks.py:161 #, python-brace-format @@ -1084,6 +1123,9 @@ msgid "" "\n" " - Status: **{src_status}** → **{dst_status}**" msgstr "" +"Cambiado el estado desde un commit de {platform}.\n" +"\n" +" - Estado: **{src_status}** → **{dst_status}**" #: taiga/hooks/event_hooks.py:179 #, python-brace-format @@ -1092,12 +1134,17 @@ msgid "" "({commit_url} \"See commit '{commit_id} - {commit_message}'\") " "\"{commit_message}\"" msgstr "" +"Esta {type_name} ha sido mencionada por {user_text} en el [commit de " +"{platform}]({commit_url} \"Ver commit '{commit_id} - {commit_message}'\") " +"\"{commit_message}\"" #: taiga/hooks/event_hooks.py:184 #, python-brace-format msgid "" "This issue has been mentioned in the {platform} commit \"{commit_message}\"" msgstr "" +"Esta pertición ha sido mencionada en el commit de {platform} " +"\"{commit_message}\"" #: taiga/hooks/event_hooks.py:206 msgid "The referenced element doesn't exist" @@ -1107,6 +1154,296 @@ msgstr "El elemento referenciado no existe" msgid "The status doesn't exist" msgstr "El estado no existe" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "El parámetro project es necesario" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "Solicitud de API de Asana no válida" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "No se pudo realizar la solicitud a la API de Asana" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "El parámetro code es necesario" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "Error importando el proyecto de Asana" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "Datos de autenticación inválidos" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "El servicio de terceros está fallando" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "Error importando el proyecto de GitHub" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "El parámetro url es necesario" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "project_type {} inválido" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "El token de atuenticación es inválido o ha expirado" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "Error importando el proyecto deJira" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "Error inportando el proyecto de PivotalTracker" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

Proyecto de Asana importado

\n" +"

Hola %(user)s,

\n" +"

Tu proyecto de Asana se ha importado correctamente.

\n" +" Ir a %(project)s\n" +"

El equipo de Taiga

\n" +" " + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"Hola %(user)s,\n" +"\n" +"Tu proyecto de Asana se ha importado correctamente.\n" +"\n" +"Puedes ver el proyecto %(project)s aquí:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"El equipo de Taiga\n" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "[%(project)s] Tu proyecto de Asana se ha importado" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

Proyecto de GitHub importado

\n" +"

Hola %(user)s,

\n" +"

Tu proyecto de GitHub se ha importado correctamente.

\n" +" Ir a %(project)s\n" +"

El equipo de Taiga

" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"Hola %(user)s,\n" +"\n" +"Tu proyecto de GitHub se ha importado correctamente.\n" +"\n" +"Puedes ver el proyecto %(project)s aquí:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"El equipo de Taiga\n" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "[%(project)s] Tu proyecto de GitHub se ha importado" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

Proyecto de Jira importado

\n" +"

Hola %(user)s,

\n" +"

Tu proyecto de Jira se ha importado correctamente.

\n" +" Ir a %(project)s\n" +"

El equipo de Taiga

\n" +" " + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"Hola %(user)s,\n" +"\n" +"Tu proyecto de Jira se ha importado correctamente.\n" +"\n" +"Puedes ver el proyecto %(project)s aquí:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"El equipo de Taiga\n" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "[%(project)s] Tu proyecto de Jira se ha importado" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

Proyecto de Trello importado

\n" +"

Hola %(user)s,

\n" +"

Tu proyecto de Trello se ha importado correctamente.

\n" +" Ir a %(project)s\n" +"

El equipo de Taiga

\n" +" " + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"Hola %(user)s,\n" +"\n" +"Tu proyecto de Trello se ha importado correctamente.\n" +"\n" +"Puedes ver el proyecto %(project)s aquí:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"El equipo de Taiga\n" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "[%(project)s] Tu proyecto de Trello se ha importado" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "Solicitud inválida: %s en %s" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "No autorizado: %s en %s" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "Recurso no disponible: %s en %s" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "Error importando proyecto de Trello" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Ver proyecto" @@ -1117,7 +1454,7 @@ msgstr "Ver sprints" #: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 msgid "View epic" -msgstr "" +msgstr "Ver epic" #: taiga/permissions/choices.py:26 msgid "View user stories" @@ -1153,19 +1490,19 @@ msgstr "Borrar sprint" #: taiga/permissions/choices.py:42 msgid "Add epic" -msgstr "" +msgstr "Añadir epic" #: taiga/permissions/choices.py:43 msgid "Modify epic" -msgstr "" +msgstr "Modificar epic" #: taiga/permissions/choices.py:44 msgid "Comment epic" -msgstr "" +msgstr "Comentar epic" #: taiga/permissions/choices.py:45 msgid "Delete epic" -msgstr "" +msgstr "Borrar epic" #: taiga/permissions/choices.py:47 msgid "View user story" @@ -1181,7 +1518,7 @@ msgstr "Modificar historia de usuario" #: taiga/permissions/choices.py:50 msgid "Comment user story" -msgstr "" +msgstr "Comentar historia de usuario" #: taiga/permissions/choices.py:51 msgid "Delete user story" @@ -1197,7 +1534,7 @@ msgstr "Modificar tarea" #: taiga/permissions/choices.py:56 msgid "Comment task" -msgstr "" +msgstr "Comentar tarea" #: taiga/permissions/choices.py:57 msgid "Delete task" @@ -1213,7 +1550,7 @@ msgstr "Modificar petición" #: taiga/permissions/choices.py:62 msgid "Comment issue" -msgstr "" +msgstr "Comentar en petición" #: taiga/permissions/choices.py:63 msgid "Delete issue" @@ -1229,7 +1566,7 @@ msgstr "Modificar pagina wiki" #: taiga/permissions/choices.py:68 msgid "Comment wiki page" -msgstr "" +msgstr "Comentar en página wiki" #: taiga/permissions/choices.py:69 msgid "Delete wiki page" @@ -1273,85 +1610,85 @@ msgstr "Administrar roles" #: taiga/projects/admin.py:100 msgid "Privacity" -msgstr "" +msgstr "Privacidad" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" -msgstr "" +msgstr "Módulos" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" -msgstr "" +msgstr "Valores por defecto" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" -msgstr "" +msgstr "Actividad" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" -msgstr "" +msgstr "Seguidores" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "Dueño" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." -msgstr "" +msgstr "{count} se hizo público satisfactoriamente" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" -msgstr "" +msgstr "Hacer público" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." -msgstr "" +msgstr "{count} se hizo privado satisfactoriamente" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" -msgstr "" +msgstr "Haz privado" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "" +msgstr "Eliminar seleccionados %(verbose_name_plural)s" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Argumentos incompletos" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Formato de imagen no válido" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Nombre de plantilla invalido" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Descripción de plantilla invalida" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "id de usuario inválido" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "El usuario no existe" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "El usuario debe ser un miembro del proyecto" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" @@ -1359,7 +1696,7 @@ msgstr "" "El proyecto debe tener un dueño y al menos uno de los usuarios debe ser un " "administrador activo" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "No tienes suficientes permisos para ver esto." @@ -1369,24 +1706,24 @@ msgstr "La actualización parcial no está soportada." #: taiga/projects/attachments/api.py:69 msgid "Object id issue isn't exists" -msgstr "" +msgstr "El 'Object id' de la petición no existe" #: taiga/projects/attachments/api.py:72 msgid "Project ID not matches between object and project" msgstr "El ID de proyecto no coincide entre el adjunto y un proyecto" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "Proyecto" @@ -1400,9 +1737,9 @@ msgstr "id de objeto" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1420,14 +1757,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "está desactualizado" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "desde comentario" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "orden" @@ -1461,21 +1802,97 @@ msgstr "El proyecto esta bloqueado porque el dueño ha salido" #: taiga/projects/choices.py:38 msgid "This project is blocked while it's deleted" -msgstr "" +msgstr "Este proyecto esta bloqueado hasta que sea borrado" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" +"\n" +" %(full_name)s ha " +"escrito a %(project_name)s\n" +" " + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" +"\n" +" Estás recibiendo este mensaje porque perteneces al grupo de " +"administradores del proyecto %(project_name)s. Si no quieres que nadie más " +"de la comunidad de Taiga pueda contactar con tu proyecto, actualiza la configuración de tu proyecto " +"en el panel de administración deshabilitando esta funcionalidad. El resto de " +"comunicaciones entre los miembros del proyecto no se verán afectadas por " +"este cambio." + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" +"\n" +"%(full_name)s ha escrito a %(project_name)s\n" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" +"\n" +"Estás recibiendo este mensaje porque perteneces al grupo de administradores " +"del proyecto %(project_name)s. Si no quieres que nadie más de la comunidad " +"de Taiga pueda contactar con tu proyecto, actualiza la configuración de tu " +"proyecto en el panel de administración en %(project_settings_url)s " +"deshabilitando esta funcionalidad. El resto de comunicaciones entre los " +"miembros del proyecto no se verán afectadas por este cambio.\n" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" +"\n" +"[Taiga] %(full_name)s ha enviado un mensaje al proyecto %(project_name)s\n" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Texto" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Texto multilínea" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "Texto enriquecido" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Fecha" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1490,7 +1907,7 @@ msgstr "valores" #: taiga/projects/custom_attributes/models.py:105 msgid "epic" -msgstr "" +msgstr "epic" #: taiga/projects/custom_attributes/models.py:121 #: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 @@ -1509,65 +1926,70 @@ msgstr "petición" msgid "Already exists one with the same name." msgstr "Ya existe uno con el mismo nombre." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." -msgstr "" +msgstr "No tiene permisos para establecer el estado de este epic" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "estado" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" -msgstr "" +msgstr "Orden de epics" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "asunto" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "color" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "asignado a" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "requerido por el cliente" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "requerido por el equipo" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" -msgstr "" +msgstr "historias de usuario" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "referencia externa" #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" -msgstr "" +msgstr "No existe ninguna épica con ese id" #: taiga/projects/history/api.py:93 msgid "comment is required" -msgstr "" +msgstr "Comentario requerido" #: taiga/projects/history/api.py:96 msgid "deleted comments can't be edited" -msgstr "" +msgstr "comentarios borrados no pueden ser editados" #: taiga/projects/history/api.py:130 msgid "Comment already deleted" @@ -1589,102 +2011,102 @@ msgstr "Crear" msgid "Delete" msgstr "Borrar" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "pntos del rol %(role)s" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "de" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "a" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Nuevo adjunto añadido" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Adjunto actualizado" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "obsoleto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "no obsoleto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Adjunto borrado" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "añadido" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "borrado" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "No asignado" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-borrado-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "a:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "de:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Añadido" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Cambiado" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Borrado" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "añadido:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "borrado:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "De:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "A:" @@ -1702,23 +2124,23 @@ msgstr "nota de bloqueo" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "No tienes permisos para asignar un sprint a esta petición." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "No tienes permisos para asignar un estado a esta petición." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "No tienes permisos para establecer la gravedad de esta petición." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "No tienes permiso para establecer la prioridad de esta petición." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "No tienes permiso para establecer el tipo de esta petición." @@ -1739,11 +2161,6 @@ msgstr "sprint" msgid "finished date" msgstr "fecha de finalización" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "referencia externa" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Like" @@ -1752,33 +2169,33 @@ msgstr "Like" msgid "Likes" msgstr "Likes" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "fecha estimada de comienzo" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "fecha estimada de finalización" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "está cerrada" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibilidad" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "" "La fecha de inicio estimada debe ser previa a la fecha de finalización " @@ -1786,12 +2203,20 @@ msgstr "" #: taiga/projects/milestones/validators.py:33 msgid "There's no milestone with that id" -msgstr "" +msgstr "No hay milestones con este id" #: taiga/projects/mixins/blocked.py:31 msgid "is blocked" msgstr "está bloqueada" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "el parametro ref es necesario" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "el parámetro project o project__slug es necesario" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1801,236 +2226,260 @@ msgstr "el parámetro '{param}' es obligatório" msgid "'project' parameter is mandatory" msgstr "el parámetro 'project' es obligatório" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "El usuario debe ser miembro del proyecto." + +#: taiga/projects/models.py:79 msgid "email" msgstr "email" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "creado el" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "texto extra de la invitación" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "orden del usuario" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "El usuario ya es miembro del proyecto" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" -msgstr "" +msgstr "valor por defecto del epic" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "estado de historia por defecto" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "puntos por defecto" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "estado de tarea por defecto" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "prioridad por defecto" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "gravedad por defecto" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "estado de petición por defecto" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "tipo de petición por defecto" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "logo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "miembros" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "total de sprints" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "puntos de historia totales" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 -msgid "active epics panel" -msgstr "" +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "activar contacto" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:174 taiga/projects/models.py:753 +msgid "active epics panel" +msgstr "Paneles épicos activos" + +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "panel de backlog activado" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "panel de kanban activado" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "panel de wiki activo" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "panel de peticiones activo" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "sistema de videoconferencia" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "datos extra de videoconferencia" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "creación de plantilla" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "privado" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "permisos de anónimo" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "permisos de usuario" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "es destacado" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "está buscando a gente" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "nota (buscando a gente)" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "notas sobre la búsqueda de gente" -#: taiga/projects/models.py:218 +#: taiga/projects/models.py:222 msgid "project transfer token" msgstr "token de transferencia de proyecto" -#: taiga/projects/models.py:222 +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "código bloqueado" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "fecha y hora de actualización" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "recuento" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "fans la última semana" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "fans el último mes" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "fans el último año" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "actividad la última semana" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "actividad el último mes" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "actividad el último áño" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "configuración de modulos" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "archivado" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "limite del trabajo en progreso" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "valor" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "rol por defecto para el propietario" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "opciones por defecto" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" -msgstr "" +msgstr "Estados del epic" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "estatuas de historias" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "puntos" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "estatus de tareas" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "estados de petición" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "tipos de petición" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "prioridades" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "gravedades" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "roles" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "atributos personalizados de épicas" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "atributos personalizados de histórias" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "atributos personalizados de tareas" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "atributos personalizados de peticiones" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Involucrado" @@ -2066,7 +2515,7 @@ msgid "Notify exists for specified user and project" msgstr "" "Ya existe una política de notificación para este usuario en el proyecto." -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Valor inválido para el nivel de notificación" @@ -2082,6 +2531,14 @@ msgid "" "%(subject)s in Taiga\">See epic\n" " " msgstr "" +"\n" +"

Épica actualizada

\n" +"

Hola %(user)s,
%(changer)s ha actualizado una épica en " +"%(project)s

\n" +"

Épica #%(ref)s %(subject)s

\n" +" Ver épica\n" +" " #: taiga/projects/notifications/templates/emails/epics/epic-change-body-text.jinja:3 #, python-format @@ -2091,6 +2548,10 @@ msgid "" "Hello %(user)s, %(changer)s has updated a epic on %(project)s\n" "See epic #%(ref)s %(subject)s at %(url)s\n" msgstr "" +"\n" +"Épica actualizada\n" +"Hola %(user)s, %(changer)s ha actualizado una épica en %(project)s\n" +"Ver épica #%(ref)s %(subject)s en %(url)s\n" #: taiga/projects/notifications/templates/emails/epics/epic-change-subject.jinja:1 #, python-format @@ -2098,6 +2559,8 @@ msgid "" "\n" "[%(project)s] Updated the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Actualizada la épica #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/epics/epic-create-body-html.jinja:4 #, python-format @@ -2112,6 +2575,14 @@ msgid "" "

The Taiga Team

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

Nueva épica creada

\n" +"

Hola %(user)s,
%(changer)s ha creado una nueva épica en " +"%(project)s

\n" +"

Épica #%(ref)s %(subject)s

\n" +" Ver épica\n" +"

El equipo de Taiga

" #: taiga/projects/notifications/templates/emails/epics/epic-create-body-text.jinja:1 #, python-format @@ -2124,6 +2595,13 @@ msgid "" "---\n" "The Taiga Team\n" msgstr "" +"\n" +"Nueva épica creada\n" +"Hola %(user)s, %(changer)s ha creado una nueva épica en %(project)s\n" +"Ver épica #%(ref)s %(subject)s en %(url)s\n" +"\n" +"---\n" +"El equipo de Taiga\n" #: taiga/projects/notifications/templates/emails/epics/epic-create-subject.jinja:1 #, python-format @@ -2131,6 +2609,8 @@ msgid "" "\n" "[%(project)s] Created the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Creada la épica #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/epics/epic-delete-body-html.jinja:4 #, python-format @@ -2143,6 +2623,13 @@ msgid "" "

The Taiga Team

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

Épica borrada

\n" +"

Hola %(user)s,
%(changer)s ha borrado una épica en %(project)s\n" +"

Épica #%(ref)s %(subject)s

\n" +"

El equipo de Taiga

\n" +" " #: taiga/projects/notifications/templates/emails/epics/epic-delete-body-text.jinja:1 #, python-format @@ -2155,6 +2642,13 @@ msgid "" "---\n" "The Taiga Team\n" msgstr "" +"\n" +"Épica borrada\n" +"Hola %(user)s, %(changer)s ha borrado una épica en %(project)s\n" +"Épica #%(ref)s %(subject)s\n" +"\n" +"---\n" +"El equipo de Taiga\n" #: taiga/projects/notifications/templates/emails/epics/epic-delete-subject.jinja:1 #, python-format @@ -2162,6 +2656,8 @@ msgid "" "\n" "[%(project)s] Deleted the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Borrada la épica #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/issues/issue-change-body-html.jinja:4 #, python-format @@ -2897,37 +3393,41 @@ msgstr "" "No puedes abandonar el proyecto si eres el dueño o no existen más " "administradores" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" -msgstr "" +msgstr "Proyecto sin propietario" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "Ha alcanzado el limite de miembros para proyectos privados" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "Ha alcanzado el limite de miembros para proyectos públicos" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "Ha alcanzado el límite actual de invitaciones pendientes" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "No puedes tener más proyectos privados" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" "Este proyecto alcanzo el limite actual de miembros para proyectos privados" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "No puedes tener más proyectos públicos" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2943,8 +3443,8 @@ msgstr "Final de proyecto" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "token inválido" @@ -2956,6 +3456,7 @@ msgstr "El token ha expirado" #, python-brace-format msgid "Invalid tag '{value}'. The color is not a valid HEX color or null." msgstr "" +"Etiqueta '{value}' inválida. El color no es un valor HEX válido o null." #: taiga/projects/tagging/fields.py:55 #, python-brace-format @@ -2963,46 +3464,50 @@ msgid "" "Invalid tag '{value}'. it must be the name or a pair '[\"name\", \"hex color/" "\" | null]'." msgstr "" +"'{value}' no es un tag válido. tiene que ser nombre o '[\"name\", \"hex " +"color/\" | null]'." #: taiga/projects/tagging/fields.py:77 #, python-brace-format msgid "Invalid tag '{value}'. It must be the tag name." msgstr "" +"'{value}' no es un tag válido. tiene que ser nombre o '[\"name\", \"hex " +"color/\" | null]'." #: taiga/projects/tagging/models.py:27 msgid "tags" -msgstr "etiquetas" +msgstr "tags" #: taiga/projects/tagging/models.py:35 msgid "tags colors" -msgstr "colores de etiquetas" +msgstr "colores de tags" #: taiga/projects/tagging/validators.py:47 #: taiga/projects/tagging/validators.py:74 msgid "This tag already exists." -msgstr "" +msgstr "Este tag ya existe" #: taiga/projects/tagging/validators.py:54 #: taiga/projects/tagging/validators.py:81 msgid "The color is not a valid HEX color." -msgstr "" +msgstr "El color no tiene un código hexadecimal válido." #: taiga/projects/tagging/validators.py:67 #: taiga/projects/tagging/validators.py:101 #: taiga/projects/tagging/validators.py:114 #: taiga/projects/tagging/validators.py:121 msgid "The tag doesn't exist." -msgstr "" +msgstr "El tag no existe" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "No tienes permisos para asignar este sprint a esta tarea." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "No tienes permisos para asignar esta historia a esta tarea." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "No tienes permisos para asignar este estado a esta tarea." @@ -3018,35 +3523,40 @@ msgstr "orden en el taskboard" msgid "is iocaine" msgstr "tiene iocaína" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." -msgstr "" +msgstr "invalido id de milestone." -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." -msgstr "" +msgstr "Estado de la tarea tiene un id que no es válido" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." -msgstr "" +msgstr "inválido id de historia de usuario." -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" +"Invalido id de estado de tarea. El estado debe pertenecer al mismo proyecto." -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" +"Invalido id de historia de usuario. La historia debe pertenecer al mismo " +"proyecto." -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." -msgstr "" +msgstr "Invalido id de milestone. El estado debe pertenecer al mismo proyecto." -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." msgstr "" +"Inválidos ids de tareas. Todas las tareas deben pertenecer al msimo proyecto " +"y, si existe, al mismo estado, historia de usuario y/o milestone." #: taiga/projects/templates/emails/membership_invitation-body-html.jinja:6 #: taiga/projects/templates/emails/membership_invitation-body-text.jinja:4 @@ -3749,39 +4259,31 @@ msgstr "Product Owner" msgid "Stakeholder" msgstr "Stakeholder" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "No tienes permisos para asignar este sprint a esta historia de usuario." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "No tienes permisos para asignar este estado a esta historia de usuario." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" -msgstr "" +msgstr "Inválido id de rol '{role_id}'" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" -msgstr "" +msgstr "Inválido id de punto de historia '{points_id}'" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Generada la historia de usuario #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rol" @@ -3796,7 +4298,7 @@ msgstr "orden en el sprint" #: taiga/projects/userstories/models.py:84 msgid "kanban order" -msgstr "" +msgstr "orden kanban" #: taiga/projects/userstories/models.py:92 msgid "finish date" @@ -3810,88 +4312,97 @@ msgstr "generada desde una petición" msgid "There's no user story with that id" msgstr "No existe ninguna historia de usuario con este id" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" +"Invalido id de estado de historia de usuario. El estado debe pertenecer al " +"mismo proyecto." -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." -msgstr "" +msgstr "Invalido id de milestone. El estado debe pertenecer al mismo proyecto." -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" +"Inválidos ids de historias de usuario. Todas las historias deben pertenecer " +"al msimo proyecto y, si existe, al mismo estado y milestone." -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" -msgstr "" +msgstr "El milestone no es válido para este proyecto" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" -msgstr "" +msgstr "Todas las historias de usuario deben ser del mismo proyecto" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "No existe ningún proyecto con este id" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "La dirección de email ya está en uso." +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "El usuario aún existe en el proyecto" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Rol inválido para el proyecto" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "El usuario debe ser un contacto válido" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "El dueño del proyecto debe ser administrador" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" "Por lo menos un usuario debe ser administrador activo para este proyecto" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" +"'role ids' inválidos. Todos los roles deben pertenecer al mismo proyecto." -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Opciones por defecto" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Estados de historia de usuario" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Puntos" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Estado de tareas" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Estados de peticion" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Tipos de petición" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioridades" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Gravedades" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Roles" @@ -3920,7 +4431,7 @@ msgstr "última modificación por" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Comprueba la API de histórico para obtener el diff exacto" @@ -3960,53 +4471,53 @@ msgstr "Restricciones" msgid "Important dates" msgstr "datos importántes" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Email duplicado" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Email no válido" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Nombre de usuario o email no válidos" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "¡Correo enviado con éxito!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "La contraseña actual es obligatoria." -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "La nueva contraseña es obligatoria" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "La longitud de la contraseña debe de ser de al menos 6 caracteres" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Contraseña actual inválida" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Invalido, ¿estás seguro de que el token es correcto y no se ha usado antes?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Inválido, ¿estás seguro de que el token es correcto?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "es superusuario" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -4014,24 +4525,24 @@ msgstr "" "Otorga todos los permisos a este usuario sin necesidad de hacerlo " "explicitamente." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "nombre de usuario" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Obligatorio. 30 caracteres o menos. Letras, números y /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Introduce un nombre de usuario válido" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "activo" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -4039,59 +4550,59 @@ msgstr "" "Denota a los usuarios activos. Desmárcalo para dar de baja/borrar a un " "usuario." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografía" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "foto" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "fecha de registro" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "idioma por defecto" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "tema por defecto" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "zona horaria por defecto" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" -msgstr "añade color a las etiquetas" +msgstr "añade color a los tags" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "token de email" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "nueva dirección de email" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "numero maximo de proyectos privados asignados" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "numero maximo de proyectos publicos asignados" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "máximo de membresías para cada proyecto privado poseído" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "máximo de membresías para cada proyecto público poseído" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "permisos" diff --git a/taiga/locale/fi/LC_MESSAGES/django.po b/taiga/locale/fi/LC_MESSAGES/django.po index 5139f702..4a26439a 100644 --- a/taiga/locale/fi/LC_MESSAGES/django.po +++ b/taiga/locale/fi/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Finnish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/fi/)\n" @@ -21,15 +21,15 @@ msgstr "" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Julkinen rekisteri on suljettu." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "väärä rekisterin tyyppi" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "väärä kirjautumistyyppi" @@ -49,17 +49,17 @@ msgstr "Tunniste ei vastaa mihinkään avoimeen kutsuun." msgid "User is already registered." msgstr "Käyttäjä on jo rekisteröitynyt." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Tämä käyttäjä on jo projektin jäsen." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Virhe käyttäjän luonnissa." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Väärä tunniste" @@ -81,108 +81,108 @@ msgstr "Pakollinen kenttä." msgid "Invalid value." msgstr "Virheellinen arvo." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' pitää olla True tai False." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Anna kelvollinen 'avain' joka koostuu merkeistä, numeroista, alaviivoista ja " "tavuviivoista." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Valitse kelvollinen valinta. %(value)s ei ole yksi vaihtoehdoista." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Anna voimassaoleva sähköpostiosoite." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "Päivämäärä on väärässä muodossa. Käytä yhtä näistä muodoista: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "Päiväys on väärässä muodossa. Käytä yhtä näistä muodoista: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Aika on väärässä muodossa. Käytä yhtä näistä muodoista: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Anna kokonaisluku." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Varmista että arvo on korkeintaan %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Varmista että arvo on vähintään %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" pitää olla desimaaliluku." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Anna numero." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Anna korkeintaan %s numeroa yhteensä." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Desimaaleja voi olla korkeintaan %s." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Ennen desimaalipistettä saa olla korkeintaan %s numeroa." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Tiedostoa ei lähtetty. Varmista merkistö lomakkeella." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Tiedostoa ei lähetetty." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Tiedosto oli tyhjä." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "" "Tiedoston nimi saa olla korkeintaan %(max)d pitkä se on nyt %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Valitse tiedosto tai Poista valintaneliö, ei molempia." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -191,25 +191,25 @@ msgstr "" "vioittunut." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Estetty elementti" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Sivu ei ole 'viimeinen', ekä sitä pystytä muuntamaan numeroksi." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Virheellinen sivu (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Virheellinen oikeuksien määrittely." @@ -273,7 +273,7 @@ msgstr "Ei löytynyt" msgid "Permission denied" msgstr "Ei käyttöoikeutta" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Palvelinsovelluksen virhe" @@ -352,11 +352,11 @@ msgstr "Precondition error" msgid "No room left for more projects." msgstr "Ei enää tilaa uusille projekteille." -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Error in filter params types." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' must be an integer value." @@ -365,43 +365,43 @@ msgstr "'project' must be an integer value." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Seuraa meitä Twitterissä" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Lataa koodi GitHubista" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Vieraile meidän web-sivuilla" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -710,6 +710,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -735,6 +736,7 @@ msgstr "" " " #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -766,6 +768,7 @@ msgstr "" "Taiga Tiimi\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -838,7 +841,7 @@ msgid "It contain invalid custom fields." msgstr "Sisältää vieheellisiä omia kenttiä." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Nimi on tuplana projektille" @@ -849,13 +852,13 @@ msgstr "" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "nimi" @@ -867,12 +870,12 @@ msgstr "" msgid "web" msgstr "" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "kuvaus" @@ -881,36 +884,34 @@ msgstr "kuvaus" msgid "Next url" msgstr "" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "koko nimi" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "sähköpostiosoite" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "kommentti" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -983,9 +984,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "The payload is not a valid json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Projektia ei löydy" @@ -1073,6 +1074,221 @@ msgstr "Viitattu elementtiä ei löydy" msgid "The status doesn't exist" msgstr "Tilaa ei löydy" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Katso projektia" @@ -1241,89 +1457,89 @@ msgstr "Hallinnoi rooleja" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "omistaja" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Puutteelliset argumentit" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Väärä kuvaformaatti" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Virheellinen mallipohjan nimi" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Virheellinen mallipohjan kuvaus" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Sinulla ei ole oikeuksia nähdä tätä." @@ -1339,18 +1555,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Projekti ID ei vastaa kohdetta ja projektia" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "projekti" @@ -1364,9 +1580,9 @@ msgstr "objekti ID" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1384,14 +1600,18 @@ msgstr "" msgid "is deprecated" msgstr "on poistettu" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "order" @@ -1427,19 +1647,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 -msgid "Text" +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" msgstr "" #: taiga/projects/custom_attributes/choices.py:29 -msgid "Multi-Line Text" +msgid "Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:30 -msgid "Date" +msgid "Multi-Line Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1473,54 +1745,59 @@ msgstr "pyyntö" msgid "Already exists one with the same name." msgstr "Nimi on jo olemassa" -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "viittaus" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "tila" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "aihe" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "väri" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "tekijä" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "on asiakkaan vaatimus" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "on tiimin vaatimus" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "ulkoinen viittaus" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1553,102 +1830,102 @@ msgstr "Luo" msgid "Delete" msgstr "Poista" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s roolipistettä" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "keneltä" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "kenelle" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Liitä tiedosto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Päivitä tiedosto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "poistettu" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "ei poistettu" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Poista liite" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "lisätty" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "poistettu" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Tekijä puuttuu" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-poistettu-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "kenelle:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "keneltä:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Lisätty" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Muutettu" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Poistettu" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "lisätty:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "poistettu:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Keneltä:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Kenelle:" @@ -1666,23 +1943,23 @@ msgstr "suljettu muistiinpano" msgid "sprint" msgstr "" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Sinulla ei ole oikeuksia laittaa kierrosta tälle pyynnölle." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Sinulla ei ole oikeutta asettaa statusta tälle pyyntö." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Sinulla ei ole oikeutta asettaa vakavuutta tälle pyynnölle." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "Sinulla ei ole oikeutta asettaa kiireellisyyttä tälle pyynnölle." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Sinulla ei ole oikeutta asettaa tyyppiä tälle pyyntö." @@ -1703,11 +1980,6 @@ msgstr "virstapylväs" msgid "finished date" msgstr "loppupvm" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "ulkoinen viittaus" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "" @@ -1716,33 +1988,33 @@ msgstr "" msgid "Likes" msgstr "" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "hukka-aika" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "arvioitu alkupvm" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "arvioitu loppupvm" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "on suljettu" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibility" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "Alkuajan pitää olla ennen loppuaikaa." @@ -1754,6 +2026,14 @@ msgstr "" msgid "is blocked" msgstr "on lukittu" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1763,236 +2043,260 @@ msgstr "'{param}' parametri on pakollinen" msgid "'project' parameter is mandatory" msgstr "'project' parametri on pakollinen" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "sähköposti" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "luo täällä" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "tunniste" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "kutsun lisäteksti" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "käyttäjäjärjestys" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Käyttäjä on jo projektin jäsen" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "oletus Kt tila" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "oletuspisteet" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "oletus tehtävän tila" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "oletus kiireellisyys" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "oletus vakavuus" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "oletus pyynnön tila" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "oletus pyyntö tyyppi" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "jäsenet" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "virstapyväitä yhteensä" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "käyttäjätarinan yhteispisteet" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "aktiivinen odottavien paneeli" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "aktiivinen kanban-paneeli" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "aktiivinen wiki-paneeli" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "aktiivinen pyyntöpaneeli" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "videokokous järjestelmä" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "luo mallipohja" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "on yksityinen" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "vieraan oikeudet" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "käyttäjän oikeudet" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "päivityspvm" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "moduulien asetukset" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "on arkistoitu" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "työn alla olevien max" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "arvo" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "oletus omistajan rooli" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "oletus optiot" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "kt tilat" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "pisteet" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "tehtävän tilat" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "pyyntöjen tilat" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "pyyntötyypit" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "kiireellisyydet" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "vakavuudet" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "roolit" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "" @@ -2027,7 +2331,7 @@ msgstr "" msgid "Notify exists for specified user and project" msgstr "Ilmoita olemassaolosta määritellyille käyttäjille ja projektille" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "" @@ -2866,36 +3170,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2910,8 +3218,8 @@ msgstr "Projektin loppu" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Tunniste on virheellinen" @@ -2961,15 +3269,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "" @@ -2985,31 +3293,31 @@ msgstr "tehtävätaulun järjestys" msgid "is iocaine" msgstr "on hidaste" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3634,37 +3942,29 @@ msgstr "Tuoteomistaja" msgid "Stakeholder" msgstr "Sidosryhmä" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rooli" @@ -3693,87 +3993,91 @@ msgstr "luotu pyynnöstä" msgid "There's no user story with that id" msgstr "En löydä käyttäjätarinaa tällä id:llä" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "En löydä projektia tällä id:llä" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Sähköpostiosoite on jo käytössä" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Virheellinen rooli projektille" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Oletusoptiot" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Käyttäjätarinatilat" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Pisteet" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Tehtävien tilat" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Pyyntöjen tilat" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "pyyntötyypit" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Kiireellisyydet" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Vakavuudet" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Roolit" @@ -3802,7 +4106,7 @@ msgstr "viimeksi muokannut" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "" @@ -3842,139 +4146,139 @@ msgstr "" msgid "Important dates" msgstr "Tärkeät päivämäärät" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Sähköposti on jo olemassa" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Virheellinen sähköposti" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Tuntematon käyttäjänimi tai sähköposti" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Sähköposti lähetetty." -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Nykyinen salasanaparametri tarvitaan" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Uusi salasanaparametri tarvitaan" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Salasanan pitää olla vähintään 6 merkkiä pitkä" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Virheellinen nykyinen salasana" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Virheellinen. Oletko varma, että tunniste on oikea ja et ole jo käyttänyt " "sitä?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Virheellinen, oletko varma että tunniste on oikea?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "pääkäyttäjän status" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "" "Kertoo että käyttäjä saa tehdä kaiken ilman erikseen annettuja oiekuksia." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "käyttäjänimi" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" "Vaaditaan. Korkeintaan 30merkkiä. Kirjaimet, numerot ja merkit /./-/_ " "sallittuja" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Anna olemassa oleva käyttäjänimi." -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "aktiivinen" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" "Käyttäjä on aktiivinen. Poista aktiivisuus käyttäjän poistamisen sijaan." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografia" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "kuva" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "liittymispvm" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "oletuskieli" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "oletus aikavyöhyke" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "väritä avainsanat" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "sähköpostitunniste" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "uusi sähköpostiosoite" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "oikeudet" diff --git a/taiga/locale/fr/LC_MESSAGES/django.po b/taiga/locale/fr/LC_MESSAGES/django.po index f579991b..fb4a2b1a 100644 --- a/taiga/locale/fr/LC_MESSAGES/django.po +++ b/taiga/locale/fr/LC_MESSAGES/django.po @@ -1,12 +1,14 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: +# Abdelouahad Megder , 2016 # Alain Poirier , 2015 # David Barragán , 2015 # Djyp Forest Fortin , 2015 # Florent B. , 2015 +# Gary , 2017 # Gautier Ferandelle , 2016 # Laurent Cabaret , 2016 # Louis-Michel Couture , 2015 @@ -23,8 +25,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: French (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/fr/)\n" @@ -34,15 +36,15 @@ msgstr "" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "L'inscription publique est désactivée." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "type d'inscription invalide" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "type d'identifiant invalide" @@ -62,17 +64,17 @@ msgstr "Le jeton ne correspond à aucune invitation." msgid "User is already registered." msgstr "Cet utilisateur est déjà inscrit." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "L'utilisateur est déjà un membre du projet" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Erreur à la création du nouvel utilisateur." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Jeton invalide" @@ -94,105 +96,105 @@ msgstr "Ce champ est requis." msgid "Invalid value." msgstr "Valeur invalide." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "La valeur de '%s' doit être soit Vrai soit Faux." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Entrez un 'slug' valide composé de lettres, chiffres, tirets bas ou traits " "d'union." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Sélectionnez une option valide. %(value)s ne fait pas partie des choix " "possibles." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" -msgstr "" +msgstr "Le nom de domaine de votre émail n'est pas autorisé" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Entrez une adresse email valide." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "" "Le format de la date est mauvais. Utilisez un de ces formats à la place: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "" "Le format de l'horodatage est mauvais. Utilisez un de ces formats à la " "place: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "" "Le format de l'heure est mauvais. Utilisez un de ces formats à la place: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Entrez un nombre entier." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" "Assurez-vous que cette valeur est inférieure ou égale à %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" "Assurez-vous que cette valeur est supérieure ou égale à %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "La valeur de \"%s\" doit être un nombre en virgule flottante." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Entrez un nombre." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Assurez-vous qu'il n'y a pas plus de %s chiffres au total." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Assurez-vous qu'il n'y a pas plus de %s décimales." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Assurez-vous qu'il n'y a pas plus de %s chiffres avant le point." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Aucun fichier n'a été soumis. Vérifiez l'encodage sur le formulaire. " -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Aucun fichier n'a été soumis." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Le fichier soumis est vide." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -200,13 +202,13 @@ msgstr "" "Assurez-vous que le nom de fichier comporte au plus %(max)d caractères (il " "en a %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Veuillez soit soumettre un fichier ou cocher la case de remise à zéro, mais " "pas les deux." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -215,27 +217,27 @@ msgstr "" "image ou était une image corrompue." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Élément bloqué" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "" "La page n'est pas la \"dernière\", et ne peut pas non plus être convertie en " "entier." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Page invalide (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Définition de permission invalide." @@ -301,7 +303,7 @@ msgstr "Non trouvé" msgid "Permission denied" msgstr "Permission refusée" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Erreur du serveur d'application" @@ -380,11 +382,11 @@ msgstr "Erreur de précondition" msgid "No room left for more projects." msgstr "Limite de projets atteinte." -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Erreur dans les types de paramètres de filtres" -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' doit être une valeur entière." @@ -393,43 +395,43 @@ msgstr "'project' doit être une valeur entière." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Suivez-nous sur Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Téléchargez le code sur GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Visitez notre site web" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -561,7 +563,7 @@ msgstr "erreur à l'importation des histoires utilisateur" #: taiga/export_import/services/store.py:790 msgid "error importing epics" -msgstr "" +msgstr "Erreur d'importation des épopées" #: taiga/export_import/services/store.py:794 msgid "error importing tasks" @@ -585,7 +587,7 @@ msgstr "erreur lors de l'import des timelines" #: taiga/export_import/services/store.py:832 msgid "unexpected error importing project" -msgstr "" +msgstr "Erreur imprévue à l'importation du projet" #: taiga/export_import/tasks.py:62 taiga/export_import/tasks.py:63 msgid "Error generating project dump" @@ -617,11 +619,11 @@ msgstr "Erreur au chargement du dump du projet" #: taiga/export_import/tasks.py:121 msgid "Error loading your project dump file" -msgstr "" +msgstr "Erreur lors du chargement de votre fichier de vidage de projet" #: taiga/export_import/tasks.py:135 msgid " -- no detail info --" -msgstr "" +msgstr " -- pas informations détaillées --" #: taiga/export_import/templates/emails/dump_project-body-html.jinja:4 #, python-format @@ -743,6 +745,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -768,6 +771,7 @@ msgstr "" " " #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -799,6 +803,7 @@ msgstr "" "L'équipe Taiga\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -870,7 +875,7 @@ msgid "It contain invalid custom fields." msgstr "Contient des champs personnalisés non valides." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Nom dupliqué pour ce projet" @@ -881,13 +886,13 @@ msgstr "Authentification requise" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "nom" @@ -899,12 +904,12 @@ msgstr "Url de l'icône" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "description" @@ -913,36 +918,34 @@ msgstr "description" msgid "Next url" msgstr "Url suivante" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "Clé secrète pour chiffrer le jeton de l'application" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "utilisateur" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "application" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "Nom complet" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "Adresse email" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "Commentaire" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -1013,9 +1016,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Le payload n'est pas un json valide" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Le projet n'existe pas" @@ -1062,7 +1065,7 @@ msgstr "Information incorrecte sur le problème" #: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 msgid "unknown user" -msgstr "" +msgstr "utilisateur inconnu" #: taiga/hooks/event_hooks.py:156 #, python-brace-format @@ -1103,6 +1106,221 @@ msgstr "L'élément référencé n'existe pas" msgid "The status doesn't exist" msgstr "L'état n'existe pas" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Consulter le projet" @@ -1113,7 +1331,7 @@ msgstr "Voir les jalons" #: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 msgid "View epic" -msgstr "" +msgstr "Voir epic" #: taiga/permissions/choices.py:26 msgid "View user stories" @@ -1153,15 +1371,15 @@ msgstr "" #: taiga/permissions/choices.py:43 msgid "Modify epic" -msgstr "" +msgstr "Modifier epic" #: taiga/permissions/choices.py:44 msgid "Comment epic" -msgstr "" +msgstr "Commenter epic" #: taiga/permissions/choices.py:45 msgid "Delete epic" -msgstr "" +msgstr "Supprimer epic" #: taiga/permissions/choices.py:47 msgid "View user story" @@ -1193,7 +1411,7 @@ msgstr "Modifier une tâche" #: taiga/permissions/choices.py:56 msgid "Comment task" -msgstr "" +msgstr "Commenter tâche" #: taiga/permissions/choices.py:57 msgid "Delete task" @@ -1209,7 +1427,7 @@ msgstr "Modifier le problème" #: taiga/permissions/choices.py:62 msgid "Comment issue" -msgstr "" +msgstr "Commenter problème" #: taiga/permissions/choices.py:63 msgid "Delete issue" @@ -1269,85 +1487,85 @@ msgstr "Administrer les rôles" #: taiga/projects/admin.py:100 msgid "Privacity" -msgstr "" +msgstr "Confidentialité" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" -msgstr "" +msgstr "Modules" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" -msgstr "" +msgstr "Les valeurs par défaut" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" -msgstr "" +msgstr "Activité" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" -msgstr "" +msgstr "Fans" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "propriétaire" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" -msgstr "" +msgstr "Rendre publique" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" -msgstr "" +msgstr "Rendre privé" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "arguments manquants" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "format de l'image non valide" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Nom de modèle non valide" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Description du modèle non valide" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "Identifiant utilisateur invalide" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "L'utilisateur n'existe pas" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "L'utilisateur doit déjà être un membre du projet" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" @@ -1355,7 +1573,7 @@ msgstr "" "Le projet doit avoir un propriétaire et au moins l'un de ses membres doit " "être un administrateur actif." -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Vous n'avez pas les permissions pour consulter cet élément" @@ -1371,18 +1589,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "L'identifiant du projet de correspond pas entre l'objet et le projet" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "projet" @@ -1396,9 +1614,9 @@ msgstr "identifiant de l'objet" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1416,14 +1634,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "est obsolète" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "ordre" @@ -1459,19 +1681,71 @@ msgstr "Ce projet est bloqué car son propriétaire est parti" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Texte" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Texte multi-ligne" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Date" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1486,7 +1760,7 @@ msgstr "valeurs" #: taiga/projects/custom_attributes/models.py:105 msgid "epic" -msgstr "" +msgstr "epic" #: taiga/projects/custom_attributes/models.py:121 #: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 @@ -1505,61 +1779,66 @@ msgstr "problème" msgid "Already exists one with the same name." msgstr "Un élément de même nom existe déjà" -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "réf" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "état" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "sujet" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "couleur" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "assigné à" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "est un requis client" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "est un requis de l'équipe" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "référence externe" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" #: taiga/projects/history/api.py:93 msgid "comment is required" -msgstr "" +msgstr "Un commentaire est requis" #: taiga/projects/history/api.py:96 msgid "deleted comments can't be edited" @@ -1585,102 +1864,102 @@ msgstr "Créer" msgid "Delete" msgstr "Supprimer" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s points de rôle" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "de" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "à" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Ajouter une pièce jointe" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Pièces jointes mises à jour" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "obsolète" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "non obsolète" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Pièce jointe supprimée" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "ajouté" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "supprimé" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Non assigné" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-supprimé-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "à :" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "de :" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Ajouté" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Modifié" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Supprimé" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "ajouté :" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "supprimé :" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "De :" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "A :" @@ -1698,23 +1977,23 @@ msgstr "note bloquée" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Vous n'avez pas la permission d'affecter ce sprint à ce problème." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Vous n'avez pas la permission d'affecter ce statut à ce problème." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Vous n'avez pas la permission d'affecter cette sévérité à ce problème." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "Vous n'avez pas la permission d'affecter cette priorité à ce problème." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Vous n'avez pas la permission d'affecter ce type à ce problème." @@ -1735,11 +2014,6 @@ msgstr "jalon" msgid "finished date" msgstr "date de fin" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "référence externe" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Aimer" @@ -1748,33 +2022,33 @@ msgstr "Aimer" msgid "Likes" msgstr "Aime" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "date de démarrage estimée" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "date de fin estimée" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "est fermé" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibilité" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "La date de démarrage doit être antérieure à la de fin prévisionnelle" @@ -1786,6 +2060,14 @@ msgstr "" msgid "is blocked" msgstr "est bloqué" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1795,236 +2077,260 @@ msgstr "'{param}' paramètre obligatoire" msgid "'project' parameter is mandatory" msgstr "'project' paramètre obligatoire" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "email" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "Créé le" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "jeton" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "Text supplémentaire de l'invitation" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "classement utilisateur" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "L'utilisateur est déjà un membre du projet" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "statut de l'HU par défaut" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "Points par défaut" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "Etat par défaut des tâches" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "Priorité par défaut" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "Sévérité par défaut" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "statut du problème par défaut" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "type de problème par défaut" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "logo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "membres" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "total des jalons" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "total des points d'histoire" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "panneau backlog actif" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "panneau kanban actif" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "panneau wiki actif" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "panneau problèmes actif" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "plateforme de vidéoconférence" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "données complémentaires pour la salle de vidéoconférence" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "Modèle de création" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "est privé" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "Permissions anonymes" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "Permission de l'utilisateur" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "est mis en avant" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "est à la recherche de main d'oeuvre" -#: taiga/projects/models.py:204 -msgid "loking for people note" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" -#: taiga/projects/models.py:218 +#: taiga/projects/models.py:222 msgid "project transfer token" msgstr "jeton de transfert de projet" -#: taiga/projects/models.py:222 +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "code bloqué" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "date de mise à jour" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "total" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "fans la semaine dernière" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "fans le mois dernier" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "fans l'année dernière" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "activité de la semaine écoulée" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "activité du mois écoulé" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "activité de l'année écoulée" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "Configurations des modules" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "est archivé" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "limite de travail en cours" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "valeur" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "rôle par défaut du propriétaire" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "options par défaut" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "statuts des us" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "points" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "états des tâches" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "statuts des problèmes" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "types de problèmes" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "priorités" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "sévérités" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "rôles" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Impliqué" @@ -2059,7 +2365,7 @@ msgstr "Suivre" msgid "Notify exists for specified user and project" msgstr "La notification existe pour l'utilisateur et le projet spécifiés" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Valeur non valide pour le niveau de notification" @@ -2684,36 +2990,40 @@ msgstr "" "Vous ne pouvez pas quitter le projet si vous en êtes le propriétaire ou " "qu'il n'y a pas d'autre administrateur." -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "Vous avez atteint le nombre maximum d'adhésions à des projets privés" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "Vous avez atteint le nombre maximum d'adhésions à des projets publics" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "Vous avez atteint le nombre maximum de projets privés" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "Ce projet privé est le dernier que vous pouvez rejoindre" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "Vous avez atteint le nombre maximum de projets publics." -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "Ce projet public est le dernier que vous pouvez rejoindre" @@ -2728,8 +3038,8 @@ msgstr "Fin du projet" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Jeton invalide" @@ -2779,15 +3089,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Vous n'avez pas la permission d'affecter ce sprint à cette tâche." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "Vous n'avez pas la permission d'affecter ce récit à cette tâche." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Vous n'avez pas la permission d'affecter ce statut à ce problème." @@ -2803,31 +3113,31 @@ msgstr "order du tableau de tâches" msgid "is iocaine" msgstr "est de l'iocaine" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3462,39 +3772,31 @@ msgstr "Product Owner" msgid "Stakeholder" msgstr "Participant" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Vous n'avez pas la permission d'affecter ce sprint à ce récit utilisateur." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "Vous n'avez pas la permission d'affecter ce statut à ce récit utilisateur." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rôle" @@ -3523,88 +3825,92 @@ msgstr "généré depuis un problème" msgid "There's no user story with that id" msgstr "Il n'y a pas d'user story avec cet id" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" -msgstr "" +msgstr "Le jalon n'est pas valide pour le projet" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" -msgstr "" +msgstr "Tous les user stories doivent provenir du même projet" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Aucun projet avec cet identifiant" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Adresse email déjà existante" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "L'utilisateur existe encore dans le projet" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Rôle non valide pour le projet" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "L'utilisateur doit être un contact valide" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "Le propriétaire du projet doit être un administrateur." -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" "Au moins un utilisateur doit être un administrateur actif de ce projet." -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Options par défaut" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Etats de la User Story" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Points" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Etats des tâches" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Statuts des problèmes" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Types de problèmes" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Priorités" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Sévérités" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Rôles" @@ -3633,17 +3939,17 @@ msgstr "dernier modificateur" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "" #: taiga/users/admin.py:39 msgid "Project Member" -msgstr "" +msgstr "Membre du projet" #: taiga/users/admin.py:40 msgid "Project Members" -msgstr "" +msgstr "Membres du projet" #: taiga/users/admin.py:50 msgid "id" @@ -3651,11 +3957,11 @@ msgstr "id" #: taiga/users/admin.py:81 msgid "Project Ownership" -msgstr "" +msgstr "Propriété du projet" #: taiga/users/admin.py:82 msgid "Project Ownerships" -msgstr "" +msgstr "Propriétés du projet" #: taiga/users/admin.py:119 msgid "Personal info" @@ -3673,54 +3979,54 @@ msgstr "Restrictions" msgid "Important dates" msgstr "Dates importantes" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Email dupliquée" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Email non valide" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Nom d'utilisateur ou email non valide" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Mail envoyé avec succès!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Paramètre 'mot de passe actuel' requis" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Paramètre 'nouveau mot de passe' requis" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Le mot de passe doit être d'au moins 6 caractères" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Mot de passe actuel incorrect" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Invalide, êtes-vous sûre que le jeton est correct et qu'il n'a pas déjà été " "utilisé ?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Invalide, êtes-vous sûre que le jeton est correct ?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "statut superutilisateur" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -3728,25 +4034,25 @@ msgstr "" "Indique que l'utilisateur a toutes les permissions sans avoir à lui les " "donner explicitement" -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "nom d'utilisateur" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" "Obligatoire. 30 caractères maximum. Lettres, nombres et les caractères /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Entrez un nom d'utilisateur valide" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "actif" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3754,59 +4060,59 @@ msgstr "" "Indique qu'un utilisateur est considéré ou non comme actif. Désélectionnez " "cette option au lieu de supprimer le compte utilisateur." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biographie" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "photo" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "date d'inscription" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "langage par défaut" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "thème par défaut" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "Fuseau horaire par défaut" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "changer la couleur des tags" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "jeton email" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "nouvelle adresse email" -#: taiga/users/models.py:166 -msgid "max number of owned private projects" -msgstr "" - #: taiga/users/models.py:169 -msgid "max number of owned public projects" -msgstr "" +msgid "max number of owned private projects" +msgstr "Nombre maximal de projets privés" #: taiga/users/models.py:172 +msgid "max number of owned public projects" +msgstr "Nombre maximal de projets publics" + +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" -msgstr "" +msgstr "Nombre maximum d'adhésions pour chaque projet privé" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" -msgstr "" +msgstr "Nombre maximum d'adhésions pour chaque projet public détenu" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "permissions" diff --git a/taiga/locale/it/LC_MESSAGES/django.po b/taiga/locale/it/LC_MESSAGES/django.po index b1b12bc8..a11979d7 100644 --- a/taiga/locale/it/LC_MESSAGES/django.po +++ b/taiga/locale/it/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -15,8 +15,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:55+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Italian (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/it/)\n" @@ -26,15 +26,15 @@ msgstr "" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." -msgstr "La registrazione pubblica è disabilitata." +msgstr "Il registro pubblico è disabilitato." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" -msgstr "Tipo di registrazione non valida" +msgstr "tipo di registro invalido" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "Tipo di login non valido" @@ -54,17 +54,17 @@ msgstr "Il token non corrisponde a nessun invito valido." msgid "User is already registered." msgstr "L'Utente è già registrato." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Questo utente fa già parte del progetto." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Errore nella creazione della nuova utenza." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Token non valido" @@ -87,99 +87,99 @@ msgstr "Questo campo è obbligatorio." msgid "Invalid value." msgstr "Valore non valido." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "il valore di '%s' deve essere o Vero o Falso." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 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." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Seleziona una scelta valida. %(value)s non è fra le scelte disponibili." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" -msgstr "" +msgstr "Il dominio della tua email non e' permesso" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Inserisci un indirizzo e-mail valido." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "La data non ha un formato valido. Usa uno dei formati disponibili: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "L'orario non ha un formato valido. Usa uno dei formati disponibili: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Formato temporale errato. Usa uno dei seguenti formati: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Inserisci il numero completo." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Assicurati che questo valore sia minore o uguale di %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Assicurati che questo valore sia maggiore o uguale di %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "il valore \"%s\" deve essere un valore \"float\"." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Inserisci un numero." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Assicurati che non ci siano più di %s cifre in totale." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Assicurati che non ci siano più di %s decimali." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Assicurati che non ci siano più di %s cifre prima del punto decimale." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Non è stato caricato alcun file. Controlla il tipo di codifica nella scheda." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Nessun file caricato." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Il file caricato è vuoto." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -187,12 +187,12 @@ msgstr "" "Assicurati che il nome del file abbia al massimo %(max)d caratteri (ne ha " "%(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Carica il file oppure controlla la casella deselezionata. Non entrambi. " -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -201,25 +201,25 @@ msgstr "" "o l'immagine potrebbe essere corrotta. " #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" -msgstr "" +msgstr "Elemento bloccato" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "La pagina non è 'last', né può essere convertita come int." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Pagina (%(page_number)s) invalida: %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Definizione di permesso non valida." @@ -285,7 +285,7 @@ msgstr "Non trovato" msgid "Permission denied" msgstr "Permesso negato" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Errore sul server" @@ -363,13 +363,13 @@ msgstr "Errore di precondizione" #: taiga/base/exceptions.py:219 msgid "No room left for more projects." -msgstr "" +msgstr "Non c'e' piu' spazio per altri progetti." -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Errore nel filtro del tipo di parametri." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'Progetto' deve essere un valore intero." @@ -378,43 +378,43 @@ msgstr "'Progetto' deve essere un valore intero." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Seguici su Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Prendi il codice su GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Visita il nostro sito" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -440,6 +440,22 @@ msgid "" " \n" " " msgstr "" +"\n" +"Supporto di Taiga:\n" +"%(support_url)s\n" +"
\n" +"Contattaci:\n" +"\n" +"%(support_email)s\n" +"\n" +"
\n" +"Mailing list:\n" +"\n" +"%(mailing_list_url)s\n" +"" #: taiga/base/templates/emails/hero-body-html.jinja:6 msgid "You have been Taigatized" @@ -545,7 +561,7 @@ msgstr "Errore nell'importazione delle user story" #: taiga/export_import/services/store.py:790 msgid "error importing epics" -msgstr "" +msgstr "errore di importazione degli epici" #: taiga/export_import/services/store.py:794 msgid "error importing tasks" @@ -569,7 +585,7 @@ msgstr "Errore nell'importazione delle timelines" #: taiga/export_import/services/store.py:832 msgid "unexpected error importing project" -msgstr "" +msgstr "si e' verificato un errore inaspettato importando il progetto" #: taiga/export_import/tasks.py:62 taiga/export_import/tasks.py:63 msgid "Error generating project dump" @@ -594,6 +610,23 @@ msgid "" "TRACE ERROR:\n" "------------" msgstr "" +"\n" +"\n" +"Errore di caricamento del dump di {user_full_name} <{user_email}>:\"\n" +"\n" +"\n" +"RAGIONE:\n" +"-------\n" +"\n" +"{reason}\n" +"\n" +"DETTAGLI:\n" +"--------\n" +"\n" +"{details}\n" +"\n" +"TRACCE DELL'ERRORE:\n" +"------------" #: taiga/export_import/tasks.py:120 msgid "Error loading project dump" @@ -601,11 +634,11 @@ msgstr "Errore nel caricamento del dump di progetto" #: taiga/export_import/tasks.py:121 msgid "Error loading your project dump file" -msgstr "" +msgstr "Errore nel caricamento del dump di progetto" #: taiga/export_import/tasks.py:135 msgid " -- no detail info --" -msgstr "" +msgstr "-- non ci sono informazioni dettagliate --" #: taiga/export_import/templates/emails/dump_project-body-html.jinja:4 #, python-format @@ -757,6 +790,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -786,6 +820,7 @@ msgstr "" "

Il Team di Taiga

" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -830,6 +865,7 @@ msgstr "" "Il Team di Taiga\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -916,7 +952,7 @@ msgid "It contain invalid custom fields." msgstr "Contiene campi personalizzati invalidi." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Il nome del progetto è duplicato" @@ -927,13 +963,13 @@ msgstr "E' richiesta l'autenticazione" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "nome" @@ -945,12 +981,12 @@ msgstr "Url dell'icona" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "descrizione" @@ -959,36 +995,34 @@ msgstr "descrizione" msgid "Next url" msgstr "Url successivo" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "chiave segreta per cifrare i token dell'applicazione" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "utente" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "applicazione" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "Nome completo" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "Inserisci un indirizzo e-mail valido." -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "Commento" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -1065,9 +1099,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Il carico non è un json valido" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Il progetto non esiste" @@ -1083,6 +1117,11 @@ msgid "" "\n" "\"{comment_message}\"" msgstr "" +"[@{user_name}] \"Vedi cosa dice il ({user_url} profilo {platform} di " +"@{user_name}\") da [{platform}#{number}]({comment_url} \"Vai al commento" +"\"):\n" +"\n" +"\"{comment_message}\"" #: taiga/hooks/event_hooks.py:71 #, python-brace-format @@ -1091,6 +1130,9 @@ msgid "" "\n" "> {comment_message}" msgstr "" +"Commento Da {platform}:\n" +"\n" +"> {comment_message}" #: taiga/hooks/event_hooks.py:84 msgid "Invalid issue comment information" @@ -1102,11 +1144,13 @@ msgid "" "Issue created by [@{user_name}]({user_url} \"See @{user_name}'s {platform} " "profile\") from [{platform}#{number}]({url} \"Go to issue\")." msgstr "" +"Problema creato da [@{user_name}]({user_url} \"Vedi il profilo {platform} di " +"@{user_name}\") da [{platform}#{number}]({url} \"Vai al problema\")." #: taiga/hooks/event_hooks.py:107 #, python-brace-format msgid "Issue created from {platform}." -msgstr "" +msgstr "Problema creato da {platform}." #: taiga/hooks/event_hooks.py:120 msgid "Invalid issue information" @@ -1114,7 +1158,7 @@ msgstr "Informazione sul problema non valida" #: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 msgid "unknown user" -msgstr "" +msgstr "utente sconosciuto" #: taiga/hooks/event_hooks.py:156 #, python-brace-format @@ -1124,6 +1168,10 @@ msgid "" "\n" " - Status: **{src_status}** → **{dst_status}**" msgstr "" +"{user_text} ha cambiato lo stato da [{platform} commit]({commit_url} \"Vedi " +"il commit '{commit_id} - {commit_message}'\")\n" +"\n" +"- Status: **{src_status}** → **{dst_status}**" #: taiga/hooks/event_hooks.py:161 #, python-brace-format @@ -1132,6 +1180,9 @@ msgid "" "\n" " - Status: **{src_status}** → **{dst_status}**" msgstr "" +"Cambiato lo stato dal commit {platform}.\n" +"\n" +"- Stato: **{src_status}** → **{dst_status}**" #: taiga/hooks/event_hooks.py:179 #, python-brace-format @@ -1140,12 +1191,16 @@ msgid "" "({commit_url} \"See commit '{commit_id} - {commit_message}'\") " "\"{commit_message}\"" msgstr "" +"Questo {type_name} e' stato citato da {user_text} nel [{platform} commit]" +"({commit_url} \"Vedi il commit commit '{commit_id} - {commit_message}'\") " +"\"{commit_message}\"" #: taiga/hooks/event_hooks.py:184 #, python-brace-format msgid "" "This issue has been mentioned in the {platform} commit \"{commit_message}\"" msgstr "" +"Questo problema e' stato citato nel commit \"{commit_message}\" di {platform}" #: taiga/hooks/event_hooks.py:206 msgid "The referenced element doesn't exist" @@ -1155,6 +1210,221 @@ msgstr "L'elemento di riferimento non esiste" msgid "The status doesn't exist" msgstr "Lo stato non esiste" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Vedi progetto" @@ -1165,7 +1435,7 @@ msgstr "Guarda le milestones" #: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 msgid "View epic" -msgstr "" +msgstr "Vedi epico" #: taiga/permissions/choices.py:26 msgid "View user stories" @@ -1201,19 +1471,19 @@ msgstr "Elimina la tappa" #: taiga/permissions/choices.py:42 msgid "Add epic" -msgstr "" +msgstr "Aggiungi epico" #: taiga/permissions/choices.py:43 msgid "Modify epic" -msgstr "" +msgstr "Modifica epico" #: taiga/permissions/choices.py:44 msgid "Comment epic" -msgstr "" +msgstr "Commenta epico" #: taiga/permissions/choices.py:45 msgid "Delete epic" -msgstr "" +msgstr "Eliminca epico" #: taiga/permissions/choices.py:47 msgid "View user story" @@ -1229,7 +1499,7 @@ msgstr "Modifica una storia utente" #: taiga/permissions/choices.py:50 msgid "Comment user story" -msgstr "" +msgstr "Commenta la storia dell'utente" #: taiga/permissions/choices.py:51 msgid "Delete user story" @@ -1245,7 +1515,7 @@ msgstr "Modifica il compito" #: taiga/permissions/choices.py:56 msgid "Comment task" -msgstr "" +msgstr "Commenta compito" #: taiga/permissions/choices.py:57 msgid "Delete task" @@ -1261,7 +1531,7 @@ msgstr "Modifica il problema" #: taiga/permissions/choices.py:62 msgid "Comment issue" -msgstr "" +msgstr "Commenta il problema" #: taiga/permissions/choices.py:63 msgid "Delete issue" @@ -1277,7 +1547,7 @@ msgstr "Modifica la pagina wiki" #: taiga/permissions/choices.py:68 msgid "Comment wiki page" -msgstr "" +msgstr "Commenta la pagina wiki" #: taiga/permissions/choices.py:69 msgid "Delete wiki page" @@ -1321,91 +1591,93 @@ msgstr "Ruoli dell'amministratore" #: taiga/projects/admin.py:100 msgid "Privacity" -msgstr "" +msgstr "Privacy" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" -msgstr "" +msgstr "Moduli" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" -msgstr "" +msgstr "Valori di default" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" -msgstr "" +msgstr "Attività" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "proprietario" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." -msgstr "" +msgstr "{count} resi pubblici con successo." -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" -msgstr "" +msgstr "Rendi pubblico" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." -msgstr "" +msgstr "{count} resi privati con successo." -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" -msgstr "" +msgstr "Rendi privato" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "" +msgstr "Cancella selezionati %(verbose_name_plural)s" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Argomento non valido" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Formato dell'immagine non valido" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Il nome del template non è valido" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "La descrizione del template non è valida" -#: taiga/projects/api.py:344 -msgid "Invalid user id" -msgstr "" - -#: taiga/projects/api.py:350 -msgid "The user doesn't exist" -msgstr "" - #: taiga/projects/api.py:354 -msgid "The user must be already a project member" -msgstr "" +msgid "Invalid user id" +msgstr "id utente non valido" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:360 +msgid "The user doesn't exist" +msgstr "L'utente non esiste" + +#: taiga/projects/api.py:364 +msgid "The user must be already a project member" +msgstr "L'utente deve gia' essere un membro del progetto" + +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" +"Il progetto deve avere un proprietario ed almeno uno dei suoi utenti deve " +"essere un amministratore attivo" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Non hai il permesso di vedere questo elemento." @@ -1415,24 +1687,24 @@ msgstr "Aggiornamento non parziale non supportato" #: taiga/projects/attachments/api.py:69 msgid "Object id issue isn't exists" -msgstr "" +msgstr "Il problema dell'oggetto id non esiste" #: taiga/projects/attachments/api.py:72 msgid "Project ID not matches between object and project" msgstr "L'ID di progetto non corrisponde tra oggetto e progetto" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "progetto" @@ -1446,9 +1718,9 @@ msgstr "ID dell'oggetto" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1466,14 +1738,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "non approvato" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "ordine" @@ -1495,35 +1771,87 @@ msgstr "Talky" #: taiga/projects/choices.py:35 msgid "This project is blocked due to payment failure" -msgstr "" +msgstr "Questo progetto e' bloccato a causa di un falliimento nel pagamento" #: taiga/projects/choices.py:36 msgid "This project is blocked by admin staff" -msgstr "" +msgstr "Questo progetto e' bloccato dallo staff di amministrazione" #: taiga/projects/choices.py:37 msgid "This project is blocked because the owner left" -msgstr "" +msgstr "Questo progetto e' bloccato perche' il proprietario lo ha abbandonato" #: taiga/projects/choices.py:38 msgid "This project is blocked while it's deleted" +msgstr "Questo progetto e' bloccato finche' e' cancellato" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Testo" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Testo multi-linea" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Data" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" -msgstr "" +msgstr "Url" #: taiga/projects/custom_attributes/models.py:40 #: taiga/projects/issues/models.py:45 @@ -1536,7 +1864,7 @@ msgstr "valori" #: taiga/projects/custom_attributes/models.py:105 msgid "epic" -msgstr "" +msgstr "epico" #: taiga/projects/custom_attributes/models.py:121 #: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 @@ -1555,65 +1883,70 @@ msgstr "problema" msgid "Already exists one with the same name." msgstr "Ne esiste già un altro con lo stesso nome" -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." -msgstr "" +msgstr "Non hai i permessi per impostare lo stato a questo epico." -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "referenza" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "stato" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" -msgstr "" +msgstr "ordine epici" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "soggeto" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "colore" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "assegnato a" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "é un requisito del cliente " -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "é una richiesta del team" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" -msgstr "" +msgstr "storie utente" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "referenza esterna" #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" -msgstr "" +msgstr "Non ci sono epici con questo id" #: taiga/projects/history/api.py:93 msgid "comment is required" -msgstr "" +msgstr "commento richiesto" #: taiga/projects/history/api.py:96 msgid "deleted comments can't be edited" -msgstr "" +msgstr "i commenti cancellati non possono essere modificati" #: taiga/projects/history/api.py:130 msgid "Comment already deleted" @@ -1635,102 +1968,102 @@ msgstr "Creato" msgid "Delete" msgstr "Eliminato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s punti del ruolo" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "da" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "a" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Aggiunto un nuovo allegato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Allegato aggiornato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "non approvato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "accettato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Allegato eliminato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "aggiunto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "rimosso" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Non assegnato" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-eliminato-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "a:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "da:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Aggiunto" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Modificato" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Eliminato" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "aggiunto:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "rimosso:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Da:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "A:" @@ -1748,23 +2081,23 @@ msgstr "nota bloccata" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Non hai i permessi per aggiungere questo sprint a questo problema" -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Non hai i permessi per aggiungere questo stato a questo problema" -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Non hai i permessi per aggiungere questa criticità a questo problema" -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "Non hai i permessi per aggiungere questa priorità a questo problema." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Non hai i permessi per aggiungere questa tipologia a questo problema" @@ -1785,11 +2118,6 @@ msgstr "tappa" msgid "finished date" msgstr "data di conclusione" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "referenza esterna" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Like" @@ -1798,45 +2126,53 @@ msgstr "Like" msgid "Likes" msgstr "Piaciuto" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "lumaca" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "data stimata di inizio" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "data stimata di fine" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "è concluso" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibilità" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "" "La data stimata di inizio deve essere precedente alla data stimata di fine." #: taiga/projects/milestones/validators.py:33 msgid "There's no milestone with that id" -msgstr "" +msgstr "Non ci sono milestone con questo id" #: taiga/projects/mixins/blocked.py:31 msgid "is blocked" msgstr "è bloccato" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "parametro ref e' richiesto" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1846,236 +2182,260 @@ msgstr "il parametro '{param}' è obbligatorio" msgid "'project' parameter is mandatory" msgstr "il parametro 'project' è obbligatorio" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "email" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "creato a " -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "testo ulteriore per l'invito" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "ordine dell'utente" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "L'utente è già membro del progetto" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" -msgstr "" +msgstr "stato predefinito dell'epico" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "stati predefiniti per le storie utente" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "punti predefiniti" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "stati predefiniti del compito" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "priorità predefinita" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "criticità predefinita" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "stato predefinito del problema" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "tipologia predefinita del problema" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "logo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "membri" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "tappe totali" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "punti totali della storia" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 -msgid "active epics panel" +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:174 taiga/projects/models.py:753 +msgid "active epics panel" +msgstr "pannelli epici attivi" + +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "pannello di backlog attivo" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "pannello kanban attivo" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "pannello wiki attivo" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "pannello dei problemi attivo" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "sistema di videoconferenza" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "ulteriori dati di videoconferenza " -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "creazione del template" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "è privato" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "permessi anonimi" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "permessi dell'utente" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "in vetrina" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "sta cercando persone" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "note sulla ricerca delle persone " - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 -msgid "blocked code" -msgstr "" +msgid "project transfer token" +msgstr "token di trasferimento progetto" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:226 +msgid "blocked code" +msgstr "codice bloccato" + +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "tempo e data aggiornati" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "conta" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "fans nella settimana" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "fans nel mese" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "fans nell'anno" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "attività nella settimana" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "attività nel mese" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "attività nell'anno" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "configurazione dei moduli" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "è archivitato" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "limite dei lavori in corso" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "valore" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "ruolo proprietario predefinito" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "opzioni predefinite " -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" -msgstr "" +msgstr "stati dell'epico" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "stati della storia utente" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "punti" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "stati del compito" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "stati del probema" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "tipologie del problema" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "priorità" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "criticità " -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "ruoli" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Coinvolto" @@ -2110,7 +2470,7 @@ msgstr "Osservato" msgid "Notify exists for specified user and project" msgstr "La notifica esiste per l'utente e il progetto specificati" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Valore non valido per il livello di notifica" @@ -2126,6 +2486,13 @@ msgid "" "%(subject)s in Taiga\">See epic\n" " " msgstr "" +"\n" +"

Epico aggiornato

\n" +"

Ciao %(user)s,
%(changer)s ha aggiornato un epico su %(project)s\n" +"

Epico #%(ref)s %(subject)s

\n" +"Vedi epico" #: taiga/projects/notifications/templates/emails/epics/epic-change-body-text.jinja:3 #, python-format @@ -2135,6 +2502,10 @@ msgid "" "Hello %(user)s, %(changer)s has updated a epic on %(project)s\n" "See epic #%(ref)s %(subject)s at %(url)s\n" msgstr "" +"\n" +"Epico aggiornato\n" +"Ciao %(user)s, %(changer)s ha aggiornato un epico su %(project)s\n" +"Vedi epico #%(ref)s %(subject)s in %(url)s\n" #: taiga/projects/notifications/templates/emails/epics/epic-change-subject.jinja:1 #, python-format @@ -2142,6 +2513,8 @@ msgid "" "\n" "[%(project)s] Updated the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Aggiornato l'epico #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/epics/epic-create-body-html.jinja:4 #, python-format @@ -2156,6 +2529,14 @@ msgid "" "

The Taiga Team

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

Nuovo epico creato

\n" +"

Ciao %(user)s,
%(changer)s ha creato un nuovo epico su %(project)s\n" +"

Epico #%(ref)s %(subject)s

\n" +"Vedi epico\n" +"

Il Team di Taiga

" #: taiga/projects/notifications/templates/emails/epics/epic-create-body-text.jinja:1 #, python-format @@ -2168,6 +2549,13 @@ msgid "" "---\n" "The Taiga Team\n" msgstr "" +"\n" +"Nuovo epico creato\n" +"Ciao %(user)s, %(changer)s ha creato un nuovo epico in %(project)s\n" +"Vedi epico #%(ref)s %(subject)s in %(url)s\n" +"\n" +"---\n" +"Il Team di Taiga\n" #: taiga/projects/notifications/templates/emails/epics/epic-create-subject.jinja:1 #, python-format @@ -2175,6 +2563,8 @@ msgid "" "\n" "[%(project)s] Created the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Ha creato l'epico #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/epics/epic-delete-body-html.jinja:4 #, python-format @@ -2199,6 +2589,13 @@ msgid "" "---\n" "The Taiga Team\n" msgstr "" +"\n" +"Epico cancellato\n" +"Ciao %(user)s, %(changer)s ha cancellato un epico su %(project)s\n" +"Epic #%(ref)s %(subject)s\n" +"\n" +"---\n" +"Il Team di Taiga\n" #: taiga/projects/notifications/templates/emails/epics/epic-delete-subject.jinja:1 #, python-format @@ -2206,6 +2603,8 @@ msgid "" "\n" "[%(project)s] Deleted the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Ha cancellato l'epico #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/issues/issue-change-body-html.jinja:4 #, python-format @@ -3088,40 +3487,50 @@ msgstr "versione" msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" +"Non puoi lasciare il progetto se sei il proprietario o se non ci sono piu' " +"amministratori" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" -msgstr "" +msgstr "Progetto senza proprietario" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" -msgstr "" +msgstr "Sei arrivato al tuo limite attuale di iscrizioni per progetti privati" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" +msgstr "Sei arrivato al tuo limite attuale di iscrizioni per progetti pubblici" + +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" -msgstr "" +msgstr "Non puoi avere altri progetti privati" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" +"Questo progetto arriva al tuo limite attuale di iscrizioni per progetti " +"privati" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" -msgstr "" +msgstr "Non puoi avere altri progetti pubblici" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" +"Questo progetto arriva al tuo limite attuale di iscrizioni per progetti " +"pubblici" #: taiga/projects/services/stats.py:197 msgid "Future sprint" @@ -3133,19 +3542,20 @@ msgstr "Termine di progetto" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Token non valido" #: taiga/projects/services/transfer.py:67 msgid "Token has expired" -msgstr "" +msgstr "Il token e' scaduto" #: taiga/projects/tagging/fields.py:52 #, python-brace-format msgid "Invalid tag '{value}'. The color is not a valid HEX color or null." msgstr "" +"Tag '{value}' non corretto. Il colore non e' un codice HEX corretto o vuoto." #: taiga/projects/tagging/fields.py:55 #, python-brace-format @@ -3153,11 +3563,13 @@ msgid "" "Invalid tag '{value}'. it must be the name or a pair '[\"name\", \"hex color/" "\" | null]'." msgstr "" +"Tag '{value}' non corretto. Deve essere il nome o una coppia '[\"name\", " +"\"hex color/\" | null]'." #: taiga/projects/tagging/fields.py:77 #, python-brace-format msgid "Invalid tag '{value}'. It must be the tag name." -msgstr "" +msgstr "Tag '{value}' non corretto. Deve essere il nome tag" #: taiga/projects/tagging/models.py:27 msgid "tags" @@ -3170,30 +3582,30 @@ msgstr "colori dei tag" #: taiga/projects/tagging/validators.py:47 #: taiga/projects/tagging/validators.py:74 msgid "This tag already exists." -msgstr "" +msgstr "Questo tag esiste gia'." #: taiga/projects/tagging/validators.py:54 #: taiga/projects/tagging/validators.py:81 msgid "The color is not a valid HEX color." -msgstr "" +msgstr "Il colore non e' un codice HEX valido." #: taiga/projects/tagging/validators.py:67 #: taiga/projects/tagging/validators.py:101 #: taiga/projects/tagging/validators.py:114 #: taiga/projects/tagging/validators.py:121 msgid "The tag doesn't exist." -msgstr "" +msgstr "Il tag non esiste." -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Non hai i permessi per aggiungere questo sprint a questo compito." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" "Non hai i permessi per aggiungere questa storia utente a questo compito." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Non hai i permessi per aggiungere questo stato a questo compito." @@ -3209,35 +3621,42 @@ msgstr "ordine del pannello dei compiti" msgid "is iocaine" msgstr "è sotto aspirina" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." -msgstr "" +msgstr "milestone id non valido." -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." -msgstr "" +msgstr "id stato attivita' non valido." -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." -msgstr "" +msgstr "id storia utente non valido." -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" +"id stato attivita' non valido. Lo stato deve appartenere allo stesso " +"progetto." -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" +"id storia utente non valido. La storia utente deve appartenere allo stesso " +"progetto." -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" +"id milestone non valido. La milestone deve appartenere allo stesso progetto." -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." msgstr "" +"id stati attivita' non validi. Tutte le attivita' devono appartenere allo " +"stesso progetto e, se esiste, allo stesso stato, storia utente e/o milestone." #: taiga/projects/templates/emails/membership_invitation-body-html.jinja:6 #: taiga/projects/templates/emails/membership_invitation-body-text.jinja:4 @@ -3452,13 +3871,15 @@ msgstr "" #: taiga/projects/templates/emails/transfer_accept-body-text.jinja:7 #, python-format msgid "%(new_owner_name)s says:" -msgstr "" +msgstr "%(new_owner_name)s dice:" #: taiga/projects/templates/emails/transfer_accept-body-text.jinja:11 msgid "" "\n" "From now on, your new status for this project will be \"admin\".\n" msgstr "" +"\n" +"Da ora in poi, il tuo nuovo stato per questo progetto sara' \"admin\".\n" #: taiga/projects/templates/emails/transfer_accept-body-text.jinja:16 #: taiga/projects/templates/emails/transfer_reject-body-text.jinja:19 @@ -3468,6 +3889,8 @@ msgid "" "\n" "The Taiga Team\n" msgstr "" +"\n" +"Il Team di Taiga\n" #: taiga/projects/templates/emails/transfer_accept-subject.jinja:1 #, python-format @@ -3475,6 +3898,9 @@ msgid "" "\n" "[%(project)s] Project ownership transfer offer accepted!\n" msgstr "" +"\n" +"[%(project)s] Offerta di trasferimento della proprieta' del progetto " +"accettata!\n" #: taiga/projects/templates/emails/transfer_reject-body-html.jinja:4 #, python-format @@ -3485,6 +3911,10 @@ msgid "" "new project owner for \"%(project_name)s\".

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

Ciao %(owner_name)s,

\n" +"

%(rejecter_name)s ha rifiutato la tua offerta e non diventera' il nuovo " +"proprietario di progetto di \"%(project_name)s\".

" #: taiga/projects/templates/emails/transfer_reject-body-html.jinja:10 #, python-format @@ -3493,6 +3923,8 @@ msgid "" "

%(rejecter_name)s says:

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

%(rejecter_name)s dice:

" #: taiga/projects/templates/emails/transfer_reject-body-html.jinja:16 msgid "" @@ -3501,11 +3933,14 @@ msgid "" "different person.

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

Se vuoi, puoi ancora provare a trasferire la proprieta' del progetto ad " +"un'altra persona.

" #: taiga/projects/templates/emails/transfer_reject-body-html.jinja:21 #: taiga/projects/templates/emails/transfer_reject-body-html.jinja:22 msgid "Request transfer to a different person" -msgstr "" +msgstr "Richiedi trasferimento ad un'altra persona" #: taiga/projects/templates/emails/transfer_reject-body-text.jinja:1 #, python-format @@ -3515,11 +3950,15 @@ msgid "" "%(rejecter_name)s has declined your offer and will not become the new " "project owner for \"%(project_name)s\".\n" msgstr "" +"\n" +"Ciao %(owner_name)s,\n" +"%(rejecter_name)s ha rifiutato la tua offerta e non diventera' il nuovo " +"proprietario di progetto di \"%(project_name)s\".\n" #: taiga/projects/templates/emails/transfer_reject-body-text.jinja:7 #, python-format msgid "%(rejecter_name)s says:" -msgstr "" +msgstr "%(rejecter_name)s dice:" #: taiga/projects/templates/emails/transfer_reject-body-text.jinja:11 msgid "" @@ -3527,10 +3966,14 @@ msgid "" "If you want, you can still try to transfer the project ownership to a " "different person.\n" msgstr "" +"\n" +"\n" +"

Se vuoi, puoi ancora provare a trasferire la proprieta' del progetto ad " +"un'altra persona.

\n" #: taiga/projects/templates/emails/transfer_reject-body-text.jinja:15 msgid "Request transfer to a different person:" -msgstr "" +msgstr "Richiedi trasferimento ad un'altra persona" #: taiga/projects/templates/emails/transfer_reject-subject.jinja:1 #, python-format @@ -3538,6 +3981,8 @@ msgid "" "\n" "[%(project)s] Project ownership transfer declined\n" msgstr "" +"\n" +"[%(project)s] Trasferimento della proprieta' del progetto rifiutato\n" #: taiga/projects/templates/emails/transfer_request-body-html.jinja:4 #, python-format @@ -3548,6 +3993,10 @@ msgid "" "\"%(project_name)s\".

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

Ciao %(owner_name)s,

\n" +"

%(requester_name)s ha chiesto di diventare il nuovo proprietario di " +"progetto di \"%(project_name)s\".

" #: taiga/projects/templates/emails/transfer_request-body-html.jinja:9 msgid "" @@ -3556,11 +4005,14 @@ msgid "" "project transfer from the administration panel.

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

Per favore, clicca su \"Continua\" se vuoi cominciare il trasferimento di " +"progetto dal pannello di amministrazione.

" #: taiga/projects/templates/emails/transfer_request-body-html.jinja:14 #: taiga/projects/templates/emails/transfer_start-body-html.jinja:22 msgid "Continue" -msgstr "" +msgstr "Continua" #: taiga/projects/templates/emails/transfer_request-body-text.jinja:1 #, python-format @@ -3570,6 +4022,10 @@ msgid "" "%(requester_name)s has requested to become the project owner for " "\"%(project_name)s\".\n" msgstr "" +"\n" +"Ciao %(owner_name)s,\n" +"%(requester_name)s ha richiesto di diventare il proprietario di progetto per " +"\"%(project_name)s\".\n" #: taiga/projects/templates/emails/transfer_request-body-text.jinja:6 msgid "" @@ -3577,10 +4033,13 @@ msgid "" "Please, go to your project settings if you would like to start the project " "transfer from the administration panel.\n" msgstr "" +"\n" +"Per favore, vai alle impostazioni del tuo progetto se vuoi cominciare il " +"trasferimento di progetto dal pannello di amministrazione.\n" #: taiga/projects/templates/emails/transfer_request-body-text.jinja:10 msgid "Go to your project settings:" -msgstr "" +msgstr "Vai alle tue impostazioni di progetto:" #: taiga/projects/templates/emails/transfer_request-subject.jinja:1 #, python-format @@ -3588,6 +4047,8 @@ msgid "" "\n" "[%(project)s] Project ownership transfer request\n" msgstr "" +"\n" +"[%(project)s] Richiesta di trasferimento della proprieta' del progetto\n" #: taiga/projects/templates/emails/transfer_start-body-html.jinja:4 #, python-format @@ -3598,6 +4059,10 @@ msgid "" "would like you to become the new project owner.

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

Ciao %(receiver_name)s,

\n" +"

%(owner_name)s, il proprietario attuale del progetto \"%(project_name)s\" " +"vorrebbe che tu diventassi il nuovo proprietario di progetto.

" #: taiga/projects/templates/emails/transfer_start-body-html.jinja:10 #, python-format @@ -3606,6 +4071,8 @@ msgid "" "

%(owner_name)s says:

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

%(owner_name)s dice:

" #: taiga/projects/templates/emails/transfer_start-body-html.jinja:17 msgid "" @@ -3614,6 +4081,9 @@ msgid "" "proposal.

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

Per favire, clicca su \"Continua\" per accettare o rifiutare questa " +"proposta.

" #: taiga/projects/templates/emails/transfer_start-body-text.jinja:1 #, python-format @@ -3623,11 +4093,15 @@ msgid "" "%(owner_name)s, the current project owner at \"%(project_name)s\" would like " "you to become the new project owner.\n" msgstr "" +"\n" +"Ciao %(receiver_name)s,\n" +"%(owner_name)s, il proprietario corrente del progetto \"%(project_name)s\" " +"vorrebbe diventare il nuovo proprietario di progetto.\n" #: taiga/projects/templates/emails/transfer_start-body-text.jinja:6 #, python-format msgid "%(owner_name)s says:" -msgstr "" +msgstr "%(owner_name)s dice:" #: taiga/projects/templates/emails/transfer_start-body-text.jinja:11 msgid "" @@ -3635,10 +4109,15 @@ msgid "" "Please, go to the following link to either accept or reject this proposal.\n" msgstr "" +"\n" +"Per favore, vai al seguente link per accettare o rifiutare questa proposta.\n" #: taiga/projects/templates/emails/transfer_start-body-text.jinja:15 msgid "Accept or reject the project ownership transfer:" msgstr "" +"Accetta o rifiuta la richiesta di trasferimento della proprieta' del " +"progetto:" #: taiga/projects/templates/emails/transfer_start-subject.jinja:1 #, python-format @@ -3646,6 +4125,8 @@ msgid "" "\n" "[%(project)s] Project ownership transfer offer\n" msgstr "" +"\n" +"[%(project)s] Offerta di trasferimento della proprieta' del progetto\n" #. Translators: Name of scrum project template. #: taiga/projects/translations.py:30 @@ -3890,38 +4371,30 @@ msgstr "Product Owner" msgid "Stakeholder" msgstr "Stakeholder" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Non hai i permessi per aggiungere questo sprint a questa storia utente." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "Non hai i permessi per aggiungere questo stato a questa storia utente." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" -msgstr "" +msgstr "id ruolo '{role_id}' non valido" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" -msgstr "" +msgstr "id punti '{points_id}' non valido" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Stiamo generando la storia utente #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "ruolo" @@ -3936,7 +4409,7 @@ msgstr "ordine dello sprint" #: taiga/projects/userstories/models.py:84 msgid "kanban order" -msgstr "" +msgstr "ordine kanban" #: taiga/projects/userstories/models.py:92 msgid "finish date" @@ -3950,87 +4423,91 @@ msgstr "generato da un problema" msgid "There's no user story with that id" msgstr "Non c'è nessuna storia utente con questo ID" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Non c'è nessuno progetto con questo ID" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "L'indirizzo email è già usato" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Ruolo di progetto non valido" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Opzioni predefinite" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Stati della storia utente" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Punti" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Stati del compito" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Stati del problema" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Tipologie del problema" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Priorità" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Criticità" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Ruoli" @@ -4059,7 +4536,7 @@ msgstr "ultima modificatore" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Controlla le API della storie per la differenza esatta" @@ -4099,54 +4576,54 @@ msgstr "" msgid "Important dates" msgstr "Date importanti" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "E-mail duplicata" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "E-mail non valida" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Username o e-mail non validi" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Mail inviata con successo!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "E' necessario il parametro della password corrente" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "E' necessario il parametro della nuovo password" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Lunghezza della password non valida, sono necessari almeno 6 caratteri" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Password corrente non valida" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Non valido. Sei sicuro che il token sia corretto e che tu non l'abbia già " "usato in precedenza?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Non valido. Sicuro che il token sia corretto?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "Stato del super-utente" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -4154,26 +4631,26 @@ msgstr "" "Definisce che questo utente ha tutti i permessi senza assegnarglieli " "esplicitamente." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "nome utente" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" "Richiede 30 caratteri o meno. Deve comprendere: lettere, numeri e caratteri " "come /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Inserisci un nome utente valido." -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "attivo" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -4181,59 +4658,59 @@ msgstr "" "Definisce se questo utente debba essere trattato come attivo. Deseleziona " "questo invece di eliminare gli account." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografia" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "fotografia" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "data di inizio partecipazione" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "lingua predefinita" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "tema predefinito" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "timezone predefinita" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "colora i tag" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "token e-mail" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "nuovo indirizzo e-mail" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "permessi" diff --git a/taiga/locale/ja/LC_MESSAGES/django.po b/taiga/locale/ja/LC_MESSAGES/django.po new file mode 100644 index 00000000..5e26385d --- /dev/null +++ b/taiga/locale/ja/LC_MESSAGES/django.po @@ -0,0 +1,4224 @@ +# taiga-back.taiga. +# Copyright (C) 2014-2017 Taiga Dev Team +# This file is distributed under the same license as the taiga-back package. +# +# Translators: +# Akihiro YAGASAKI , 2016 +# Masaharu KAMIMURA , 2016 +# Nikita K , 2016 +# Shun Yanaura , 2016 +# Suguru Sato , 2016 +# Tomonori Tanabe , 2015 +msgid "" +msgstr "" +"Project-Id-Version: taiga-back\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" +"Last-Translator: Taiga Dev Team \n" +"Language-Team: Japanese (http://www.transifex.com/taiga-agile-llc/taiga-back/" +"language/ja/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ja\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: taiga/auth/api.py:74 +msgid "Public register is disabled." +msgstr "パブリックなレジスタは無効です。" + +#: taiga/auth/api.py:101 +msgid "invalid register type" +msgstr "このレジスタタイプは無効です。" + +#: taiga/auth/api.py:117 +msgid "invalid login type" +msgstr "このログインタイプは無効です。" + +#: taiga/auth/services.py:76 +msgid "Username is already in use." +msgstr "Username は既に使用されています." + +#: taiga/auth/services.py:79 +msgid "Email is already in use." +msgstr "Email はすでに使用されています." + +#: taiga/auth/services.py:95 +msgid "Token not matches any valid invitation." +msgstr "存在しないトークンです。" + +#: taiga/auth/services.py:123 +msgid "User is already registered." +msgstr "User は既に登録されています." + +#: taiga/auth/services.py:140 +msgid "This user is already a member of the project." +msgstr "このユーザーは既にプロジェクトに追加されています." + +#: taiga/auth/services.py:164 +msgid "Error on creating new user." +msgstr "新しいユーザを作成中にエラーが発生しました。" + +#: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 +msgid "Invalid token" +msgstr "トークンが間違っています" + +#: taiga/auth/validators.py:37 taiga/users/validators.py:44 +msgid "invalid username" +msgstr "username が間違っています" + +#: taiga/auth/validators.py:42 taiga/users/validators.py:50 +msgid "" +"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'" +msgstr "必須です. 255文字以下. 半角英数字,記号 /./-/_ が使用できます." + +#: taiga/base/api/fields.py:294 +msgid "This field is required." +msgstr "このフィールドは必須です." + +#: taiga/base/api/fields.py:295 taiga/base/api/relations.py:337 +msgid "Invalid value." +msgstr "値が正しくありません." + +#: taiga/base/api/fields.py:484 +#, python-format +msgid "'%s' value must be either True or False." +msgstr "'%s' は True または False である必要があります." + +#: taiga/base/api/fields.py:549 +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "" +"英数文字、アンダースコア、ハイフンを含む正しいスラグを入力してください。" + +#: taiga/base/api/fields.py:564 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" +"有効な選択肢を選択してください。%(value)sは選択できる選択肢ではありません。" + +#: taiga/base/api/fields.py:638 +msgid "You email domain is not allowed" +msgstr "そのメールドメインは許可されていません。" + +#: taiga/base/api/fields.py:647 +msgid "Enter a valid email address." +msgstr "正しいメールアドレスを入力してください." + +#: taiga/base/api/fields.py:689 +#, python-format +msgid "Date has wrong format. Use one of these formats instead: %s" +msgstr "" +"日付のフォーマットが間違っています。このフォーマットを利用してください。%s" + +#: taiga/base/api/fields.py:753 +#, python-format +msgid "Datetime has wrong format. Use one of these formats instead: %s" +msgstr "" +"日時のフォーマットが間違っています。このフォーマットを利用してください。%s" + +#: taiga/base/api/fields.py:823 +#, python-format +msgid "Time has wrong format. Use one of these formats instead: %s" +msgstr "" +"時刻のフォーマットが間違っています。このフォーマットを利用してください。%s" + +#: taiga/base/api/fields.py:880 +msgid "Enter a whole number." +msgstr "整数を入力してください。" + +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "値がこの値以下であることを確認してください。%(limit_value)s." + +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "値がこの値以上であることを確認してください。%(limit_value)s." + +#: taiga/base/api/fields.py:912 +#, python-format +msgid "\"%s\" value must be a float." +msgstr "\"%s\"は小数である必要があります。" + +#: taiga/base/api/fields.py:933 +msgid "Enter a number." +msgstr "数字を入力してください。" + +#: taiga/base/api/fields.py:936 +#, python-format +msgid "Ensure that there are no more than %s digits in total." +msgstr "桁数が%s以下であることを確認してください。" + +#: taiga/base/api/fields.py:937 +#, python-format +msgid "Ensure that there are no more than %s decimal places." +msgstr "小数点以下の桁数は%sより少ないことを確認してください。" + +#: taiga/base/api/fields.py:938 +#, python-format +msgid "Ensure that there are no more than %s digits before the decimal point." +msgstr "整数部分の桁数は%s以下であることを確認してください。" + +#: taiga/base/api/fields.py:1005 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "ファイルが送信されませんでした. フォームのエンコードを確認して下さい." + +#: taiga/base/api/fields.py:1006 +msgid "No file was submitted." +msgstr "ファイルが送信されませんでした." + +#: taiga/base/api/fields.py:1007 +msgid "The submitted file is empty." +msgstr "送信されたファイルは空です." + +#: taiga/base/api/fields.py:1008 +#, python-format +msgid "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr "" +"ファイル名の文字数は%(max)d以下であることを確認してください。現在は%(length)d" +"字です。" + +#: taiga/base/api/fields.py:1009 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" +"ファイルをサブミットするか、クリアチェックボックスを入れるか、どちらかだけし" +"てください。" + +#: taiga/base/api/fields.py:1049 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"正しい画像をアップロードしてください。アップロードしたファイルは画像じゃない" +"か破損しています。" + +#: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 +msgid "Blocked element" +msgstr "ブロックされた要素" + +#: taiga/base/api/pagination.py:228 +msgid "Page is not 'last', nor can it be converted to an int." +msgstr "ページは「最後」じゃないか、整数に変換することができません。" + +#: taiga/base/api/pagination.py:232 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "このページは無効です。(%(page_number)s): %(message)s" + +#: taiga/base/api/permissions.py:65 +msgid "Invalid permission definition." +msgstr "許可定義は無効です。" + +#: taiga/base/api/relations.py:247 +#, python-format +msgid "Invalid pk '%s' - object does not exist." +msgstr "pk'%s'は無効です。オブジェクトが存在しません。" + +#: taiga/base/api/relations.py:248 +#, python-format +msgid "Incorrect type. Expected pk value, received %s." +msgstr "" +"タイプが正しくありません。予想したタイプはpk、受け取ったタイプは%sです。" + +#: taiga/base/api/relations.py:336 +#, python-format +msgid "Object with %s=%s does not exist." +msgstr "%s=%sであるオブジェクトは存在しません。" + +#: taiga/base/api/relations.py:372 +msgid "Invalid hyperlink - No URL match" +msgstr "URLがマッチしないためハイパーリンクは無効です。" + +#: taiga/base/api/relations.py:373 +msgid "Invalid hyperlink - Incorrect URL match" +msgstr "URLのマッチが正しくないためハイパーリンクは無効です。" + +#: taiga/base/api/relations.py:374 +msgid "Invalid hyperlink due to configuration error" +msgstr "設定エラーのためハイパーリンクは無効です。" + +#: taiga/base/api/relations.py:375 +msgid "Invalid hyperlink - object does not exist." +msgstr "オブジェクトが存在しないためハイパーリンクは無効です。" + +#: taiga/base/api/relations.py:376 +#, python-format +msgid "Incorrect type. Expected url string, received %s." +msgstr "" +"タイプが正しくありません。予想したタイプはURLの配列、受け取ったタイプは%sで" +"す。" + +#: taiga/base/api/serializers.py:324 +msgid "Invalid data" +msgstr "無効なデータ" + +#: taiga/base/api/serializers.py:416 +msgid "No input provided" +msgstr "未入力" + +#: taiga/base/api/serializers.py:579 +msgid "Cannot create a new item, only existing items may be updated." +msgstr "新しいアイテムは作成できません。既存アイテムを更新してください。" + +#: taiga/base/api/serializers.py:590 +msgid "Expected a list of items." +msgstr "アイテムの配列を予想していました。" + +#: taiga/base/api/views.py:126 +msgid "Not found" +msgstr "見つかりませんでした。" + +#: taiga/base/api/views.py:129 +msgid "Permission denied" +msgstr "権限がありません。" + +#: taiga/base/api/views.py:492 +msgid "Server application error" +msgstr "サーバーアプリケーションのエラー" + +#: taiga/base/connectors/exceptions.py:26 +msgid "Connection error." +msgstr "接続エラー" + +#: taiga/base/exceptions.py:79 +msgid "Malformed request." +msgstr "不正なリクエスト" + +#: taiga/base/exceptions.py:84 +msgid "Incorrect authentication credentials." +msgstr "認証情報が正しくありません。" + +#: taiga/base/exceptions.py:89 +msgid "Authentication credentials were not provided." +msgstr "認証情報が未入力です。" + +#: taiga/base/exceptions.py:94 +msgid "You do not have permission to perform this action." +msgstr "この動作を行う権限がありません。" + +#: taiga/base/exceptions.py:99 +#, python-format +msgid "Method '%s' not allowed." +msgstr "'%s'メソッドが許可されていません。" + +#: taiga/base/exceptions.py:107 +msgid "Could not satisfy the request's Accept header" +msgstr "リクエストのAcceptヘッダーを満たすことができませんでした。" + +#: taiga/base/exceptions.py:116 +#, python-format +msgid "Unsupported media type '%s' in request." +msgstr "リクエストのメディアタイプ'%s'に対応していません。" + +#: taiga/base/exceptions.py:124 +msgid "Request was throttled." +msgstr "リクエストはスロットルされました。" + +#: taiga/base/exceptions.py:125 +#, python-format +msgid "Expected available in %d second%s." +msgstr "%d %s秒後に利用可能になる見込みです。" + +#: taiga/base/exceptions.py:139 +msgid "Unexpected error" +msgstr "予期せぬエラー" + +#: taiga/base/exceptions.py:151 +msgid "Not found." +msgstr "見つかりませんでした。" + +#: taiga/base/exceptions.py:156 +msgid "Method not supported for this endpoint." +msgstr "このエンドポイントはこのメソッドに対応していません。" + +#: taiga/base/exceptions.py:164 taiga/base/exceptions.py:172 +msgid "Wrong arguments." +msgstr "引数の数が正しくありません。" + +#: taiga/base/exceptions.py:176 +msgid "Data validation error" +msgstr "データのバリデーションエラー" + +#: taiga/base/exceptions.py:188 +msgid "Integrity Error for wrong or invalid arguments" +msgstr "引数が不正か無効による整合性エラー" + +#: taiga/base/exceptions.py:195 +msgid "Precondition error" +msgstr "前提条件エラー" + +#: taiga/base/exceptions.py:219 +msgid "No room left for more projects." +msgstr "新しいプロジェクトを作成するスペースがありません。" + +#: taiga/base/filters.py:81 taiga/base/filters.py:463 +msgid "Error in filter params types." +msgstr "パラメータタイプのフィルターエラー" + +#: taiga/base/filters.py:136 taiga/base/filters.py:243 +#: taiga/projects/filters.py:64 +msgid "'project' must be an integer value." +msgstr "'project'は整数である必要があります。" + +#: taiga/base/templates/emails/base-body-html.jinja:6 +msgid "Taiga" +msgstr "Taiga" + +#: taiga/base/templates/emails/base-body-html.jinja:421 +#: taiga/base/templates/emails/hero-body-html.jinja:380 +#: taiga/base/templates/emails/updates-body-html.jinja:442 +msgid "Follow us on Twitter" +msgstr "Twitterをフォローする" + +#: taiga/base/templates/emails/base-body-html.jinja:421 +#: taiga/base/templates/emails/hero-body-html.jinja:380 +#: taiga/base/templates/emails/updates-body-html.jinja:442 +msgid "Twitter" +msgstr "Twitter" + +#: taiga/base/templates/emails/base-body-html.jinja:422 +#: taiga/base/templates/emails/hero-body-html.jinja:381 +#: taiga/base/templates/emails/updates-body-html.jinja:443 +msgid "Get the code on GitHub" +msgstr "GitHubからコードを入手する" + +#: taiga/base/templates/emails/base-body-html.jinja:422 +#: taiga/base/templates/emails/hero-body-html.jinja:381 +#: taiga/base/templates/emails/updates-body-html.jinja:443 +msgid "GitHub" +msgstr "Github" + +#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/hero-body-html.jinja:382 +#: taiga/base/templates/emails/updates-body-html.jinja:444 +msgid "Visit our website" +msgstr "ホームページに移動" + +#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/hero-body-html.jinja:382 +#: taiga/base/templates/emails/updates-body-html.jinja:444 +msgid "Taiga.io" +msgstr "Taiga.io" + +#: taiga/base/templates/emails/base-body-html.jinja:438 +#: taiga/base/templates/emails/hero-body-html.jinja:397 +#: taiga/base/templates/emails/updates-body-html.jinja:459 +#, python-format +msgid "" +"\n" +" Taiga Support:\n" +" %(support_url)s\n" +"
\n" +" Contact us:\n" +" \n" +" %(support_email)s\n" +" \n" +"
\n" +" Mailing list:\n" +" \n" +" %(mailing_list_url)s\n" +" \n" +" " +msgstr "" +"\n" +"Taigaサポート\n" +"%(support_url)s\n" +"
\n" +"問い合わせ\n" +"\n" +"%(support_email)s\n" +"\n" +"
\n" +"メールリスト\n" +"\n" +"%(mailing_list_url)s\n" +"" + +#: taiga/base/templates/emails/hero-body-html.jinja:6 +msgid "You have been Taigatized" +msgstr "あなたはTaiga化されました" + +#: taiga/base/templates/emails/hero-body-html.jinja:359 +msgid "" +"\n" +"

You have been Taigatized!" +"

\n" +"

Welcome to Taiga, an Open " +"Source, Agile Project Management Tool

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

あなたはTaiga化されまし" +"た。

\n" +"

Taigaへようこそ!Taigaは" +"オープンソースでアジャイルなプロジェクトマネジメントツールです。

\n" +" " + +#: taiga/base/templates/emails/updates-body-html.jinja:6 +msgid "[Taiga] Updates" +msgstr "[Taiga] Updates" + +#: taiga/base/templates/emails/updates-body-html.jinja:417 +msgid "Updates" +msgstr "Updates" + +#: taiga/base/templates/emails/updates-body-html.jinja:423 +#, python-format +msgid "" +"\n" +"

comment:" +"

\n" +"

" +"%(comment)s

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

コメント:

\n" +"

%(comment)s

" + +#: taiga/base/templates/emails/updates-body-text.jinja:6 +#, python-format +msgid "" +"\n" +" Comment: %(comment)s\n" +" " +msgstr "" +"\n" +"コメント: %(comment)s" + +#: taiga/export_import/api.py:127 +msgid "We needed at least one role" +msgstr "少なくとも1つの役割が必要です." + +#: taiga/export_import/api.py:323 +msgid "Needed dump file" +msgstr "ダンプファイルが必要です." + +#: taiga/export_import/api.py:333 +msgid "Invalid dump format" +msgstr "ダンプフォーマットが正しくありません" + +#: taiga/export_import/services/store.py:718 +#: taiga/export_import/services/store.py:736 +msgid "error importing project data" +msgstr "プロジェクトデータのインポートエラー" + +#: taiga/export_import/services/store.py:743 +msgid "error importing roles" +msgstr "役割のインポートエラー" + +#: taiga/export_import/services/store.py:748 +msgid "error importing memberships" +msgstr "メンバーシップのインポートエラー" + +#: taiga/export_import/services/store.py:759 +msgid "error importing lists of project attributes" +msgstr "プロジェクトアトリビュートリストのインポートエラー" + +#: taiga/export_import/services/store.py:763 +msgid "error importing default project attributes values" +msgstr "デフォルトプロジェクトアトリビュートのインポートエラー" + +#: taiga/export_import/services/store.py:774 +msgid "error importing custom attributes" +msgstr "カスタムアトリビュートのインポートエラー" + +#: taiga/export_import/services/store.py:778 +msgid "error importing sprints" +msgstr "スプリントのインポートエラー" + +#: taiga/export_import/services/store.py:782 +msgid "error importing issues" +msgstr "チケットのインポートエラー" + +#: taiga/export_import/services/store.py:786 +msgid "error importing user stories" +msgstr "ユーザストーリのインポートエラー" + +#: taiga/export_import/services/store.py:790 +msgid "error importing epics" +msgstr "エピックのインポートエラー" + +#: taiga/export_import/services/store.py:794 +msgid "error importing tasks" +msgstr "タスクのインポートエラー" + +#: taiga/export_import/services/store.py:798 +msgid "error importing wiki pages" +msgstr "Wikiページのインポートエラー" + +#: taiga/export_import/services/store.py:802 +msgid "error importing wiki links" +msgstr "Wikiリンクのインポートエラー" + +#: taiga/export_import/services/store.py:806 +msgid "error importing tags" +msgstr "タグのインポートエラー" + +#: taiga/export_import/services/store.py:810 +msgid "error importing timelines" +msgstr "タイムラインのインポートエラー" + +#: taiga/export_import/services/store.py:832 +msgid "unexpected error importing project" +msgstr "プロジェクトをインポート中に予期せぬエラー" + +#: taiga/export_import/tasks.py:62 taiga/export_import/tasks.py:63 +msgid "Error generating project dump" +msgstr "プロジェクトダンプの生成エラー" + +#: taiga/export_import/tasks.py:91 +#, python-brace-format +msgid "" +"\n" +"\n" +"Error loading dump by {user_full_name} <{user_email}>:\"\n" +"\n" +"\n" +"REASON:\n" +"-------\n" +"{reason}\n" +"\n" +"DETAILS:\n" +"--------\n" +"{details}\n" +"\n" +"TRACE ERROR:\n" +"------------" +msgstr "" +"\n" +"\n" +"ユーザー{user_full_name} <{user_email}>のダンプのロードエラー\"\n" +"\n" +"\n" +"原因\n" +"-------\n" +"{reason}\n" +"\n" +"詳細:\n" +"--------\n" +"{details}\n" +"\n" +"エラートレース\n" +"------------" + +#: taiga/export_import/tasks.py:120 +msgid "Error loading project dump" +msgstr "プロジェクトダンプのロードエラー" + +#: taiga/export_import/tasks.py:121 +msgid "Error loading your project dump file" +msgstr "プロジェクトダンプのロードエラー" + +#: taiga/export_import/tasks.py:135 +msgid " -- no detail info --" +msgstr "詳細な情報がありません" + +#: taiga/export_import/templates/emails/dump_project-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Project dump generated

\n" +"

Hello %(user)s,

\n" +"

Your dump from project %(project)s has been correctly generated.\n" +"

You can download it here:

\n" +" Download the dump file\n" +"

This file will be deleted on %(deletion_date)s.

\n" +"

The Taiga Team

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

プロジェクトダンプを生成しました

\n" +"

こんにちは、%(user)s。

\n" +"

プロジェクト%(project)sのダンプが正常に生成されました。

\n" +"

以下リンクからダウンロードできます。

\n" +" ダンプファイルをダウンロード\n" +"

このファイルは%(deletion_date)sに削除されます。

\n" +"

Taigaチーム

\n" +" " + +#: taiga/export_import/templates/emails/dump_project-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your dump from project %(project)s has been correctly generated. You can " +"download it here:\n" +"\n" +"%(url)s\n" +"\n" +"This file will be deleted on %(deletion_date)s.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"こんにちは、%(user)s。\n" +"\n" +"プロジェクト%(project)sのダンプが正常に生成されました。以下リンクからダウン" +"ロードできます。\n" +"\n" +"%(url)s\n" +"\n" +"このファイルは%(deletion_date)sに削除されます。\n" +"\n" +"---\n" +"Taigaチーム\n" + +#: taiga/export_import/templates/emails/dump_project-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your project dump has been generated" +msgstr "[%(project)s]プロジェクトのダンプが生成されました。" + +#: taiga/export_import/templates/emails/export_error-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

%(error_message)s

\n" +"

Hello %(user)s,

\n" +"

Your project %(project)s has not been exported correctly.

\n" +"

The Taiga system administrators have been informed.
Please, try " +"it again or contact with the support team at\n" +" %(support_email)s

\n" +"

The Taiga Team

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

%(error_message)s

\n" +"

こんにちは、%(user)s。

\n" +"

プロジェクト%(project)sが正常にエクスポートできませんでした。

\n" +"

Taigaシステム管理者にお知らせしました。
もう一度試してみるかサポー" +"トチームにお問い合せください。問い合わせ先は、\n" +" %(support_email)sです。

\n" +"

Taigaチーム

\n" +" " + +#: taiga/export_import/templates/emails/export_error-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"%(error_message)s\n" +"Your project %(project)s has not been exported correctly.\n" +"\n" +"The Taiga system administrators have been informed.\n" +"\n" +"Please, try it again or contact with the support team at %(support_email)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"こんにちは、%(user)s。\n" +"\n" +"%(error_message)s\n" +"プロジェクト%(project)sは正常にエクスポートできませんでした。\n" +"\n" +"Taigaシステム管理者にお知らせしました。\n" +"\n" +"もう一度試してみるかサポートチームにお問い合せください。問い合わせ先は、 " +"%(support_email)sです。\n" +"\n" +"---\n" +"Taigaチーム\n" + +#: taiga/export_import/templates/emails/export_error-subject.jinja:1 +#, python-format +msgid "[%(project)s] %(error_subject)s" +msgstr "[%(project)s] %(error_subject)s" + +#: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

%(error_message)s

\n" +"

Hello %(user)s,

\n" +"

Your project has not been importer correctly.

\n" +"

The Taiga system administrators have been informed.
Please, try " +"it again or contact with the support team at\n" +" %(support_email)s

\n" +"

The Taiga Team

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

%(error_message)s

\n" +"

こんにちは、%(user)s。

\n" +"

プロジェクトが正常にインポートできませんでした。

\n" +"

Taigaシステム管理者にお知らせしました。
もう一度試してみるかサ" +"ポートチームにお問い合せください。問い合わせ先は、\n" +" %(support_email)sです。

\n" +"

Taigaチーム

\n" +" " + +#: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"%(error_message)s\n" +"\n" +"Your project has not been importer correctly.\n" +"\n" +"The Taiga system administrators have been informed.\n" +"\n" +"Please, try it again or contact with the support team at %(support_email)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"こんにちは、%(user)s。\n" +"\n" +"%(error_message)s\n" +"\n" +"プロジェクトが正常にインポートできませんでした。\n" +"\n" +"Taigaシステム管理者にお知らせしました。\n" +"\n" +"もう一度試してみるかサポートチームにお問い合せください。問い合わせ先" +"は、%(support_email)sです。\n" +"\n" +"---\n" +"Taigaチーム\n" + +#: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 +#, python-format +msgid "[Taiga] %(error_subject)s" +msgstr "[Taiga] %(error_subject)s" + +#: taiga/export_import/templates/emails/load_dump-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Project dump imported

\n" +"

Hello %(user)s,

\n" +"

Your project dump has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

プロジェクトダンプがインポートされました。

\n" +"

こんにちは、%(user)s。

\n" +"

プロジェクトダンプが正常にインポートされました。

\n" +" " +"プロジェクト%(project)sへ\n" +"

Taigaチーム

\n" +" " + +#: taiga/export_import/templates/emails/load_dump-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your project dump has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"こんにちは、%(user)s。\n" +"\n" +"プロジェクトダンプが正常にインポートされました。\n" +"\n" +"プロジェクト%(project)sを以下リンクからアクセスでいます。\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"Taigaチーム\n" + +#: taiga/export_import/templates/emails/load_dump-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your project dump has been imported" +msgstr "[%(project)s] プロジェクトダンプがインポートされました。" + +#: taiga/export_import/validators/fields.py:144 +msgid "{}=\"{}\" not found in this project" +msgstr "{}=\"{}\"がプロジェクトに見つかりませんでした。" + +#: taiga/export_import/validators/validators.py:150 +#: taiga/projects/custom_attributes/validators.py:109 +msgid "Invalid content. It must be {\"key\": \"value\",...}" +msgstr "コンテントが無効です。正しい形式は {\"key\": \"value\",...}" + +#: taiga/export_import/validators/validators.py:165 +#: taiga/projects/custom_attributes/validators.py:124 +msgid "It contain invalid custom fields." +msgstr "無効なカスタムフィールドが含まれています。" + +#: taiga/export_import/validators/validators.py:245 +#: taiga/projects/validators.py:54 +msgid "Name duplicated for the project" +msgstr "同じプロジェクト名があります。" + +#: taiga/external_apps/api.py:43 taiga/external_apps/api.py:70 +#: taiga/external_apps/api.py:77 +msgid "Authentication required" +msgstr "認証が必要です。" + +#: taiga/external_apps/models.py:35 +#: taiga/projects/custom_attributes/models.py:36 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 +msgid "name" +msgstr "名前" + +#: taiga/external_apps/models.py:37 +msgid "Icon url" +msgstr "アイコンのURL" + +#: taiga/external_apps/models.py:38 +msgid "web" +msgstr "ウェブ" + +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 +#: taiga/projects/custom_attributes/models.py:37 +#: taiga/projects/epics/models.py:56 +#: taiga/projects/history/templatetags/functions.py:25 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 +#: taiga/projects/userstories/models.py:95 +msgid "description" +msgstr "説明" + +#: taiga/external_apps/models.py:41 +msgid "Next url" +msgstr "次のURL" + +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 +msgid "user" +msgstr "ユーザー" + +#: taiga/external_apps/models.py:59 +msgid "application" +msgstr "アプリケーション" + +#: taiga/feedback/models.py:25 taiga/users/models.py:140 +msgid "full name" +msgstr "フルネーム" + +#: taiga/feedback/models.py:27 taiga/users/models.py:135 +msgid "email address" +msgstr "メールアドレス" + +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 +msgid "comment" +msgstr "コメント" + +#: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 +#: taiga/projects/custom_attributes/models.py:46 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 +#: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 +#: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 +#: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 +msgid "created date" +msgstr "作成日時" + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Feedback

\n" +"

Taiga has received feedback from %(full_name)s <%(email)s>

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

フィードバック

\n" +"

Taigaが%(full_name)s <%(email)s>からフィードバックを受け付けました。\n" +" " + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:9 +#, python-format +msgid "" +"\n" +"

Comment

\n" +"

%(comment)s

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

コメント

\n" +"

%(comment)s

\n" +" " + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 +#: taiga/projects/admin.py:106 taiga/users/admin.py:120 +msgid "Extra info" +msgstr "追加情報" + +#: taiga/feedback/templates/emails/feedback_notification-body-text.jinja:1 +#, python-format +msgid "" +"---------\n" +"- From: %(full_name)s <%(email)s>\n" +"---------\n" +"- Comment:\n" +"%(comment)s\n" +"---------" +msgstr "" +"---------\n" +"- 差出人 %(full_name)s <%(email)s>\n" +"---------\n" +"- コメント\n" +"%(comment)s\n" +"---------" + +#: taiga/feedback/templates/emails/feedback_notification-body-text.jinja:8 +msgid "- Extra info:" +msgstr "- 追加情報" + +#: taiga/feedback/templates/emails/feedback_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Feedback from %(full_name)s <%(email)s>\n" +msgstr "" +"\n" +"[Taiga] %(full_name)sさんからのフィードバック <%(email)s>\n" + +#: taiga/hooks/api.py:54 +msgid "The payload is not a valid json" +msgstr "ペイロードは有効なjsonではありません。" + +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 +msgid "The project doesn't exist" +msgstr "プロジェクトは存在していません。" + +#: taiga/hooks/api.py:66 +msgid "Bad signature" +msgstr "無効なシグネチャー" + +#: taiga/hooks/event_hooks.py:66 +#, python-brace-format +msgid "" +"[@{user_name}]({user_url} \"See @{user_name}'s {platform} profile\") says in " +"[{platform}#{number}]({comment_url} \"Go to comment\"):\n" +"\n" +"\"{comment_message}\"" +msgstr "" +"[@{user_name}]({user_url} \"@{user_name}さんの{platform}プロフィール\")が" +"[{platform}#{number}]({comment_url}でコメントしました。 \"コメントへ移動" +"\"):\n" +"\n" +"\"{comment_message}\"" + +#: taiga/hooks/event_hooks.py:71 +#, python-brace-format +msgid "" +"Comment From {platform}:\n" +"\n" +"> {comment_message}" +msgstr "" +"{platform}からのコメント\n" +"\n" +"> {comment_message}" + +#: taiga/hooks/event_hooks.py:84 +msgid "Invalid issue comment information" +msgstr "イッシューコメントの情報は無効です。" + +#: taiga/hooks/event_hooks.py:103 +#, python-brace-format +msgid "" +"Issue created by [@{user_name}]({user_url} \"See @{user_name}'s {platform} " +"profile\") from [{platform}#{number}]({url} \"Go to issue\")." +msgstr "" +"[@{user_name}]({user_url}がc\"@{user_name}さんの{platform}プロフィールを見る" +"\")[{platform}#{number}]からイッシューを作成しました。({url} \"イッシューへ移" +"動\")." + +#: taiga/hooks/event_hooks.py:107 +#, python-brace-format +msgid "Issue created from {platform}." +msgstr "{platform}からイッシューが作成されました。" + +#: taiga/hooks/event_hooks.py:120 +msgid "Invalid issue information" +msgstr "イッシュー情報は無効です。" + +#: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 +msgid "unknown user" +msgstr "無効なユーザー" + +#: taiga/hooks/event_hooks.py:156 +#, python-brace-format +msgid "" +"{user_text} changed the status from [{platform} commit]({commit_url} \"See " +"commit '{commit_id} - {commit_message}'\")\n" +"\n" +" - Status: **{src_status}** → **{dst_status}**" +msgstr "" +"{user_text}さんが[{platform} commit]({commit_url}ステータスを更新しまし" +"た。\"コミットを参照する'{commit_id} - {commit_message}'\")\n" +"\n" +" - ステータス**{src_status}** → **{dst_status}**" + +#: taiga/hooks/event_hooks.py:161 +#, python-brace-format +msgid "" +"Changed status from {platform} commit.\n" +"\n" +" - Status: **{src_status}** → **{dst_status}**" +msgstr "" +"コミット{platform}更新しました。\n" +"\n" +" - ステータス: **{src_status}** → **{dst_status}**" + +#: taiga/hooks/event_hooks.py:179 +#, python-brace-format +msgid "" +"This {type_name} has been mentioned by {user_text} in the [{platform} commit]" +"({commit_url} \"See commit '{commit_id} - {commit_message}'\") " +"\"{commit_message}\"" +msgstr "" +"{type_name}は{user_text}さんによって[{platform} commit]({commit_url}でメン" +"ションされました。 \"コミット'{commit_id} - {commit_message}'を参照する\") " +"\"{commit_message}\"" + +#: taiga/hooks/event_hooks.py:184 +#, python-brace-format +msgid "" +"This issue has been mentioned in the {platform} commit \"{commit_message}\"" +msgstr "" +"このイッシューは{platform}コミット内でメンションされまし" +"た。\"{commit_message}\"" + +#: taiga/hooks/event_hooks.py:206 +msgid "The referenced element doesn't exist" +msgstr "参照された要素は存在しません。" + +#: taiga/hooks/event_hooks.py:222 +msgid "The status doesn't exist" +msgstr "ステータスは存在しません。" + +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + +#: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 +msgid "View project" +msgstr "プロジェクトを見る" + +#: taiga/permissions/choices.py:24 taiga/permissions/choices.py:36 +msgid "View milestones" +msgstr "マイルストーンを見る" + +#: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 +msgid "View epic" +msgstr "エピックを見る" + +#: taiga/permissions/choices.py:26 +msgid "View user stories" +msgstr "ユーザストーリを見る" + +#: taiga/permissions/choices.py:27 taiga/permissions/choices.py:53 +msgid "View tasks" +msgstr "タスクを見る" + +#: taiga/permissions/choices.py:28 taiga/permissions/choices.py:59 +msgid "View issues" +msgstr "課題 を表示" + +#: taiga/permissions/choices.py:29 taiga/permissions/choices.py:65 +msgid "View wiki pages" +msgstr "ウィキページを見る" + +#: taiga/permissions/choices.py:30 taiga/permissions/choices.py:71 +msgid "View wiki links" +msgstr "ウィキリンクを見る" + +#: taiga/permissions/choices.py:37 +msgid "Add milestone" +msgstr "マイルストーンを追加する" + +#: taiga/permissions/choices.py:38 +msgid "Modify milestone" +msgstr "マイルストーンを変更する" + +#: taiga/permissions/choices.py:39 +msgid "Delete milestone" +msgstr "マイルストーンを削除する" + +#: taiga/permissions/choices.py:42 +msgid "Add epic" +msgstr "エピックを追加する" + +#: taiga/permissions/choices.py:43 +msgid "Modify epic" +msgstr "エピックを変更する" + +#: taiga/permissions/choices.py:44 +msgid "Comment epic" +msgstr "エピックをコメントする" + +#: taiga/permissions/choices.py:45 +msgid "Delete epic" +msgstr "エピックを削除する" + +#: taiga/permissions/choices.py:47 +msgid "View user story" +msgstr "ユーザストーリを見る" + +#: taiga/permissions/choices.py:48 +msgid "Add user story" +msgstr "ユーザストーリを追加する" + +#: taiga/permissions/choices.py:49 +msgid "Modify user story" +msgstr "ユーザストーリを変更する" + +#: taiga/permissions/choices.py:50 +msgid "Comment user story" +msgstr "ユーザストーリをコメントする" + +#: taiga/permissions/choices.py:51 +msgid "Delete user story" +msgstr "ユーザストーリを削除する" + +#: taiga/permissions/choices.py:54 +msgid "Add task" +msgstr "タスクを追加する" + +#: taiga/permissions/choices.py:55 +msgid "Modify task" +msgstr "タスクを変更する" + +#: taiga/permissions/choices.py:56 +msgid "Comment task" +msgstr "タスクをコメントする" + +#: taiga/permissions/choices.py:57 +msgid "Delete task" +msgstr "タスクを削除する" + +#: taiga/permissions/choices.py:60 +msgid "Add issue" +msgstr "イッシューを追加する" + +#: taiga/permissions/choices.py:61 +msgid "Modify issue" +msgstr "イッシューを変更する" + +#: taiga/permissions/choices.py:62 +msgid "Comment issue" +msgstr "イッシューをコメントする" + +#: taiga/permissions/choices.py:63 +msgid "Delete issue" +msgstr "イッシューを削除する" + +#: taiga/permissions/choices.py:66 +msgid "Add wiki page" +msgstr "ウィキページを追加する" + +#: taiga/permissions/choices.py:67 +msgid "Modify wiki page" +msgstr "ウィキページを変更する" + +#: taiga/permissions/choices.py:68 +msgid "Comment wiki page" +msgstr "ウィキページをコメントする" + +#: taiga/permissions/choices.py:69 +msgid "Delete wiki page" +msgstr "ウィキページを削除する" + +#: taiga/permissions/choices.py:72 +msgid "Add wiki link" +msgstr "ウィキリンクを追加する" + +#: taiga/permissions/choices.py:73 +msgid "Modify wiki link" +msgstr "ウィキリンクを変更する" + +#: taiga/permissions/choices.py:74 +msgid "Delete wiki link" +msgstr "ウィキリンクを削除する" + +#: taiga/permissions/choices.py:78 +msgid "Modify project" +msgstr "プロジェクトを変更する" + +#: taiga/permissions/choices.py:79 +msgid "Delete project" +msgstr "プロジェクトを削除する" + +#: taiga/permissions/choices.py:80 +msgid "Add member" +msgstr "メンバーを追加する" + +#: taiga/permissions/choices.py:81 +msgid "Remove member" +msgstr "メンバーを削除する" + +#: taiga/permissions/choices.py:82 +msgid "Admin project values" +msgstr "管理者プロジェクトの値" + +#: taiga/permissions/choices.py:83 +msgid "Admin roles" +msgstr "管理者のロール" + +#: taiga/projects/admin.py:100 +msgid "Privacity" +msgstr "プライバシー" + +#: taiga/projects/admin.py:111 +msgid "Modules" +msgstr "モジュール" + +#: taiga/projects/admin.py:119 +msgid "Default values" +msgstr "初期値" + +#: taiga/projects/admin.py:125 +msgid "Activity" +msgstr "アクティビティ" + +#: taiga/projects/admin.py:130 +msgid "Fans" +msgstr "ファン" + +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 +#: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 +#: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:27 +msgid "owner" +msgstr "オーナー" + +#: taiga/projects/admin.py:192 +#, python-brace-format +msgid "{count} successfully made public." +msgstr "{count} 公開に成功しました。" + +#: taiga/projects/admin.py:193 +msgid "Make public" +msgstr "公開にする" + +#: taiga/projects/admin.py:207 +#, python-brace-format +msgid "{count} successfully made private." +msgstr "{count} プライベートにするのに成功しました。" + +#: taiga/projects/admin.py:208 +msgid "Make private" +msgstr "プライベートにする" + +#: taiga/projects/admin.py:238 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "選択中の%(verbose_name_plural)sらを削除する" + +#: taiga/projects/api.py:160 taiga/users/api.py:244 +msgid "Incomplete arguments" +msgstr "引数は不足している" + +#: taiga/projects/api.py:164 taiga/users/api.py:249 +msgid "Invalid image format" +msgstr "画像形式が正しくありません" + +#: taiga/projects/api.py:225 +msgid "Not valid template name" +msgstr "無効なテンプレート名" + +#: taiga/projects/api.py:228 +msgid "Not valid template description" +msgstr "無効なテンプレートの説明文" + +#: taiga/projects/api.py:354 +msgid "Invalid user id" +msgstr "無効なユーザーID" + +#: taiga/projects/api.py:360 +msgid "The user doesn't exist" +msgstr "このユーザーは存在しません。" + +#: taiga/projects/api.py:364 +msgid "The user must be already a project member" +msgstr "ユーザーはプロジェクトのメンバーである必要がある" + +#: taiga/projects/api.py:785 +msgid "" +"The project must have an owner and at least one of the users must be an " +"active admin" +msgstr "" +"プロジェクトにオーナーと、1人以上のアクティブな管理者であるユーザーが必要で" +"す。" + +#: taiga/projects/api.py:819 +msgid "You don't have permisions to see that." +msgstr "閲覧する権限がありません。" + +#: taiga/projects/attachments/api.py:54 +msgid "Partial updates are not supported" +msgstr "部分的な更新に対応していません。" + +#: taiga/projects/attachments/api.py:69 +msgid "Object id issue isn't exists" +msgstr "オブジェクトIDのイッシューは存在しません。" + +#: taiga/projects/attachments/api.py:72 +msgid "Project ID not matches between object and project" +msgstr "" + +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 +#: taiga/projects/custom_attributes/models.py:43 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 +#: taiga/projects/notifications/models.py:74 +#: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 +#: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 +msgid "project" +msgstr "プロジェクト" + +#: taiga/projects/attachments/models.py:43 +msgid "content type" +msgstr "コンテントタイプ" + +#: taiga/projects/attachments/models.py:45 +msgid "object id" +msgstr "オブジェクトID" + +#: taiga/projects/attachments/models.py:51 +#: taiga/projects/custom_attributes/models.py:48 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 +#: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 +#: taiga/userstorage/models.py:31 +msgid "modified date" +msgstr "更新日時" + +#: taiga/projects/attachments/models.py:56 +msgid "attached file" +msgstr "添付ファイル" + +#: taiga/projects/attachments/models.py:58 +msgid "sha1" +msgstr "sha1" + +#: taiga/projects/attachments/models.py:60 +msgid "is deprecated" +msgstr "は廃止予定である" + +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 +#: taiga/projects/custom_attributes/models.py:41 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 +msgid "order" +msgstr "並べ替え" + +#: taiga/projects/choices.py:23 +msgid "AppearIn" +msgstr "AppearIn" + +#: taiga/projects/choices.py:24 +msgid "Jitsi" +msgstr "Jitsi" + +#: taiga/projects/choices.py:25 +msgid "Custom" +msgstr "カスタム" + +#: taiga/projects/choices.py:26 +msgid "Talky" +msgstr "" + +#: taiga/projects/choices.py:35 +msgid "This project is blocked due to payment failure" +msgstr "このプロジェクトは支払い不能によりブロックされています。" + +#: taiga/projects/choices.py:36 +msgid "This project is blocked by admin staff" +msgstr "このプロジェクトは管理スタッフによりブロックされました。" + +#: taiga/projects/choices.py:37 +msgid "This project is blocked because the owner left" +msgstr "このプロジェクトはオーナーの辞退によりブロックされています。" + +#: taiga/projects/choices.py:38 +msgid "This project is blocked while it's deleted" +msgstr "このプロジェクトは削除中のためブロックされています。" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 +msgid "Text" +msgstr "テキスト" + +#: taiga/projects/custom_attributes/choices.py:30 +msgid "Multi-Line Text" +msgstr "マルチラインテキスト" + +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "日時" + +#: taiga/projects/custom_attributes/choices.py:33 +msgid "Url" +msgstr "URL" + +#: taiga/projects/custom_attributes/models.py:40 +#: taiga/projects/issues/models.py:45 +msgid "type" +msgstr "タイプ" + +#: taiga/projects/custom_attributes/models.py:95 +msgid "values" +msgstr "値" + +#: taiga/projects/custom_attributes/models.py:105 +msgid "epic" +msgstr "エピック" + +#: taiga/projects/custom_attributes/models.py:121 +#: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 +msgid "user story" +msgstr "ユーザストーリ" + +#: taiga/projects/custom_attributes/models.py:137 +msgid "task" +msgstr "タスク" + +#: taiga/projects/custom_attributes/models.py:153 +msgid "issue" +msgstr "イッシュー" + +#: taiga/projects/custom_attributes/validators.py:58 +msgid "Already exists one with the same name." +msgstr "同じ名前のものがすでに存在します。" + +#: taiga/projects/epics/api.py:94 +msgid "You don't have permissions to set this status to this epic." +msgstr "このエピックにこのステータスを付与する権限がありません。" + +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 +#: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 +msgid "ref" +msgstr "" + +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 +#: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 +msgid "status" +msgstr "ステータス" + +#: taiga/projects/epics/models.py:46 +msgid "epics order" +msgstr "" + +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 +#: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 +msgid "subject" +msgstr "件名" + +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 +msgid "color" +msgstr "色" + +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 +#: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 +msgid "assigned to" +msgstr "担当者" + +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 +msgid "is client requirement" +msgstr "クライアントの要件" + +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 +msgid "is team requirement" +msgstr "チームの要件" + +#: taiga/projects/epics/models.py:70 +msgid "user stories" +msgstr "ユーザストーリ" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "外部参照" + +#: taiga/projects/epics/validators.py:37 +msgid "There's no epic with that id" +msgstr "このIDのエピックは存在しません。" + +#: taiga/projects/history/api.py:93 +msgid "comment is required" +msgstr "コメントが必須" + +#: taiga/projects/history/api.py:96 +msgid "deleted comments can't be edited" +msgstr "削除されたコメントを編集できません。" + +#: taiga/projects/history/api.py:130 +msgid "Comment already deleted" +msgstr "コメントがすでに削除済みです。" + +#: taiga/projects/history/api.py:151 +msgid "Comment not deleted" +msgstr "コメントが削除されていません。" + +#: taiga/projects/history/choices.py:31 +msgid "Change" +msgstr "変更する" + +#: taiga/projects/history/choices.py:32 +msgid "Create" +msgstr "作成する" + +#: taiga/projects/history/choices.py:33 +msgid "Delete" +msgstr "削除する" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 +#, python-format +msgid "%(role)s role points" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 +msgid "from" +msgstr "from" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 +msgid "to" +msgstr "to" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 +msgid "Added new attachment" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 +msgid "Updated attachment" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +msgid "deprecated" +msgstr "非推奨" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 +msgid "not deprecated" +msgstr "非推奨ではない" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 +msgid "Deleted attachment" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 +msgid "added" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 +msgid "removed" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 +#: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 +msgid "Unassigned" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 +msgid "-deleted-" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 +msgid "to:" +msgstr "to:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 +msgid "from:" +msgstr "from:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 +msgid "Added" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 +msgid "Changed" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 +msgid "Deleted" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 +msgid "added:" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 +msgid "removed:" +msgstr "" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 +msgid "From:" +msgstr "差出人" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 +msgid "To:" +msgstr "宛先" + +#: taiga/projects/history/templatetags/functions.py:26 +#: taiga/projects/wiki/models.py:38 +msgid "content" +msgstr "コンテント" + +#: taiga/projects/history/templatetags/functions.py:27 +#: taiga/projects/mixins/blocked.py:33 +msgid "blocked note" +msgstr "ブロックされたノート" + +#: taiga/projects/history/templatetags/functions.py:28 +msgid "sprint" +msgstr "スプリント" + +#: taiga/projects/issues/api.py:157 +msgid "You don't have permissions to set this sprint to this issue." +msgstr "このイッシューにこのスプリントを付与する権限がありません。" + +#: taiga/projects/issues/api.py:161 +msgid "You don't have permissions to set this status to this issue." +msgstr "このイッシューにこのステータスを付与する権限がありません。" + +#: taiga/projects/issues/api.py:165 +msgid "You don't have permissions to set this severity to this issue." +msgstr "このイッシューにこの深刻度を付与する権限がありません。" + +#: taiga/projects/issues/api.py:169 +msgid "You don't have permissions to set this priority to this issue." +msgstr "このイッシューにこの優先度を付与する権限がありません。" + +#: taiga/projects/issues/api.py:173 +msgid "You don't have permissions to set this type to this issue." +msgstr "このイッシューのこのタイプを付与する権限がありません。" + +#: taiga/projects/issues/models.py:41 +msgid "severity" +msgstr "深刻度" + +#: taiga/projects/issues/models.py:43 +msgid "priority" +msgstr "優先度" + +#: taiga/projects/issues/models.py:48 taiga/projects/tasks/models.py:46 +#: taiga/projects/userstories/models.py:65 +msgid "milestone" +msgstr "マイルストーン" + +#: taiga/projects/issues/models.py:57 taiga/projects/tasks/models.py:53 +msgid "finished date" +msgstr "完了日時" + +#: taiga/projects/likes/models.py:36 +msgid "Like" +msgstr "いいね" + +#: taiga/projects/likes/models.py:37 +msgid "Likes" +msgstr "いいねの数" + +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 +msgid "slug" +msgstr "スラグ" + +#: taiga/projects/milestones/models.py:45 +msgid "estimated start date" +msgstr "開始予定日時" + +#: taiga/projects/milestones/models.py:46 +msgid "estimated finish date" +msgstr "完了予定日時" + +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 +msgid "is closed" +msgstr "" + +#: taiga/projects/milestones/models.py:55 +msgid "disponibility" +msgstr "" + +#: taiga/projects/milestones/models.py:79 +msgid "The estimated start must be previous to the estimated finish." +msgstr "" + +#: taiga/projects/milestones/validators.py:33 +msgid "There's no milestone with that id" +msgstr "このIDのマイルストーンは存在しません。" + +#: taiga/projects/mixins/blocked.py:31 +msgid "is blocked" +msgstr "はブロックされています。" + +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + +#: taiga/projects/mixins/ordering.py:49 +#, python-brace-format +msgid "'{param}' parameter is mandatory" +msgstr "'{param}'パラメータは必須です。" + +#: taiga/projects/mixins/ordering.py:53 +msgid "'project' parameter is mandatory" +msgstr "'project'パラメータは必須です。" + +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 +msgid "email" +msgstr "メール" + +#: taiga/projects/models.py:81 +msgid "create at" +msgstr "" + +#: taiga/projects/models.py:83 taiga/users/models.py:157 +msgid "token" +msgstr "トークン" + +#: taiga/projects/models.py:89 +msgid "invitation extra text" +msgstr "招待状の追加テキスト" + +#: taiga/projects/models.py:92 taiga/projects/models.py:741 +msgid "user order" +msgstr "" + +#: taiga/projects/models.py:108 +msgid "The user is already member of the project" +msgstr "このユーザーはすでにプロジェクトのメンバーです。" + +#: taiga/projects/models.py:115 +msgid "default epic status" +msgstr "" + +#: taiga/projects/models.py:119 +msgid "default US status" +msgstr "" + +#: taiga/projects/models.py:122 +msgid "default points" +msgstr "" + +#: taiga/projects/models.py:126 +msgid "default task status" +msgstr "" + +#: taiga/projects/models.py:129 +msgid "default priority" +msgstr "" + +#: taiga/projects/models.py:132 +msgid "default severity" +msgstr "" + +#: taiga/projects/models.py:136 +msgid "default issue status" +msgstr "" + +#: taiga/projects/models.py:140 +msgid "default issue type" +msgstr "" + +#: taiga/projects/models.py:156 +msgid "logo" +msgstr "ロゴ" + +#: taiga/projects/models.py:166 +msgid "members" +msgstr "メンバー" + +#: taiga/projects/models.py:169 +msgid "total of milestones" +msgstr "" + +#: taiga/projects/models.py:170 +msgid "total story points" +msgstr "" + +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 +msgid "active epics panel" +msgstr "" + +#: taiga/projects/models.py:176 taiga/projects/models.py:755 +msgid "active backlog panel" +msgstr "" + +#: taiga/projects/models.py:178 taiga/projects/models.py:757 +msgid "active kanban panel" +msgstr "" + +#: taiga/projects/models.py:180 taiga/projects/models.py:759 +msgid "active wiki panel" +msgstr "" + +#: taiga/projects/models.py:182 taiga/projects/models.py:761 +msgid "active issues panel" +msgstr "" + +#: taiga/projects/models.py:185 taiga/projects/models.py:768 +msgid "videoconference system" +msgstr "" + +#: taiga/projects/models.py:187 taiga/projects/models.py:770 +msgid "videoconference extra data" +msgstr "" + +#: taiga/projects/models.py:193 +msgid "creation template" +msgstr "" + +#: taiga/projects/models.py:196 taiga/users/admin.py:62 +msgid "is private" +msgstr "" + +#: taiga/projects/models.py:198 +msgid "anonymous permissions" +msgstr "" + +#: taiga/projects/models.py:200 +msgid "user permissions" +msgstr "" + +#: taiga/projects/models.py:203 +msgid "is featured" +msgstr "" + +#: taiga/projects/models.py:206 taiga/projects/models.py:763 +msgid "is looking for people" +msgstr "" + +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "" + +#: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 +msgid "blocked code" +msgstr "" + +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 +msgid "updated date time" +msgstr "" + +#: taiga/projects/models.py:232 taiga/projects/models.py:244 +#: taiga/projects/votes/models.py:30 +msgid "count" +msgstr "" + +#: taiga/projects/models.py:235 +msgid "fans last week" +msgstr "" + +#: taiga/projects/models.py:238 +msgid "fans last month" +msgstr "" + +#: taiga/projects/models.py:241 +msgid "fans last year" +msgstr "" + +#: taiga/projects/models.py:248 +msgid "activity last week" +msgstr "" + +#: taiga/projects/models.py:252 +msgid "activity last month" +msgstr "" + +#: taiga/projects/models.py:256 +msgid "activity last year" +msgstr "" + +#: taiga/projects/models.py:507 +msgid "modules config" +msgstr "" + +#: taiga/projects/models.py:559 +msgid "is archived" +msgstr "" + +#: taiga/projects/models.py:563 +msgid "work in progress limit" +msgstr "" + +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 +msgid "value" +msgstr "" + +#: taiga/projects/models.py:749 +msgid "default owner's role" +msgstr "" + +#: taiga/projects/models.py:772 +msgid "default options" +msgstr "" + +#: taiga/projects/models.py:773 +msgid "epic statuses" +msgstr "" + +#: taiga/projects/models.py:774 +msgid "us statuses" +msgstr "" + +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 +#: taiga/projects/userstories/models.py:77 +msgid "points" +msgstr "" + +#: taiga/projects/models.py:776 +msgid "task statuses" +msgstr "" + +#: taiga/projects/models.py:777 +msgid "issue statuses" +msgstr "" + +#: taiga/projects/models.py:778 +msgid "issue types" +msgstr "" + +#: taiga/projects/models.py:779 +msgid "priorities" +msgstr "" + +#: taiga/projects/models.py:780 +msgid "severities" +msgstr "" + +#: taiga/projects/models.py:781 +msgid "roles" +msgstr "" + +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + +#: taiga/projects/notifications/choices.py:30 +msgid "Involved" +msgstr "" + +#: taiga/projects/notifications/choices.py:31 +msgid "All" +msgstr "" + +#: taiga/projects/notifications/choices.py:32 +msgid "None" +msgstr "" + +#: taiga/projects/notifications/models.py:64 +msgid "created date time" +msgstr "" + +#: taiga/projects/notifications/models.py:68 +msgid "history entries" +msgstr "" + +#: taiga/projects/notifications/models.py:71 +msgid "notify users" +msgstr "" + +#: taiga/projects/notifications/models.py:93 +#: taiga/projects/notifications/models.py:94 +msgid "Watched" +msgstr "" + +#: taiga/projects/notifications/services.py:65 +#: taiga/projects/notifications/services.py:79 +msgid "Notify exists for specified user and project" +msgstr "" + +#: taiga/projects/notifications/services.py:436 +msgid "Invalid value for notify level" +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Epic updated

\n" +"

Hello %(user)s,
%(changer)s has updated a epic on %(project)s\n" +"

Epic #%(ref)s %(subject)s

\n" +" See epic\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Epic updated\n" +"Hello %(user)s, %(changer)s has updated a epic on %(project)s\n" +"See epic #%(ref)s %(subject)s at %(url)s\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New epic created

\n" +"

Hello %(user)s,
%(changer)s has created a new epic on " +"%(project)s

\n" +"

Epic #%(ref)s %(subject)s

\n" +" See epic\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New epic created\n" +"Hello %(user)s, %(changer)s has created a new epic on %(project)s\n" +"See epic #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Epic deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a epic on %(project)s\n" +"

Epic #%(ref)s %(subject)s

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Epic deleted\n" +"Hello %(user)s, %(changer)s has deleted a epic on %(project)s\n" +"Epic #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Issue updated

\n" +"

Hello %(user)s,
%(changer)s has updated an issue on %(project)s\n" +"

Issue #%(ref)s %(subject)s

\n" +" See issue\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Issue updated\n" +"Hello %(user)s, %(changer)s has updated an issue on %(project)s\n" +"See issue #%(ref)s %(subject)s at %(url)s\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New issue created

\n" +"

Hello %(user)s,
%(changer)s has created a new issue on " +"%(project)s

\n" +"

Issue #%(ref)s %(subject)s

\n" +" See issue\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New issue created\n" +"Hello %(user)s, %(changer)s has created a new issue on %(project)s\n" +"See issue #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Issue deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted an issue on %(project)s\n" +"

Issue #%(ref)s %(subject)s

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Issue deleted\n" +"Hello %(user)s, %(changer)s has deleted an issue on %(project)s\n" +"Issue #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Sprint updated

\n" +"

Hello %(user)s,
%(changer)s has updated an sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +" See sprint\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Sprint updated\n" +"Hello %(user)s, %(changer)s has updated a sprint on %(project)s\n" +"See sprint %(name)s at %(url)s\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the sprint \"%(milestone)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New sprint created

\n" +"

Hello %(user)s,
%(changer)s has created a new sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +" See " +"sprint\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New sprint created\n" +"Hello %(user)s, %(changer)s has created a new sprint on %(project)s\n" +"See sprint %(name)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the sprint \"%(milestone)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Sprint deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted an sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Sprint deleted\n" +"Hello %(user)s, %(changer)s has deleted an sprint on %(project)s\n" +"Sprint %(name)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the Sprint \"%(milestone)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Task updated

\n" +"

Hello %(user)s,
%(changer)s has updated a task on %(project)s\n" +"

Task #%(ref)s %(subject)s

\n" +" See task\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Task updated\n" +"Hello %(user)s, %(changer)s has updated a task on %(project)s\n" +"See task #%(ref)s %(subject)s at %(url)s\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the task #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New task created

\n" +"

Hello %(user)s,
%(changer)s has created a new task on " +"%(project)s

\n" +"

Task #%(ref)s %(subject)s

\n" +" See task\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New task created\n" +"Hello %(user)s, %(changer)s has created a new task on %(project)s\n" +"See task #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the task #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Task deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a task on %(project)s\n" +"

Task #%(ref)s %(subject)s

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Task deleted\n" +"Hello %(user)s, %(changer)s has deleted a task on %(project)s\n" +"Task #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the task #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

User Story updated

\n" +"

Hello %(user)s,
%(changer)s has updated a user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +" See user story\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"User story updated\n" +"Hello %(user)s, %(changer)s has updated a user story on %(project)s\n" +"See user story #%(ref)s %(subject)s at %(url)s\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the US #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New user story created

\n" +"

Hello %(user)s,
%(changer)s has created a new user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +" See user story\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New user story created\n" +"Hello %(user)s, %(changer)s has created a new user story on %(project)s\n" +"See user story #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the US #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

User Story deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"User Story deleted\n" +"Hello %(user)s, %(changer)s has deleted a user story on %(project)s\n" +"User Story #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the US #%(ref)s \"%(subject)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Wiki Page updated

\n" +"

Hello %(user)s,
%(changer)s has updated a wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +" See Wiki Page\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Wiki Page updated\n" +"\n" +"Hello %(user)s, %(changer)s has updated a wiki page on %(project)s\n" +"\n" +"See wiki page %(page)s at %(url)s\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the Wiki Page \"%(page)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New wiki page created

\n" +"

Hello %(user)s,
%(changer)s has created a new wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +" See " +"wiki page\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New wiki page created\n" +"\n" +"Hello %(user)s, %(changer)s has created a new wiki page on %(project)s\n" +"\n" +"See wiki page %(page)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the Wiki Page \"%(page)s\"\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Wiki page deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Wiki page deleted\n" +"\n" +"Hello %(user)s, %(changer)s has deleted a wiki page on %(project)s\n" +"\n" +"Wiki page %(page)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the Wiki Page \"%(page)s\"\n" +msgstr "" + +#: taiga/projects/notifications/validators.py:48 +msgid "Watchers contains invalid users" +msgstr "" + +#: taiga/projects/occ/mixins.py:37 +msgid "The version must be an integer" +msgstr "" + +#: taiga/projects/occ/mixins.py:60 +msgid "The version parameter is not valid" +msgstr "" + +#: taiga/projects/occ/mixins.py:76 +msgid "The version doesn't match with the current one" +msgstr "" + +#: taiga/projects/occ/mixins.py:95 +msgid "version" +msgstr "" + +#: taiga/projects/permissions.py:44 +msgid "" +"You can't leave the project if you are the owner or there are no more admins" +msgstr "" + +#: taiga/projects/services/members.py:133 +msgid "Project without owner" +msgstr "" + +#: taiga/projects/services/members.py:138 +msgid "You have reached your current limit of memberships for private projects" +msgstr "" + +#: taiga/projects/services/members.py:142 +msgid "You have reached your current limit of memberships for public projects" +msgstr "" + +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 +msgid "You can't have more private projects" +msgstr "" + +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 +msgid "" +"This project reaches your current limit of memberships for private projects" +msgstr "" + +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 +msgid "You can't have more public projects" +msgstr "" + +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 +msgid "" +"This project reaches your current limit of memberships for public projects" +msgstr "" + +#: taiga/projects/services/stats.py:197 +msgid "Future sprint" +msgstr "" + +#: taiga/projects/services/stats.py:217 +msgid "Project End" +msgstr "プロジェクト完了" + +#: taiga/projects/services/transfer.py:62 +#: taiga/projects/services/transfer.py:69 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 +msgid "Token is invalid" +msgstr "" + +#: taiga/projects/services/transfer.py:67 +msgid "Token has expired" +msgstr "" + +#: taiga/projects/tagging/fields.py:52 +#, python-brace-format +msgid "Invalid tag '{value}'. The color is not a valid HEX color or null." +msgstr "" + +#: taiga/projects/tagging/fields.py:55 +#, python-brace-format +msgid "" +"Invalid tag '{value}'. it must be the name or a pair '[\"name\", \"hex color/" +"\" | null]'." +msgstr "" + +#: taiga/projects/tagging/fields.py:77 +#, python-brace-format +msgid "Invalid tag '{value}'. It must be the tag name." +msgstr "" + +#: taiga/projects/tagging/models.py:27 +msgid "tags" +msgstr "" + +#: taiga/projects/tagging/models.py:35 +msgid "tags colors" +msgstr "" + +#: taiga/projects/tagging/validators.py:47 +#: taiga/projects/tagging/validators.py:74 +msgid "This tag already exists." +msgstr "" + +#: taiga/projects/tagging/validators.py:54 +#: taiga/projects/tagging/validators.py:81 +msgid "The color is not a valid HEX color." +msgstr "" + +#: taiga/projects/tagging/validators.py:67 +#: taiga/projects/tagging/validators.py:101 +#: taiga/projects/tagging/validators.py:114 +#: taiga/projects/tagging/validators.py:121 +msgid "The tag doesn't exist." +msgstr "" + +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 +msgid "You don't have permissions to set this sprint to this task." +msgstr "" + +#: taiga/projects/tasks/api.py:101 +msgid "You don't have permissions to set this user story to this task." +msgstr "" + +#: taiga/projects/tasks/api.py:104 +msgid "You don't have permissions to set this status to this task." +msgstr "" + +#: taiga/projects/tasks/models.py:58 +msgid "us order" +msgstr "" + +#: taiga/projects/tasks/models.py:60 +msgid "taskboard order" +msgstr "" + +#: taiga/projects/tasks/models.py:68 +msgid "is iocaine" +msgstr "" + +#: taiga/projects/tasks/validators.py:61 +msgid "Invalid milestone id." +msgstr "" + +#: taiga/projects/tasks/validators.py:72 +msgid "Invalid task status id." +msgstr "" + +#: taiga/projects/tasks/validators.py:85 +msgid "Invalid user story id." +msgstr "" + +#: taiga/projects/tasks/validators.py:109 +msgid "Invalid task status id. The status must belong to the same project." +msgstr "" + +#: taiga/projects/tasks/validators.py:123 +msgid "Invalid user story id. The user story must belong to the same project." +msgstr "" + +#: taiga/projects/tasks/validators.py:135 +msgid "Invalid milestone id. The milestone must belong to the same project." +msgstr "" + +#: taiga/projects/tasks/validators.py:152 +msgid "" +"Invalid task ids. All tasks must belong to the same project and, if it " +"exists, to the same status, user story and/or milestone." +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:6 +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:4 +msgid "someone" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:11 +#, python-format +msgid "" +"\n" +"

You have been invited to Taiga!

\n" +"

Hi! %(full_name)s has sent you an invitation to join project " +"%(project)s in Taiga.
Taiga is a Free, open Source Agile Project " +"Management Tool.

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:17 +#, python-format +msgid "" +"\n" +"

And now a few words from the jolly good fellow or sistren
" +"who thought so kindly as to invite you

\n" +"

%(extra)s

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:24 +msgid "Accept your invitation to Taiga" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:24 +msgid "Accept your invitation" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:25 +msgid "The Taiga Team" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:6 +#, python-format +msgid "" +"\n" +"You, or someone you know, has invited you to Taiga\n" +"\n" +"Hi! %(full_name)s has sent you an invitation to join a project called " +"%(project)s which is being managed on Taiga, a Free, open Source Agile " +"Project Management Tool.\n" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:12 +#, python-format +msgid "" +"\n" +"And now a few words from the jolly good fellow or sistren who thought so " +"kindly as to invite you:\n" +"\n" +"%(extra)s\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:18 +msgid "Accept your invitation to Taiga following this link:" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:20 +msgid "" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/templates/emails/membership_invitation-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Invitation to join to the project '%(project)s'\n" +msgstr "" + +#: taiga/projects/templates/emails/membership_notification-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

You have been added to a project

\n" +"

Hello %(full_name)s,
you have been added to the project " +"%(project)s

\n" +" Go to " +"project\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/membership_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"You have been added to a project\n" +"Hello %(full_name)s,you have been added to the project %(project)s\n" +"\n" +"See project at %(url)s\n" +msgstr "" + +#: taiga/projects/templates/emails/membership_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Added to the project '%(project)s'\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(old_owner_name)s,

\n" +"

%(new_owner_name)s has accepted your offer and will become the " +"new project owner for \"%(project_name)s\".

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

%(new_owner_name)s says:

" +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-body-html.jinja:14 +msgid "" +"\n" +"

From now on, your new status for this project will be \"admin\".\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(old_owner_name)s,\n" +"%(new_owner_name)s has accepted your offer and will become the new project " +"owner for \"%(project_name)s\".\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:7 +#, python-format +msgid "%(new_owner_name)s says:" +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:11 +msgid "" +"\n" +"From now on, your new status for this project will be \"admin\".\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:16 +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:19 +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:13 +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:18 +msgid "" +"\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_accept-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer offer accepted!\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(owner_name)s,

\n" +"

%(rejecter_name)s has declined your offer and will not become the " +"new project owner for \"%(project_name)s\".

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:10 +#, python-format +msgid "" +"\n" +"

%(rejecter_name)s says:

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:16 +msgid "" +"\n" +"

If you want, you can still try to transfer the project ownership to a " +"different person.

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:21 +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:22 +msgid "Request transfer to a different person" +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(owner_name)s,\n" +"%(rejecter_name)s has declined your offer and will not become the new " +"project owner for \"%(project_name)s\".\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:7 +#, python-format +msgid "%(rejecter_name)s says:" +msgstr "%(rejecter_name)s がコメント:" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:11 +msgid "" +"\n" +"If you want, you can still try to transfer the project ownership to a " +"different person.\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:15 +msgid "Request transfer to a different person:" +msgstr "" + +#: taiga/projects/templates/emails/transfer_reject-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer declined\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(owner_name)s,

\n" +"

%(requester_name)s has requested to become the project owner for " +"\"%(project_name)s\".

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:9 +msgid "" +"\n" +"

Please, click on \"Continue\" if you would like to start the " +"project transfer from the administration panel.

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:14 +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:22 +msgid "Continue" +msgstr "続ける" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(owner_name)s,\n" +"%(requester_name)s has requested to become the project owner for " +"\"%(project_name)s\".\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:6 +msgid "" +"\n" +"Please, go to your project settings if you would like to start the project " +"transfer from the administration panel.\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:10 +msgid "Go to your project settings:" +msgstr "プロジェクト設定へ移動する:" + +#: taiga/projects/templates/emails/transfer_request-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer request\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(receiver_name)s,

\n" +"

%(owner_name)s, the current project owner at \"%(project_name)s\" " +"would like you to become the new project owner.

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:10 +#, python-format +msgid "" +"\n" +"

%(owner_name)s says:

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:17 +msgid "" +"\n" +"

Please, click on \"Continue\" to either accept or reject this " +"proposal.

\n" +" " +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(receiver_name)s,\n" +"%(owner_name)s, the current project owner at \"%(project_name)s\" would like " +"you to become the new project owner.\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:6 +#, python-format +msgid "%(owner_name)s says:" +msgstr "%(owner_name)s がコメント:" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:11 +msgid "" +"\n" +"Please, go to the following link to either accept or reject this proposal.\n" +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:15 +msgid "Accept or reject the project ownership transfer:" +msgstr "" + +#: taiga/projects/templates/emails/transfer_start-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer offer\n" +msgstr "" + +#. Translators: Name of scrum project template. +#: taiga/projects/translations.py:30 +msgid "Scrum" +msgstr "スクラム" + +#. Translators: Description of scrum project template. +#: taiga/projects/translations.py:32 +msgid "" +"The agile product backlog in Scrum is a prioritized features list, " +"containing short descriptions of all functionality desired in the product. " +"When applying Scrum, it's not necessary to start a project with a lengthy, " +"upfront effort to document all requirements. The Scrum product backlog is " +"then allowed to grow and change as more is learned about the product and its " +"customers" +msgstr "" + +#. Translators: Name of kanban project template. +#: taiga/projects/translations.py:35 +msgid "Kanban" +msgstr "カンバン" + +#. Translators: Description of kanban project template. +#: taiga/projects/translations.py:37 +msgid "" +"Kanban is a method for managing knowledge work with an emphasis on just-in-" +"time delivery while not overloading the team members. In this approach, the " +"process, from definition of a task to its delivery to the customer, is " +"displayed for participants to see and team members pull work from a queue." +msgstr "" + +#. Translators: User story point value (value = undefined) +#: taiga/projects/translations.py:45 +msgid "?" +msgstr "?" + +#. Translators: User story point value (value = 0) +#: taiga/projects/translations.py:47 +msgid "0" +msgstr "0" + +#. Translators: User story point value (value = 0.5) +#: taiga/projects/translations.py:49 +msgid "1/2" +msgstr "1/2" + +#. Translators: User story point value (value = 1) +#: taiga/projects/translations.py:51 +msgid "1" +msgstr "1" + +#. Translators: User story point value (value = 2) +#: taiga/projects/translations.py:53 +msgid "2" +msgstr "2" + +#. Translators: User story point value (value = 3) +#: taiga/projects/translations.py:55 +msgid "3" +msgstr "3" + +#. Translators: User story point value (value = 5) +#: taiga/projects/translations.py:57 +msgid "5" +msgstr "5" + +#. Translators: User story point value (value = 8) +#: taiga/projects/translations.py:59 +msgid "8" +msgstr "8" + +#. Translators: User story point value (value = 10) +#: taiga/projects/translations.py:61 +msgid "10" +msgstr "10" + +#. Translators: User story point value (value = 13) +#: taiga/projects/translations.py:63 +msgid "13" +msgstr "13" + +#. Translators: User story point value (value = 20) +#: taiga/projects/translations.py:65 +msgid "20" +msgstr "20" + +#. Translators: User story point value (value = 40) +#: taiga/projects/translations.py:67 +msgid "40" +msgstr "40" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:75 taiga/projects/translations.py:98 +#: taiga/projects/translations.py:114 +msgid "New" +msgstr "新規" + +#. Translators: User story status +#: taiga/projects/translations.py:78 +msgid "Ready" +msgstr "準備中" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:81 taiga/projects/translations.py:100 +#: taiga/projects/translations.py:116 +msgid "In progress" +msgstr "処理中" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:84 taiga/projects/translations.py:102 +#: taiga/projects/translations.py:118 +msgid "Ready for test" +msgstr "テスト待ち" + +#. Translators: User story status +#: taiga/projects/translations.py:87 +msgid "Done" +msgstr "完了" + +#. Translators: User story status +#: taiga/projects/translations.py:90 +msgid "Archived" +msgstr "アーカイブ" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:104 taiga/projects/translations.py:120 +msgid "Closed" +msgstr "終了" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:106 taiga/projects/translations.py:122 +msgid "Needs Info" +msgstr "情報が必要" + +#. Translators: Issue status +#: taiga/projects/translations.py:124 +msgid "Postponed" +msgstr "延期" + +#. Translators: Issue status +#: taiga/projects/translations.py:126 +msgid "Rejected" +msgstr "拒否" + +#. Translators: Issue type +#: taiga/projects/translations.py:134 +msgid "Bug" +msgstr "バグ" + +#. Translators: Issue type +#: taiga/projects/translations.py:136 +msgid "Question" +msgstr "問い合わせ" + +#. Translators: Issue type +#: taiga/projects/translations.py:138 +msgid "Enhancement" +msgstr "拡張" + +#. Translators: Issue priority +#: taiga/projects/translations.py:146 +msgid "Low" +msgstr "低い" + +#. Translators: Issue priority +#. Translators: Issue severity +#: taiga/projects/translations.py:148 taiga/projects/translations.py:161 +msgid "Normal" +msgstr "通常" + +#. Translators: Issue priority +#: taiga/projects/translations.py:150 +msgid "High" +msgstr "高い" + +#. Translators: Issue severity +#: taiga/projects/translations.py:157 +msgid "Wishlist" +msgstr "やりたい事" + +#. Translators: Issue severity +#: taiga/projects/translations.py:159 +msgid "Minor" +msgstr "マイナー" + +#. Translators: Issue severity +#: taiga/projects/translations.py:163 +msgid "Important" +msgstr "重要" + +#. Translators: Issue severity +#: taiga/projects/translations.py:165 +msgid "Critical" +msgstr "危険" + +#. Translators: User role +#: taiga/projects/translations.py:172 +msgid "UX" +msgstr "UX" + +#. Translators: User role +#: taiga/projects/translations.py:174 +msgid "Design" +msgstr "デザイン" + +#. Translators: User role +#: taiga/projects/translations.py:176 +msgid "Front" +msgstr "フロントエンド" + +#. Translators: User role +#: taiga/projects/translations.py:178 +msgid "Back" +msgstr "バックエンド" + +#. Translators: User role +#: taiga/projects/translations.py:180 +msgid "Product Owner" +msgstr "プロダクトオーナー" + +#. Translators: User role +#: taiga/projects/translations.py:182 +msgid "Stakeholder" +msgstr "" + +#: taiga/projects/userstories/api.py:128 +msgid "You don't have permissions to set this sprint to this user story." +msgstr "" + +#: taiga/projects/userstories/api.py:132 +msgid "You don't have permissions to set this status to this user story." +msgstr "" + +#: taiga/projects/userstories/api.py:222 +#, python-brace-format +msgid "Invalid role id '{role_id}'" +msgstr "" + +#: taiga/projects/userstories/api.py:229 +#, python-brace-format +msgid "Invalid points id '{points_id}'" +msgstr "" + +#: taiga/projects/userstories/api.py:244 +#, python-brace-format +msgid "Generating the user story #{ref} - {subject}" +msgstr "" + +#: taiga/projects/userstories/models.py:41 +msgid "role" +msgstr "役割" + +#: taiga/projects/userstories/models.py:80 +msgid "backlog order" +msgstr "バックログ順" + +#: taiga/projects/userstories/models.py:82 +msgid "sprint order" +msgstr "スプリント順" + +#: taiga/projects/userstories/models.py:84 +msgid "kanban order" +msgstr "" + +#: taiga/projects/userstories/models.py:92 +msgid "finish date" +msgstr "完了日" + +#: taiga/projects/userstories/models.py:107 +msgid "generated from issue" +msgstr "" + +#: taiga/projects/userstories/validators.py:43 +msgid "There's no user story with that id" +msgstr "" + +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 +msgid "" +"Invalid user story status id. The status must belong to the same project." +msgstr "" + +#: taiga/projects/userstories/validators.py:121 +msgid "Invalid milestone id. The milistone must belong to the same project." +msgstr "" + +#: taiga/projects/userstories/validators.py:136 +msgid "" +"Invalid user story ids. All stories must belong to the same project and, if " +"it exists, to the same status and milestone." +msgstr "" + +#: taiga/projects/userstories/validators.py:160 +msgid "The milestone isn't valid for the project" +msgstr "" + +#: taiga/projects/userstories/validators.py:170 +msgid "All the user stories must be from the same project" +msgstr "" + +#: taiga/projects/validators.py:63 +msgid "There's no project with that id" +msgstr "" + +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" + +#: taiga/projects/validators.py:155 +msgid "Invalid role for the project" +msgstr "" + +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 +msgid "The project owner must be admin." +msgstr "" + +#: taiga/projects/validators.py:195 +msgid "At least one user must be an active admin for this project." +msgstr "" + +#: taiga/projects/validators.py:240 +msgid "Invalid role ids. All roles must belong to the same project." +msgstr "" + +#: taiga/projects/validators.py:264 +msgid "Default options" +msgstr "" + +#: taiga/projects/validators.py:265 +msgid "User story's statuses" +msgstr "ユーザーストーリーステータス" + +#: taiga/projects/validators.py:266 +msgid "Points" +msgstr "点数" + +#: taiga/projects/validators.py:267 +msgid "Task's statuses" +msgstr "タスクステータス" + +#: taiga/projects/validators.py:268 +msgid "Issue's statuses" +msgstr "課題ステータス" + +#: taiga/projects/validators.py:269 +msgid "Issue's types" +msgstr "課題タイプ" + +#: taiga/projects/validators.py:270 +msgid "Priorities" +msgstr "優先度" + +#: taiga/projects/validators.py:271 +msgid "Severities" +msgstr "重要度" + +#: taiga/projects/validators.py:272 +msgid "Roles" +msgstr "権限" + +#: taiga/projects/votes/models.py:33 taiga/projects/votes/models.py:34 +#: taiga/projects/votes/models.py:58 +msgid "Votes" +msgstr "投票" + +#: taiga/projects/votes/models.py:57 +msgid "Vote" +msgstr "投票" + +#: taiga/projects/wiki/api.py:77 +msgid "'content' parameter is mandatory" +msgstr "" + +#: taiga/projects/wiki/api.py:80 +msgid "'project_id' parameter is mandatory" +msgstr "" + +#: taiga/projects/wiki/models.py:42 +msgid "last modifier" +msgstr "最終更新者" + +#: taiga/projects/wiki/models.py:75 +msgid "href" +msgstr "" + +#: taiga/timeline/signals.py:65 +msgid "Check the history API for the exact diff" +msgstr "" + +#: taiga/users/admin.py:39 +msgid "Project Member" +msgstr "プロジェクトメンバー" + +#: taiga/users/admin.py:40 +msgid "Project Members" +msgstr "プロジェクトメンバー" + +#: taiga/users/admin.py:50 +msgid "id" +msgstr "ID" + +#: 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:122 +msgid "Permissions" +msgstr "アクセス権" + +#: taiga/users/admin.py:123 +msgid "Restrictions" +msgstr "制限事項" + +#: taiga/users/admin.py:125 +msgid "Important dates" +msgstr "" + +#: taiga/users/api.py:132 +msgid "Duplicated email" +msgstr "重複メール" + +#: taiga/users/api.py:134 +msgid "Not valid email" +msgstr "無効なメール" + +#: taiga/users/api.py:172 +msgid "Invalid username or email" +msgstr "" + +#: taiga/users/api.py:181 +msgid "Mail sended successful!" +msgstr "" + +#: taiga/users/api.py:219 +msgid "Current password parameter needed" +msgstr "" + +#: taiga/users/api.py:222 +msgid "New password parameter needed" +msgstr "" + +#: taiga/users/api.py:225 +msgid "Invalid password length at least 6 charaters needed" +msgstr "" + +#: taiga/users/api.py:228 +msgid "Invalid current password" +msgstr "現在のパスワードが無効です" + +#: taiga/users/api.py:275 taiga/users/api.py:281 +msgid "" +"Invalid, are you sure the token is correct and you didn't use it before?" +msgstr "" + +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 +msgid "Invalid, are you sure the token is correct?" +msgstr "" + +#: taiga/users/models.py:98 +msgid "superuser status" +msgstr "管理者の状態" + +#: taiga/users/models.py:99 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" + +#: taiga/users/models.py:129 +msgid "username" +msgstr "ユーザー名" + +#: taiga/users/models.py:130 +msgid "" +"Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" +msgstr "" + +#: taiga/users/models.py:133 +msgid "Enter a valid username." +msgstr "有効なユーザー名を入力してください。" + +#: taiga/users/models.py:136 +msgid "active" +msgstr "有効" + +#: taiga/users/models.py:137 +msgid "" +"Designates whether this user should be treated as active. Unselect this " +"instead of deleting accounts." +msgstr "" + +#: taiga/users/models.py:143 +msgid "biography" +msgstr "経歴" + +#: taiga/users/models.py:146 +msgid "photo" +msgstr "写真" + +#: taiga/users/models.py:147 +msgid "date joined" +msgstr "参加日" + +#: taiga/users/models.py:149 +msgid "default language" +msgstr "既定の言語" + +#: taiga/users/models.py:151 +msgid "default theme" +msgstr "既定のテーマ" + +#: taiga/users/models.py:153 +msgid "default timezone" +msgstr "既定のタイムゾーン" + +#: taiga/users/models.py:155 +msgid "colorize tags" +msgstr "タグを色付け" + +#: taiga/users/models.py:160 +msgid "email token" +msgstr "Eメールトークン" + +#: taiga/users/models.py:162 +msgid "new email address" +msgstr "新しいEメールアドレス" + +#: taiga/users/models.py:169 +msgid "max number of owned private projects" +msgstr "非公開プロジェクトの最大数" + +#: taiga/users/models.py:172 +msgid "max number of owned public projects" +msgstr "公開プロジェクトの最大数" + +#: taiga/users/models.py:175 +msgid "max number of memberships for each owned private project" +msgstr "非公開プロジェクトごとのメンバーの最大数" + +#: taiga/users/models.py:179 +msgid "max number of memberships for each owned public project" +msgstr "公開プロジェクトごとのメンバーの最大数" + +#: taiga/users/models.py:307 +msgid "permissions" +msgstr "アクセス権" + +#: taiga/users/services.py:51 taiga/users/services.py:68 +msgid "Username or password does not matches user." +msgstr "" + +#: taiga/users/templates/emails/change_email-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Change your email

\n" +"

Hello %(full_name)s,
please confirm your email

\n" +" Confirm " +"email\n" +"

You can ignore this message if you did not request.

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/users/templates/emails/change_email-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(full_name)s, please confirm your email\n" +"\n" +"%(url)s\n" +"\n" +"You can ignore this message if you did not request.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/users/templates/emails/change_email-subject.jinja:1 +msgid "[Taiga] Change email" +msgstr "" + +#: taiga/users/templates/emails/password_recovery-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Recover your password

\n" +"

Hello %(full_name)s,
you asked to recover your password

\n" +" Recover your password\n" +"

You can ignore this message if you did not request.

\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/users/templates/emails/password_recovery-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(full_name)s, you asked to recover your password\n" +"\n" +"%(url)s\n" +"\n" +"You can ignore this message if you did not request.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/users/templates/emails/password_recovery-subject.jinja:1 +msgid "[Taiga] Password recovery" +msgstr "" + +#: taiga/users/templates/emails/registered_user-body-html.jinja:6 +msgid "" +"\n" +"
\n" +" " +msgstr "" + +#: taiga/users/templates/emails/registered_user-body-html.jinja:23 +#, python-format +msgid "" +"\n" +" You may remove your account from this service clicking " +"here\n" +" " +msgstr "" + +#: taiga/users/templates/emails/registered_user-body-text.jinja:1 +msgid "" +"\n" +"Thank you for registering in Taiga\n" +"\n" +"We hope you enjoy it\n" +"\n" +"We built Taiga because we wanted the project management tool that sits open " +"on our computers all day long, to serve as a continued reminder of why we " +"love to collaborate, code and design.\n" +"\n" +"We built it to be beautiful, elegant, simple to use and fun - without " +"forsaking flexibility and power.\n" +"\n" +"--\n" +"The taiga Team\n" +msgstr "" + +#: taiga/users/templates/emails/registered_user-body-text.jinja:13 +#, python-format +msgid "" +"\n" +"You may remove your account from this service: %(url)s\n" +msgstr "" + +#: taiga/users/templates/emails/registered_user-subject.jinja:1 +msgid "You've been Taigatized!" +msgstr "" + +#: taiga/users/validators.py:45 +msgid "invalid" +msgstr "無効" + +#: taiga/users/validators.py:56 +msgid "Invalid username. Try with a different one." +msgstr "" + +#: taiga/userstorage/api.py:53 +msgid "" +"Duplicate key value violates unique constraint. Key '{}' already exists." +msgstr "" + +#: taiga/userstorage/models.py:32 +msgid "key" +msgstr "" + +#: taiga/webhooks/models.py:30 taiga/webhooks/models.py:40 +msgid "URL" +msgstr "URL" + +#: taiga/webhooks/models.py:31 +msgid "secret key" +msgstr "秘密鍵" + +#: taiga/webhooks/models.py:41 +msgid "status code" +msgstr "ステータスコード" + +#: taiga/webhooks/models.py:42 +msgid "request data" +msgstr "リクエストデータ" + +#: taiga/webhooks/models.py:43 +msgid "request headers" +msgstr "リクエストヘッダー" + +#: taiga/webhooks/models.py:44 +msgid "response data" +msgstr "レスポンスデータ" + +#: taiga/webhooks/models.py:45 +msgid "response headers" +msgstr "レスポンスヘッダー" + +#: taiga/webhooks/models.py:46 +msgid "duration" +msgstr "期限" diff --git a/taiga/locale/ko/LC_MESSAGES/django.po b/taiga/locale/ko/LC_MESSAGES/django.po new file mode 100644 index 00000000..360d24ff --- /dev/null +++ b/taiga/locale/ko/LC_MESSAGES/django.po @@ -0,0 +1,4763 @@ +# taiga-back.taiga. +# Copyright (C) 2014-2017 Taiga Dev Team +# This file is distributed under the same license as the taiga-back package. +# +# Translators: +# Jonghyuk Baik , 2017 +# 안민규 (luasenvy) , 2017 +# mcsong , 2015 +# mcsong , 2015 +msgid "" +msgstr "" +"Project-Id-Version: taiga-back\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:55+0000\n" +"Last-Translator: Taiga Dev Team \n" +"Language-Team: Korean (http://www.transifex.com/taiga-agile-llc/taiga-back/" +"language/ko/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ko\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: taiga/auth/api.py:74 +msgid "Public register is disabled." +msgstr "공개 가입이 비활성화 되었습니다." + +#: taiga/auth/api.py:101 +msgid "invalid register type" +msgstr "유효하지 않은 가입 형태" + +#: taiga/auth/api.py:117 +msgid "invalid login type" +msgstr "유효하지 않은 로그인 형태" + +#: taiga/auth/services.py:76 +msgid "Username is already in use." +msgstr "이미 사용중인 아이디입니다." + +#: taiga/auth/services.py:79 +msgid "Email is already in use." +msgstr "이미 사용중인 이메일입니다. " + +#: taiga/auth/services.py:95 +msgid "Token not matches any valid invitation." +msgstr "토큰과 일치하는 초대가 없습니다." + +#: taiga/auth/services.py:123 +msgid "User is already registered." +msgstr "이미 등록된 사용자입니다. " + +#: taiga/auth/services.py:140 +msgid "This user is already a member of the project." +msgstr "이 사용자는 이미 프로젝트의 회원입니다." + +#: taiga/auth/services.py:164 +msgid "Error on creating new user." +msgstr "사용자를 생성에 에러가 발생했습니다." + +#: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 +msgid "Invalid token" +msgstr "유효하지 않은 토큰" + +#: taiga/auth/validators.py:37 taiga/users/validators.py:44 +msgid "invalid username" +msgstr "유효하지 않은 아이디" + +#: taiga/auth/validators.py:42 taiga/users/validators.py:50 +msgid "" +"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'" +msgstr "필수. 255자 이하. 문자, 숫자 그리고 /./-/_ 만 사용 가능합니다'" + +#: taiga/base/api/fields.py:294 +msgid "This field is required." +msgstr "필수 입력필드입니다." + +#: taiga/base/api/fields.py:295 taiga/base/api/relations.py:337 +msgid "Invalid value." +msgstr "유효하지 않은 값." + +#: taiga/base/api/fields.py:484 +#, python-format +msgid "'%s' value must be either True or False." +msgstr "'%s' 값은 반드시 True 또는 False이어야 합니다. " + +#: taiga/base/api/fields.py:549 +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "" +"문자, 숫자, 언더 스코어(_)또는 하이픈(-)으로 이루어진 올바른 '슬러그'를 입력" +"하세요." + +#: taiga/base/api/fields.py:564 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" +"%(value)s 는 사용 가능한 선택 중 하나가 아닙니다. 올바른 것을 선택하세요." + +#: taiga/base/api/fields.py:638 +msgid "You email domain is not allowed" +msgstr "당신의 이메일 도메인은 허용되지 않았" + +#: taiga/base/api/fields.py:647 +msgid "Enter a valid email address." +msgstr "이메일 주소를 입력하세요." + +#: taiga/base/api/fields.py:689 +#, python-format +msgid "Date has wrong format. Use one of these formats instead: %s" +msgstr "" +"날짜 형식이 잘못되었습니다. %s 대신에 아래의 포맷중에 하나를 사용해 주세요." + +#: taiga/base/api/fields.py:753 +#, python-format +msgid "Datetime has wrong format. Use one of these formats instead: %s" +msgstr "" +"날짜와 시간의 형식이 잘못되었습니다. %s 대신에 아래의 포맷중에 하나를 사용해 " +"주세요." + +#: taiga/base/api/fields.py:823 +#, python-format +msgid "Time has wrong format. Use one of these formats instead: %s" +msgstr "" +"시간 형식이 잘못되었습니다. %s 대신에 아래의 포맷중에 하나를 사용해 주세요." + +#: taiga/base/api/fields.py:880 +msgid "Enter a whole number." +msgstr "번호 전체를 입력하세요" + +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "이 값은 %(limit_value)s 보다 작거나 같아야 합니다." + +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" +"이 값은 %(limit_value)s 보다 크거나 같아야 합니다.Ensure this value is " +"greater than or equal to " + +#: taiga/base/api/fields.py:912 +#, python-format +msgid "\"%s\" value must be a float." +msgstr "\"%s\"" + +#: taiga/base/api/fields.py:933 +msgid "Enter a number." +msgstr "숫자를 입력하세요." + +#: taiga/base/api/fields.py:936 +#, python-format +msgid "Ensure that there are no more than %s digits in total." +msgstr "총계는 %s 자릿수가 넘지 않아야 합니다." + +#: taiga/base/api/fields.py:937 +#, python-format +msgid "Ensure that there are no more than %s decimal places." +msgstr "%s 자리 이하 여야합니다." + +#: taiga/base/api/fields.py:938 +#, python-format +msgid "Ensure that there are no more than %s digits before the decimal point." +msgstr "소수점 앞이 %s 자리 이하인지 확인하십시오." + +#: taiga/base/api/fields.py:1005 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "제출된 파일이 없습니다. " + +#: taiga/base/api/fields.py:1006 +msgid "No file was submitted." +msgstr "제출한 파일이 없습니다." + +#: taiga/base/api/fields.py:1007 +msgid "The submitted file is empty." +msgstr "전송된 파일이 비어있습니다." + +#: taiga/base/api/fields.py:1008 +#, python-format +msgid "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr "이 파일 이름의 %(max)d 자 (%(length)d)를 초과하지 않도록하십시오." + +#: taiga/base/api/fields.py:1009 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "파일을 제출하거나 모두 지우기 체크박스를 선택하십시오." + +#: taiga/base/api/fields.py:1049 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"유효한 이미지를 업로드하십시오. 업로드 한 파일이 이미지도 아니며 손상된 이미" +"지 또한 아닙니다." + +#: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 +msgid "Blocked element" +msgstr "차단된 엘리먼트" + +#: taiga/base/api/pagination.py:228 +msgid "Page is not 'last', nor can it be converted to an int." +msgstr "페이지가 '마지막'이 아니며 int로 변환 될 수도 없습니다." + +#: taiga/base/api/pagination.py:232 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "잘못된 페이지 (%(page_number)s): %(message)s" + +#: taiga/base/api/permissions.py:65 +msgid "Invalid permission definition." +msgstr "권한 정의가 잘못되었습니다." + +#: taiga/base/api/relations.py:247 +#, python-format +msgid "Invalid pk '%s' - object does not exist." +msgstr "잘못된 pk '%s' - 객체가 존재하지 않습니다." + +#: taiga/base/api/relations.py:248 +#, python-format +msgid "Incorrect type. Expected pk value, received %s." +msgstr "pk값을 받을거라 예상했지만 %s 를 받음. 정확하지 않은 형식" + +#: taiga/base/api/relations.py:336 +#, python-format +msgid "Object with %s=%s does not exist." +msgstr "%s = %s의 객체가 존재하지 않습니다." + +#: taiga/base/api/relations.py:372 +msgid "Invalid hyperlink - No URL match" +msgstr "잘못된 하이퍼 링크 - 일치하는 URL 없음" + +#: taiga/base/api/relations.py:373 +msgid "Invalid hyperlink - Incorrect URL match" +msgstr "잘못된 하이퍼 링크 - 잘못된 URL 일치" + +#: taiga/base/api/relations.py:374 +msgid "Invalid hyperlink due to configuration error" +msgstr "구성 오류로 인한 잘못된 하이퍼 링크" + +#: taiga/base/api/relations.py:375 +msgid "Invalid hyperlink - object does not exist." +msgstr "유효하지 않은 하이퍼링크 - 객체가 존재하지 않음." + +#: taiga/base/api/relations.py:376 +#, python-format +msgid "Incorrect type. Expected url string, received %s." +msgstr "url 문자를 받을거라 예상했지만 %s 를 받음. 정확하지 않은 형식" + +#: taiga/base/api/serializers.py:324 +msgid "Invalid data" +msgstr "데이터가 유효하지 않습니다." + +#: taiga/base/api/serializers.py:416 +msgid "No input provided" +msgstr "제공된 입력 없음" + +#: taiga/base/api/serializers.py:579 +msgid "Cannot create a new item, only existing items may be updated." +msgstr "새 항목을 만들 수 없으며 기존 항목 만 수정 할 수 있습니다." + +#: taiga/base/api/serializers.py:590 +msgid "Expected a list of items." +msgstr "예상되는 항목 목록." + +#: taiga/base/api/views.py:126 +msgid "Not found" +msgstr "찾지 못함" + +#: taiga/base/api/views.py:129 +msgid "Permission denied" +msgstr "권한이 없습니다" + +#: taiga/base/api/views.py:492 +msgid "Server application error" +msgstr "서버 애플리케이션 에러" + +#: taiga/base/connectors/exceptions.py:26 +msgid "Connection error." +msgstr "연결 에러" + +#: taiga/base/exceptions.py:79 +msgid "Malformed request." +msgstr "형식에 어긋난 요청." + +#: taiga/base/exceptions.py:84 +msgid "Incorrect authentication credentials." +msgstr "정확하지 않은 인증 자격 증명." + +#: taiga/base/exceptions.py:89 +msgid "Authentication credentials were not provided." +msgstr "인증 자격 증명이 제공되지 않았습니다." + +#: taiga/base/exceptions.py:94 +msgid "You do not have permission to perform this action." +msgstr "이 작업을 할 권한이 없습니다." + +#: taiga/base/exceptions.py:99 +#, python-format +msgid "Method '%s' not allowed." +msgstr "메서드 '%s' 가 허용되지 않았습니다." + +#: taiga/base/exceptions.py:107 +msgid "Could not satisfy the request's Accept header" +msgstr "요청의 Accept 헤더를 만족시킬 수 없습니다." + +#: taiga/base/exceptions.py:116 +#, python-format +msgid "Unsupported media type '%s' in request." +msgstr "요청시 지원되지 않는 미디어 유형 '%s'" + +#: taiga/base/exceptions.py:124 +msgid "Request was throttled." +msgstr "요청이 제한되었습니다." + +#: taiga/base/exceptions.py:125 +#, python-format +msgid "Expected available in %d second%s." +msgstr "%d 초%s에서 사용할 수 있습니다." + +#: taiga/base/exceptions.py:139 +msgid "Unexpected error" +msgstr "알 수 없는 에러" + +#: taiga/base/exceptions.py:151 +msgid "Not found." +msgstr "찾지 못함." + +#: taiga/base/exceptions.py:156 +msgid "Method not supported for this endpoint." +msgstr "이 끝점에서 지원되지 않는 메서드입니다." + +#: taiga/base/exceptions.py:164 taiga/base/exceptions.py:172 +msgid "Wrong arguments." +msgstr "잘못된 인수" + +#: taiga/base/exceptions.py:176 +msgid "Data validation error" +msgstr "데이터 검증 에러" + +#: taiga/base/exceptions.py:188 +msgid "Integrity Error for wrong or invalid arguments" +msgstr "잘못되었거나 유효하지 않은 인수에 대한 무결성 오류" + +#: taiga/base/exceptions.py:195 +msgid "Precondition error" +msgstr "필수조건 오류" + +#: taiga/base/exceptions.py:219 +msgid "No room left for more projects." +msgstr "더 많은 프로젝트를 위한 공간이 남지 않았습니다." + +#: taiga/base/filters.py:81 taiga/base/filters.py:463 +msgid "Error in filter params types." +msgstr "필터 매개 변수 유형에 오류가 있습니다." + +#: taiga/base/filters.py:136 taiga/base/filters.py:243 +#: taiga/projects/filters.py:64 +msgid "'project' must be an integer value." +msgstr "'project'는 반드시 정수이어야 합니다." + +#: taiga/base/templates/emails/base-body-html.jinja:6 +msgid "Taiga" +msgstr "타이가" + +#: taiga/base/templates/emails/base-body-html.jinja:421 +#: taiga/base/templates/emails/hero-body-html.jinja:380 +#: taiga/base/templates/emails/updates-body-html.jinja:442 +msgid "Follow us on Twitter" +msgstr "트위터 팔로우" + +#: taiga/base/templates/emails/base-body-html.jinja:421 +#: taiga/base/templates/emails/hero-body-html.jinja:380 +#: taiga/base/templates/emails/updates-body-html.jinja:442 +msgid "Twitter" +msgstr "트위터" + +#: taiga/base/templates/emails/base-body-html.jinja:422 +#: taiga/base/templates/emails/hero-body-html.jinja:381 +#: taiga/base/templates/emails/updates-body-html.jinja:443 +msgid "Get the code on GitHub" +msgstr "GitHub에서 코드 가져오기" + +#: taiga/base/templates/emails/base-body-html.jinja:422 +#: taiga/base/templates/emails/hero-body-html.jinja:381 +#: taiga/base/templates/emails/updates-body-html.jinja:443 +msgid "GitHub" +msgstr "GitHub" + +#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/hero-body-html.jinja:382 +#: taiga/base/templates/emails/updates-body-html.jinja:444 +msgid "Visit our website" +msgstr "웹 사이트 방문" + +#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/hero-body-html.jinja:382 +#: taiga/base/templates/emails/updates-body-html.jinja:444 +msgid "Taiga.io" +msgstr "Taiga.io" + +#: taiga/base/templates/emails/base-body-html.jinja:438 +#: taiga/base/templates/emails/hero-body-html.jinja:397 +#: taiga/base/templates/emails/updates-body-html.jinja:459 +#, python-format +msgid "" +"\n" +" Taiga Support:\n" +" %(support_url)s\n" +"
\n" +" Contact us:\n" +" \n" +" %(support_email)s\n" +" \n" +"
\n" +" Mailing list:\n" +" \n" +" %(mailing_list_url)s\n" +" \n" +" " +msgstr "" +"\n" +" 타이가 지원:\n" +" %(support_url)s\n" +"
\n" +" 연락처:\n" +" \n" +" %(support_email)s\n" +" \n" +"
\n" +" 메일링 목록:\n" +" \n" +" %(mailing_list_url)s\n" +" \n" +" " + +#: taiga/base/templates/emails/hero-body-html.jinja:6 +msgid "You have been Taigatized" +msgstr "당신은 타이가화 되었습니다." + +#: taiga/base/templates/emails/hero-body-html.jinja:359 +msgid "" +"\n" +"

You have been Taigatized!" +"

\n" +"

Welcome to Taiga, an Open " +"Source, Agile Project Management Tool

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

당신은 타이가화 되었습니" +"다!

\n" +"

타이가에 오신것을 환영합니" +"다, 오픈소스, 애자일 프로젝트 관리 도구

\n" +" " + +#: taiga/base/templates/emails/updates-body-html.jinja:6 +msgid "[Taiga] Updates" +msgstr "[타이가] 업데이트" + +#: taiga/base/templates/emails/updates-body-html.jinja:417 +msgid "Updates" +msgstr "업데이트" + +#: taiga/base/templates/emails/updates-body-html.jinja:423 +#, python-format +msgid "" +"\n" +"

comment:" +"

\n" +"

" +"%(comment)s

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

댓글:" +"

\n" +"

" +"%(comment)s

\n" +" " + +#: taiga/base/templates/emails/updates-body-text.jinja:6 +#, python-format +msgid "" +"\n" +" Comment: %(comment)s\n" +" " +msgstr "" +"\n" +" 댓글: %(comment)s\n" +" " + +#: taiga/export_import/api.py:127 +msgid "We needed at least one role" +msgstr "최소 한 개의 역할이 필요합니다." + +#: taiga/export_import/api.py:323 +msgid "Needed dump file" +msgstr "덤프 파일이 필요함" + +#: taiga/export_import/api.py:333 +msgid "Invalid dump format" +msgstr "잘못된 덤프 포맷" + +#: taiga/export_import/services/store.py:718 +#: taiga/export_import/services/store.py:736 +msgid "error importing project data" +msgstr "프로젝트 데이터를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:743 +msgid "error importing roles" +msgstr "역할을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:748 +msgid "error importing memberships" +msgstr "회원을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:759 +msgid "error importing lists of project attributes" +msgstr "프로젝트 속성 목록을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:763 +msgid "error importing default project attributes values" +msgstr "기본 프로젝트 속성값을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:774 +msgid "error importing custom attributes" +msgstr "사용자 정의 속성을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:778 +msgid "error importing sprints" +msgstr "스프린트를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:782 +msgid "error importing issues" +msgstr "이슈를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:786 +msgid "error importing user stories" +msgstr "유저 스토리를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:790 +msgid "error importing epics" +msgstr "에픽을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:794 +msgid "error importing tasks" +msgstr "태스크를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:798 +msgid "error importing wiki pages" +msgstr "위키 페이지를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:802 +msgid "error importing wiki links" +msgstr "위키 링크를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:806 +msgid "error importing tags" +msgstr "태그를 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:810 +msgid "error importing timelines" +msgstr "타임라인을 가져오는 중 오류가 발생하였습니다." + +#: taiga/export_import/services/store.py:832 +msgid "unexpected error importing project" +msgstr "예상치 못한 프로젝트 가져 오기 오류가 발생하였습니다." + +#: taiga/export_import/tasks.py:62 taiga/export_import/tasks.py:63 +msgid "Error generating project dump" +msgstr "프로젝트 덤프를 생성하는 중 오류가 발생하였습니다." + +#: taiga/export_import/tasks.py:91 +#, python-brace-format +msgid "" +"\n" +"\n" +"Error loading dump by {user_full_name} <{user_email}>:\"\n" +"\n" +"\n" +"REASON:\n" +"-------\n" +"{reason}\n" +"\n" +"DETAILS:\n" +"--------\n" +"{details}\n" +"\n" +"TRACE ERROR:\n" +"------------" +msgstr "" +"\n" +"\n" +"{user_full_name} <{user_email}> 덤프를 로딩하던중 오류가 발생하였습니다.:\"\n" +"\n" +"\n" +"이유:\n" +"-------\n" +"{reason}\n" +"\n" +"상세정보:\n" +"--------\n" +"{details}\n" +"\n" +"에러 추적:\n" +"------------" + +#: taiga/export_import/tasks.py:120 +msgid "Error loading project dump" +msgstr "프로젝트 덤프를 로드하는 중 오류가 발생하였습니다." + +#: taiga/export_import/tasks.py:121 +msgid "Error loading your project dump file" +msgstr "프로젝트 덤프 파일을 로드하는 중 오류가 발생하였습니다." + +#: taiga/export_import/tasks.py:135 +msgid " -- no detail info --" +msgstr "-- 상세 정보가 없습니다. --" + +#: taiga/export_import/templates/emails/dump_project-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Project dump generated

\n" +"

Hello %(user)s,

\n" +"

Your dump from project %(project)s has been correctly generated.\n" +"

You can download it here:

\n" +" Download the dump file\n" +"

This file will be deleted on %(deletion_date)s.

\n" +"

The Taiga Team

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

프로젝트 덤프가 생성되었습니다.

\n" +"

안녕하세요 %(user)s 님,

\n" +"

%(project)s 프로젝트의 덤프가 생성되었습니다.

\n" +"

여기서 다운로드 하실 수 있습니다.:

\n" +" 덤프 파" +"일 다운로드\n" +"

이 파일은 %(deletion_date)s 에 삭제됩니다.

\n" +"

타이가 팀

\n" +" " + +#: taiga/export_import/templates/emails/dump_project-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your dump from project %(project)s has been correctly generated. You can " +"download it here:\n" +"\n" +"%(url)s\n" +"\n" +"This file will be deleted on %(deletion_date)s.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"안녕하세요 %(user)s 님,\n" +"\n" +"%(project)s 프로젝트의 덤프가 생성되었습니다. 여기서 다운로드 하실 수 있습니" +"다.:\n" +"\n" +"%(url)s\n" +"\n" +"이 파일은 %(deletion_date)s 에 삭제됩니다.\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/export_import/templates/emails/dump_project-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your project dump has been generated" +msgstr "[%(project)s] 프로젝트 덤프가 생성되었습니다." + +#: taiga/export_import/templates/emails/export_error-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

%(error_message)s

\n" +"

Hello %(user)s,

\n" +"

Your project %(project)s has not been exported correctly.

\n" +"

The Taiga system administrators have been informed.
Please, try " +"it again or contact with the support team at\n" +" %(support_email)s

\n" +"

The Taiga Team

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

%(error_message)s

\n" +"

안녕하세요 %(user)s 님,

\n" +"

%(project)s 프로젝트를 내보낼 수 없었습니다.

\n" +"

타이가 시스템 관리자에게 알렸습니다.
다시 시도하거나 지원팀에게 " +"문의해주세요.\n" +" %(support_email)s

\n" +"

타이가 팀

\n" +" " + +#: taiga/export_import/templates/emails/export_error-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"%(error_message)s\n" +"Your project %(project)s has not been exported correctly.\n" +"\n" +"The Taiga system administrators have been informed.\n" +"\n" +"Please, try it again or contact with the support team at %(support_email)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"안녕하세요 %(user)s 님,\n" +"\n" +"%(error_message)s\n" +"%(project)s 프로젝트를 내보낼 수 없었습니다.\n" +"\n" +"타이가 시스템 관리자에게 알렸습니다.\n" +"\n" +"다시 시도하거나 지원팀에게 문의해주세요. %(support_email)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/export_import/templates/emails/export_error-subject.jinja:1 +#, python-format +msgid "[%(project)s] %(error_subject)s" +msgstr "[%(project)s] %(error_subject)s" + +#: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

%(error_message)s

\n" +"

Hello %(user)s,

\n" +"

Your project has not been importer correctly.

\n" +"

The Taiga system administrators have been informed.
Please, try " +"it again or contact with the support team at\n" +" %(support_email)s

\n" +"

The Taiga Team

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

%(error_message)s

\n" +"

안녕하세요 %(user)s 님,

\n" +"

프로젝트를 가져올 수 없었습니다.

\n" +"

타이가 시스템 관리자에게 알렸습니다.
다시 시도하거나 지원팀에게 " +"문의해주세요.\n" +" %(support_email)s

\n" +"

타이가 팀

\n" +" " + +#: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"%(error_message)s\n" +"\n" +"Your project has not been importer correctly.\n" +"\n" +"The Taiga system administrators have been informed.\n" +"\n" +"Please, try it again or contact with the support team at %(support_email)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"안녕하세요 %(user)s 님\n" +"\n" +"%(error_message)s\n" +"\n" +"프로젝트를 가져올 수 없었습니다.\n" +"\n" +"타이가 시스템 관리자에게 알렸습니다.\n" +"\n" +"다시 시도하거나 지원팀에게 문의해주세요. %(support_email)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 +#, python-format +msgid "[Taiga] %(error_subject)s" +msgstr "[타이가] %(error_subject)s" + +#: taiga/export_import/templates/emails/load_dump-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Project dump imported

\n" +"

Hello %(user)s,

\n" +"

Your project dump has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

프로젝트 덤프 가져오기 완료

\n" +"

안녕하세요 %(user)s 님,

\n" +"

프로젝트 덤프를 가져오기 완료하였습니다.

\n" +" %(project)s 프로젝트로 가기\n" +"

타이가 팀

\n" +" " + +#: taiga/export_import/templates/emails/load_dump-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your project dump has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"안녕하세요 %(user)s 님,\n" +"\n" +"프로젝트 덤프를 가져오기 완료하였습니다.\n" +"\n" +"%(project)s 프로젝트를 확인하실 수 있습니다.:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/export_import/templates/emails/load_dump-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your project dump has been imported" +msgstr "[%(project)s] 프로젝트 덤프를 가져오기 하였습니다." + +#: taiga/export_import/validators/fields.py:144 +msgid "{}=\"{}\" not found in this project" +msgstr "{}=\"{}\" 이 프로젝트에서 찾을 수 없습니다." + +#: taiga/export_import/validators/validators.py:150 +#: taiga/projects/custom_attributes/validators.py:109 +msgid "Invalid content. It must be {\"key\": \"value\",...}" +msgstr "잘못된 컨텐츠. 반드시 {\"key\": \"value\",...} 형태이어야 합니다." + +#: taiga/export_import/validators/validators.py:165 +#: taiga/projects/custom_attributes/validators.py:124 +msgid "It contain invalid custom fields." +msgstr "유효하지 않은 사용자 정의 필드를 포함하고 있습니다." + +#: taiga/export_import/validators/validators.py:245 +#: taiga/projects/validators.py:54 +msgid "Name duplicated for the project" +msgstr "프로젝트 이름이 중복되었습니다." + +#: taiga/external_apps/api.py:43 taiga/external_apps/api.py:70 +#: taiga/external_apps/api.py:77 +msgid "Authentication required" +msgstr "인증 필요" + +#: taiga/external_apps/models.py:35 +#: taiga/projects/custom_attributes/models.py:36 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 +msgid "name" +msgstr "이름" + +#: taiga/external_apps/models.py:37 +msgid "Icon url" +msgstr "아이콘 url" + +#: taiga/external_apps/models.py:38 +msgid "web" +msgstr "웹" + +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 +#: taiga/projects/custom_attributes/models.py:37 +#: taiga/projects/epics/models.py:56 +#: taiga/projects/history/templatetags/functions.py:25 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 +#: taiga/projects/userstories/models.py:95 +msgid "description" +msgstr "설명" + +#: taiga/external_apps/models.py:41 +msgid "Next url" +msgstr "다음 url" + +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 +msgid "user" +msgstr "유저" + +#: taiga/external_apps/models.py:59 +msgid "application" +msgstr "어플리케이션" + +#: taiga/feedback/models.py:25 taiga/users/models.py:140 +msgid "full name" +msgstr "성명" + +#: taiga/feedback/models.py:27 taiga/users/models.py:135 +msgid "email address" +msgstr "이메일 주소" + +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 +msgid "comment" +msgstr "댓글" + +#: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 +#: taiga/projects/custom_attributes/models.py:46 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 +#: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 +#: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 +#: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 +msgid "created date" +msgstr "생성된 날짜" + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Feedback

\n" +"

Taiga has received feedback from %(full_name)s <%(email)s>

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

피드백

\n" +"

타이가가 %(full_name)s <%(email)s>의 피드백을 받았습니다.

\n" +" " + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:9 +#, python-format +msgid "" +"\n" +"

Comment

\n" +"

%(comment)s

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

댓글

\n" +"

%(comment)s

\n" +" " + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 +#: taiga/projects/admin.py:106 taiga/users/admin.py:120 +msgid "Extra info" +msgstr "추가 정보" + +#: taiga/feedback/templates/emails/feedback_notification-body-text.jinja:1 +#, python-format +msgid "" +"---------\n" +"- From: %(full_name)s <%(email)s>\n" +"---------\n" +"- Comment:\n" +"%(comment)s\n" +"---------" +msgstr "" +"---------\n" +"- 보낸이: %(full_name)s <%(email)s>\n" +"---------\n" +"- 댓글:\n" +"%(comment)s\n" +"---------" + +#: taiga/feedback/templates/emails/feedback_notification-body-text.jinja:8 +msgid "- Extra info:" +msgstr "- 추가 정보:" + +#: taiga/feedback/templates/emails/feedback_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Feedback from %(full_name)s <%(email)s>\n" +msgstr "" +"\n" +"[타이가] %(full_name)s <%(email)s>의 피드백\n" + +#: taiga/hooks/api.py:54 +msgid "The payload is not a valid json" +msgstr "페이로드의 json이 유효하지 않습니다." + +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 +msgid "The project doesn't exist" +msgstr "프로젝트가 존재하지 않습니다." + +#: taiga/hooks/api.py:66 +msgid "Bad signature" +msgstr "좋지 않은 기호" + +#: taiga/hooks/event_hooks.py:66 +#, python-brace-format +msgid "" +"[@{user_name}]({user_url} \"See @{user_name}'s {platform} profile\") says in " +"[{platform}#{number}]({comment_url} \"Go to comment\"):\n" +"\n" +"\"{comment_message}\"" +msgstr "" +"[@{user_name}]({user_url} \"@{user_name}'s {platform} 프로필 보기\") 님의 " +"[{platform}#{number}]({comment_url} \"댓글로 가기\") 플랫폼에서 한 말:\n" +"\n" +"\"{comment_message}\"" + +#: taiga/hooks/event_hooks.py:71 +#, python-brace-format +msgid "" +"Comment From {platform}:\n" +"\n" +"> {comment_message}" +msgstr "" +"{platform} 플랫폼으로부터의 댓글:\n" +"\n" +"> {comment_message}" + +#: taiga/hooks/event_hooks.py:84 +msgid "Invalid issue comment information" +msgstr "이슈 댓글 정보가 유효하지 않습니다." + +#: taiga/hooks/event_hooks.py:103 +#, python-brace-format +msgid "" +"Issue created by [@{user_name}]({user_url} \"See @{user_name}'s {platform} " +"profile\") from [{platform}#{number}]({url} \"Go to issue\")." +msgstr "" +"[@{user_name}]({user_url} \"@{user_name}'s {platform} 프로필 보기\") 님의 " +"[{platform}#{number}]({url} \"이슈로 가기\"). 플랫폼으로부터 이슈가 생성되었" +"습니다." + +#: taiga/hooks/event_hooks.py:107 +#, python-brace-format +msgid "Issue created from {platform}." +msgstr "{platform} 플랫폼으로부터 이슈가 생성되었습니다." + +#: taiga/hooks/event_hooks.py:120 +msgid "Invalid issue information" +msgstr "이슈 정보가 유효하지 않습니다." + +#: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 +msgid "unknown user" +msgstr "알수 없는 유저" + +#: taiga/hooks/event_hooks.py:156 +#, python-brace-format +msgid "" +"{user_text} changed the status from [{platform} commit]({commit_url} \"See " +"commit '{commit_id} - {commit_message}'\")\n" +"\n" +" - Status: **{src_status}** → **{dst_status}**" +msgstr "" +"{user_text} [{platform} commit]({commit_url} \"'{commit_id} - " +"{commit_message}' 댓글 보기\") 의 상태가 변경되었습니다.\n" +"\n" +" - 상태: **{src_status}** → **{dst_status}**" + +#: taiga/hooks/event_hooks.py:161 +#, python-brace-format +msgid "" +"Changed status from {platform} commit.\n" +"\n" +" - Status: **{src_status}** → **{dst_status}**" +msgstr "" +"{platform} 커밋으로부터 상태가 수정되었습니다..\n" +"\n" +" - 상태: **{src_status}** → **{dst_status}**" + +#: taiga/hooks/event_hooks.py:179 +#, python-brace-format +msgid "" +"This {type_name} has been mentioned by {user_text} in the [{platform} commit]" +"({commit_url} \"See commit '{commit_id} - {commit_message}'\") " +"\"{commit_message}\"" +msgstr "" +"[{platform} 커밋] {user_text} ({commit_url} \"'{commit_id} - " +"{commit_message}' 커밋 보기\") \"{commit_message}\" {type_name} 언급됨" + +#: taiga/hooks/event_hooks.py:184 +#, python-brace-format +msgid "" +"This issue has been mentioned in the {platform} commit \"{commit_message}\"" +msgstr "{platform} 커밋에서 이 이슈가 언급되었습니다. \"{commit_message}\"" + +#: taiga/hooks/event_hooks.py:206 +msgid "The referenced element doesn't exist" +msgstr "참조된 엘리먼트가 존재하지 않습니다." + +#: taiga/hooks/event_hooks.py:222 +msgid "The status doesn't exist" +msgstr "상태가 존재하지 않습니다." + +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "프로젝트 값이 필요합니다." + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "Asana API 요청이 올바르지 않습니다." + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "Asana API에 요청하는 중 실패하였습니다." + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "Code 값이 필요합니다." + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "Asana 프로젝트를 가져오는 중 에러가 발생하였습니다." + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "인증 정보가 유효하지 않습니다." + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "서드 파티 서비스에 실패하였습니다." + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "GitHub프로젝트를 가져오는 중 에러가 발생하였습니다." + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "url값이 필요합니다." + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "유효하지 않은 프로젝트_유형 {}" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "인증 토큰이 유효하지 않거나 만료되었습니다." + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "Jira 프로젝트를 가져오는 중 에러가 발생하였습니다." + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "PivotalTracker 프로젝트를 가져오는 중 에러가 발생하였습니다." + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "유효하지 않은 요청: %s 의 %s" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "인증되지 않음: %s 의 %s" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "자원 사용 불가: %s 의 %s" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "Trello 프로젝트를 가져오는 중 에러가 발생하였습니다." + +#: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 +msgid "View project" +msgstr "프로젝트 보기" + +#: taiga/permissions/choices.py:24 taiga/permissions/choices.py:36 +msgid "View milestones" +msgstr "마일스톤 보기" + +#: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 +msgid "View epic" +msgstr "에픽 보기" + +#: taiga/permissions/choices.py:26 +msgid "View user stories" +msgstr "유저 스토리 보기" + +#: taiga/permissions/choices.py:27 taiga/permissions/choices.py:53 +msgid "View tasks" +msgstr "태스크 보기" + +#: taiga/permissions/choices.py:28 taiga/permissions/choices.py:59 +msgid "View issues" +msgstr "이슈 보기" + +#: taiga/permissions/choices.py:29 taiga/permissions/choices.py:65 +msgid "View wiki pages" +msgstr "위키 페이지 보기" + +#: taiga/permissions/choices.py:30 taiga/permissions/choices.py:71 +msgid "View wiki links" +msgstr "위키 링크 보기" + +#: taiga/permissions/choices.py:37 +msgid "Add milestone" +msgstr "마일스톤 추가" + +#: taiga/permissions/choices.py:38 +msgid "Modify milestone" +msgstr "마일스톤 수정" + +#: taiga/permissions/choices.py:39 +msgid "Delete milestone" +msgstr "마일스톤 삭제" + +#: taiga/permissions/choices.py:42 +msgid "Add epic" +msgstr "에픽 추가" + +#: taiga/permissions/choices.py:43 +msgid "Modify epic" +msgstr "에픽 수정" + +#: taiga/permissions/choices.py:44 +msgid "Comment epic" +msgstr "에픽 댓글" + +#: taiga/permissions/choices.py:45 +msgid "Delete epic" +msgstr "에픽 삭제" + +#: taiga/permissions/choices.py:47 +msgid "View user story" +msgstr "유저 스토리 보기" + +#: taiga/permissions/choices.py:48 +msgid "Add user story" +msgstr "유저 스토리 추가" + +#: taiga/permissions/choices.py:49 +msgid "Modify user story" +msgstr "유저 스토리 수정" + +#: taiga/permissions/choices.py:50 +msgid "Comment user story" +msgstr "유저 스토리 댓글" + +#: taiga/permissions/choices.py:51 +msgid "Delete user story" +msgstr "유저 스토리 삭제" + +#: taiga/permissions/choices.py:54 +msgid "Add task" +msgstr "태스크 추가" + +#: taiga/permissions/choices.py:55 +msgid "Modify task" +msgstr "태스크 수정" + +#: taiga/permissions/choices.py:56 +msgid "Comment task" +msgstr "태스크 댓글" + +#: taiga/permissions/choices.py:57 +msgid "Delete task" +msgstr "태스크 삭제" + +#: taiga/permissions/choices.py:60 +msgid "Add issue" +msgstr "이슈 추가" + +#: taiga/permissions/choices.py:61 +msgid "Modify issue" +msgstr "이슈 수정" + +#: taiga/permissions/choices.py:62 +msgid "Comment issue" +msgstr "이슈 댓글" + +#: taiga/permissions/choices.py:63 +msgid "Delete issue" +msgstr "이슈 삭제" + +#: taiga/permissions/choices.py:66 +msgid "Add wiki page" +msgstr "위키 페이지 추가" + +#: taiga/permissions/choices.py:67 +msgid "Modify wiki page" +msgstr "위키 페이지 수정" + +#: taiga/permissions/choices.py:68 +msgid "Comment wiki page" +msgstr "위키 페이지 댓글" + +#: taiga/permissions/choices.py:69 +msgid "Delete wiki page" +msgstr "위키 페이지 삭제" + +#: taiga/permissions/choices.py:72 +msgid "Add wiki link" +msgstr "위키 링크 추가" + +#: taiga/permissions/choices.py:73 +msgid "Modify wiki link" +msgstr "위키 링크 수정" + +#: taiga/permissions/choices.py:74 +msgid "Delete wiki link" +msgstr "위키 링크 삭제" + +#: taiga/permissions/choices.py:78 +msgid "Modify project" +msgstr "프로젝트 수정" + +#: taiga/permissions/choices.py:79 +msgid "Delete project" +msgstr "프로젝트 삭제" + +#: taiga/permissions/choices.py:80 +msgid "Add member" +msgstr "회원 추가" + +#: taiga/permissions/choices.py:81 +msgid "Remove member" +msgstr "회원 삭제" + +#: taiga/permissions/choices.py:82 +msgid "Admin project values" +msgstr "운영자 프로젝트 값" + +#: taiga/permissions/choices.py:83 +msgid "Admin roles" +msgstr "운영자 역할" + +#: taiga/projects/admin.py:100 +msgid "Privacity" +msgstr "비공개" + +#: taiga/projects/admin.py:111 +msgid "Modules" +msgstr "모듈" + +#: taiga/projects/admin.py:119 +msgid "Default values" +msgstr "기본값" + +#: taiga/projects/admin.py:125 +msgid "Activity" +msgstr "활동" + +#: taiga/projects/admin.py:130 +msgid "Fans" +msgstr "좋아요" + +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 +#: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 +#: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:27 +msgid "owner" +msgstr "소유자" + +#: taiga/projects/admin.py:192 +#, python-brace-format +msgid "{count} successfully made public." +msgstr "{count} 개를 공개로 만드는데 성공하였습니다." + +#: taiga/projects/admin.py:193 +msgid "Make public" +msgstr "공개로 만들기" + +#: taiga/projects/admin.py:207 +#, python-brace-format +msgid "{count} successfully made private." +msgstr "{count} 개를 비공개로 만드는데 성공하였습니다." + +#: taiga/projects/admin.py:208 +msgid "Make private" +msgstr "비공개로 만들기" + +#: taiga/projects/admin.py:238 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "%(verbose_name_plural)s 선택된 것을 삭제" + +#: taiga/projects/api.py:160 taiga/users/api.py:244 +msgid "Incomplete arguments" +msgstr "매개변수가 완전하지 않습니다." + +#: taiga/projects/api.py:164 taiga/users/api.py:249 +msgid "Invalid image format" +msgstr "이미지 형식이 유효하지 않습니다." + +#: taiga/projects/api.py:225 +msgid "Not valid template name" +msgstr "탬플릿 이름이 유효하지 않습니다." + +#: taiga/projects/api.py:228 +msgid "Not valid template description" +msgstr "탬플릿 설명이 유효하지 않습니다." + +#: taiga/projects/api.py:354 +msgid "Invalid user id" +msgstr "사용자 아이디가 유효하지 않습니다." + +#: taiga/projects/api.py:360 +msgid "The user doesn't exist" +msgstr "사용자가 존재하지 않습니다." + +#: taiga/projects/api.py:364 +msgid "The user must be already a project member" +msgstr "프로젝트 회원이어야 합니다." + +#: taiga/projects/api.py:785 +msgid "" +"The project must have an owner and at least one of the users must be an " +"active admin" +msgstr "" +"프로젝트는 반드시 소유자가 있어야하며 사용자 중 적어도 한 명이 활성 관리자이" +"어야 합니다." + +#: taiga/projects/api.py:819 +msgid "You don't have permisions to see that." +msgstr "볼 수 있는 권한이 없습니다." + +#: taiga/projects/attachments/api.py:54 +msgid "Partial updates are not supported" +msgstr "부분 업데이트는 지원되지 않습니다." + +#: taiga/projects/attachments/api.py:69 +msgid "Object id issue isn't exists" +msgstr "객체 아이디의 이슈는 존재하지 않습니다." + +#: taiga/projects/attachments/api.py:72 +msgid "Project ID not matches between object and project" +msgstr "프로젝트 아이디가 객체와 프로젝트간에 알맞지 않습니다." + +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 +#: taiga/projects/custom_attributes/models.py:43 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 +#: taiga/projects/notifications/models.py:74 +#: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 +#: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 +msgid "project" +msgstr "프로젝트" + +#: taiga/projects/attachments/models.py:43 +msgid "content type" +msgstr "컨텐츠 형태" + +#: taiga/projects/attachments/models.py:45 +msgid "object id" +msgstr "객체 아이디" + +#: taiga/projects/attachments/models.py:51 +#: taiga/projects/custom_attributes/models.py:48 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 +#: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 +#: taiga/userstorage/models.py:31 +msgid "modified date" +msgstr "날짜가 변경되었습니다." + +#: taiga/projects/attachments/models.py:56 +msgid "attached file" +msgstr "파일이 첨부되었습니다." + +#: taiga/projects/attachments/models.py:58 +msgid "sha1" +msgstr "sha1" + +#: taiga/projects/attachments/models.py:60 +msgid "is deprecated" +msgstr "지원안함" + +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "댓글로부터" + +#: taiga/projects/attachments/models.py:63 +#: taiga/projects/custom_attributes/models.py:41 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 +msgid "order" +msgstr "순서" + +#: taiga/projects/choices.py:23 +msgid "AppearIn" +msgstr "AppearIn" + +#: taiga/projects/choices.py:24 +msgid "Jitsi" +msgstr "Jitsi" + +#: taiga/projects/choices.py:25 +msgid "Custom" +msgstr "사용자 정의" + +#: taiga/projects/choices.py:26 +msgid "Talky" +msgstr "Talky" + +#: taiga/projects/choices.py:35 +msgid "This project is blocked due to payment failure" +msgstr "이 프로젝트는 결제 실패로 인해 차단되었습니다." + +#: taiga/projects/choices.py:36 +msgid "This project is blocked by admin staff" +msgstr "관리자에 의해 프로젝트가 차단되었습니다." + +#: taiga/projects/choices.py:37 +msgid "This project is blocked because the owner left" +msgstr "소유자가 남아 있어 프로젝트가 차단되었습니다." + +#: taiga/projects/choices.py:38 +msgid "This project is blocked while it's deleted" +msgstr "이 프로젝트는 삭제되는 동안 차단됩니다." + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" +"\n" +" %(full_name)s 님께서 " +"%(project_name)s 프로젝트에 글을 남겼습니다.\n" +" " + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" +"\n" +"이 메시지는 %(project_name)s 이름의 프로젝트 관리자로 되어있어 수신되었습니" +"다. 타이가 커뮤니티의 회원이 귀하의 프로젝트에 연락하기를 원치 않으시면, 프로젝트 설정 변경을 통해 차단하실 수 " +"있습니다. 프로젝트 회원간의 연락은 영향을 받지 않습니다." + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" +"\n" +"%(full_name)s 님께서 %(project_name)s 프로젝트에 글을 남겼습니다.\n" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" +"\n" +"이 메시지는 당신이 %(project_name)s 이름의 프로젝트의 관리자로 되어기 때문에 " +"수신되었습니다. 타이가 커뮤니티의 회원이 귀하의 프로젝트에 연락하기를 원치 않" +"으시면, %(project_settings_url)s 에서 당신의 프로젝트 설정을 변경하십시오. 프" +"로젝트 회원간의 대화에는 영향을 받지 않습니다.\n" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" +"\n" +"[Taiga] %(full_name)s 님께서 %(project_name)s 프로젝트에 메시지를 보냈습니" +"다.\n" + +#: taiga/projects/custom_attributes/choices.py:29 +msgid "Text" +msgstr "텍스트" + +#: taiga/projects/custom_attributes/choices.py:30 +msgid "Multi-Line Text" +msgstr "여러 줄의 텍스트" + +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "서식있는 텍스트" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "날짜" + +#: taiga/projects/custom_attributes/choices.py:33 +msgid "Url" +msgstr "Url" + +#: taiga/projects/custom_attributes/models.py:40 +#: taiga/projects/issues/models.py:45 +msgid "type" +msgstr "형태" + +#: taiga/projects/custom_attributes/models.py:95 +msgid "values" +msgstr "값" + +#: taiga/projects/custom_attributes/models.py:105 +msgid "epic" +msgstr "에픽" + +#: taiga/projects/custom_attributes/models.py:121 +#: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 +msgid "user story" +msgstr "유저 스토리" + +#: taiga/projects/custom_attributes/models.py:137 +msgid "task" +msgstr "태스크" + +#: taiga/projects/custom_attributes/models.py:153 +msgid "issue" +msgstr "이슈" + +#: taiga/projects/custom_attributes/validators.py:58 +msgid "Already exists one with the same name." +msgstr "이미 동일한 이름이 존재합니다." + +#: taiga/projects/epics/api.py:94 +msgid "You don't have permissions to set this status to this epic." +msgstr "이 에픽의 상태를 설정할 권한이 없습니다." + +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 +#: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 +msgid "ref" +msgstr "참조" + +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 +#: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 +msgid "status" +msgstr "상태" + +#: taiga/projects/epics/models.py:46 +msgid "epics order" +msgstr "에픽 순서" + +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 +#: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 +msgid "subject" +msgstr "제목" + +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 +msgid "color" +msgstr "색" + +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 +#: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 +msgid "assigned to" +msgstr "할당됨" + +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 +msgid "is client requirement" +msgstr "고객 요구 사항" + +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 +msgid "is team requirement" +msgstr "팀 요구 사항" + +#: taiga/projects/epics/models.py:70 +msgid "user stories" +msgstr "유저 스토리" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "외부 참조" + +#: taiga/projects/epics/validators.py:37 +msgid "There's no epic with that id" +msgstr "아이디를 가진 에픽이 없습니다." + +#: taiga/projects/history/api.py:93 +msgid "comment is required" +msgstr "comment가 필수입니다." + +#: taiga/projects/history/api.py:96 +msgid "deleted comments can't be edited" +msgstr "삭제된 댓글은 수정할 수 없습니다." + +#: taiga/projects/history/api.py:130 +msgid "Comment already deleted" +msgstr "댓글이 이미 삭제되었습니다." + +#: taiga/projects/history/api.py:151 +msgid "Comment not deleted" +msgstr "댓글이 삭제되지 않았습니다." + +#: taiga/projects/history/choices.py:31 +msgid "Change" +msgstr "수정" + +#: taiga/projects/history/choices.py:32 +msgid "Create" +msgstr "생성" + +#: taiga/projects/history/choices.py:33 +msgid "Delete" +msgstr "삭제" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 +#, python-format +msgid "%(role)s role points" +msgstr "%(role)s 역할 포인트" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 +msgid "from" +msgstr "보낸이" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 +msgid "to" +msgstr "받는이" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 +msgid "Added new attachment" +msgstr "새로운 첨부파일이 추가되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 +msgid "Updated attachment" +msgstr "첨부파일이 수정되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +msgid "deprecated" +msgstr "지원하지 않음" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 +msgid "not deprecated" +msgstr "아직 지원중임" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 +msgid "Deleted attachment" +msgstr "첨부파일이 삭제되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 +msgid "added" +msgstr "추가되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 +msgid "removed" +msgstr "삭제되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 +#: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 +msgid "Unassigned" +msgstr "할당되지 않았습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 +msgid "-deleted-" +msgstr "-삭제되었습니다-" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 +msgid "to:" +msgstr "받는이:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 +msgid "from:" +msgstr "보낸이:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 +msgid "Added" +msgstr "추가되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 +msgid "Changed" +msgstr "수정되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 +msgid "Deleted" +msgstr "삭제되었습니다." + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 +msgid "added:" +msgstr "추가되었습니다:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 +msgid "removed:" +msgstr "삭제되었습니다:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 +msgid "From:" +msgstr "보낸이:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 +msgid "To:" +msgstr "받는이:" + +#: taiga/projects/history/templatetags/functions.py:26 +#: taiga/projects/wiki/models.py:38 +msgid "content" +msgstr "컨텐츠" + +#: taiga/projects/history/templatetags/functions.py:27 +#: taiga/projects/mixins/blocked.py:33 +msgid "blocked note" +msgstr "차단된 노트" + +#: taiga/projects/history/templatetags/functions.py:28 +msgid "sprint" +msgstr "스프린트" + +#: taiga/projects/issues/api.py:157 +msgid "You don't have permissions to set this sprint to this issue." +msgstr "이 이슈의 스프린트를 설정할 권한이 없습니다." + +#: taiga/projects/issues/api.py:161 +msgid "You don't have permissions to set this status to this issue." +msgstr "이 이슈의 상태를 설정할 권한이 없습니다." + +#: taiga/projects/issues/api.py:165 +msgid "You don't have permissions to set this severity to this issue." +msgstr "이 이슈의 심각도를 설정할 권한이 없습니다." + +#: taiga/projects/issues/api.py:169 +msgid "You don't have permissions to set this priority to this issue." +msgstr "이 우선 순위의 형태를 설정할 권한이 없습니다." + +#: taiga/projects/issues/api.py:173 +msgid "You don't have permissions to set this type to this issue." +msgstr "이 이슈에 형태를 설정할 권한이 없습니다." + +#: taiga/projects/issues/models.py:41 +msgid "severity" +msgstr "심각도" + +#: taiga/projects/issues/models.py:43 +msgid "priority" +msgstr "우선 순위" + +#: taiga/projects/issues/models.py:48 taiga/projects/tasks/models.py:46 +#: taiga/projects/userstories/models.py:65 +msgid "milestone" +msgstr "마일스톤" + +#: taiga/projects/issues/models.py:57 taiga/projects/tasks/models.py:53 +msgid "finished date" +msgstr "종료된 날짜" + +#: taiga/projects/likes/models.py:36 +msgid "Like" +msgstr "좋아요" + +#: taiga/projects/likes/models.py:37 +msgid "Likes" +msgstr "좋아요" + +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 +msgid "slug" +msgstr "슬러그" + +#: taiga/projects/milestones/models.py:45 +msgid "estimated start date" +msgstr "예측 시작일" + +#: taiga/projects/milestones/models.py:46 +msgid "estimated finish date" +msgstr "예측 종료일" + +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 +msgid "is closed" +msgstr "완료됨" + +#: taiga/projects/milestones/models.py:55 +msgid "disponibility" +msgstr "가용성" + +#: taiga/projects/milestones/models.py:79 +msgid "The estimated start must be previous to the estimated finish." +msgstr "예측 시작은 반드시 예측 종료보다 이전이어야 합니다." + +#: taiga/projects/milestones/validators.py:33 +msgid "There's no milestone with that id" +msgstr "아이디를 가진 마일스톤이 없습니다." + +#: taiga/projects/mixins/blocked.py:31 +msgid "is blocked" +msgstr "차단됨" + +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "ref 매개변수가 필요합니다." + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "project 또는 project__slug 매개변수가 필요합니다." + +#: taiga/projects/mixins/ordering.py:49 +#, python-brace-format +msgid "'{param}' parameter is mandatory" +msgstr "'{param}' 매개변수는 필수입니다." + +#: taiga/projects/mixins/ordering.py:53 +msgid "'project' parameter is mandatory" +msgstr "'project' 매개변수는 필수입니다." + +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "반드시 프로젝트 회원이어야 합니다." + +#: taiga/projects/models.py:79 +msgid "email" +msgstr "이메일" + +#: taiga/projects/models.py:81 +msgid "create at" +msgstr "생성일" + +#: taiga/projects/models.py:83 taiga/users/models.py:157 +msgid "token" +msgstr "토큰" + +#: taiga/projects/models.py:89 +msgid "invitation extra text" +msgstr "초대장 추가 문자" + +#: taiga/projects/models.py:92 taiga/projects/models.py:741 +msgid "user order" +msgstr "유저 순서" + +#: taiga/projects/models.py:108 +msgid "The user is already member of the project" +msgstr "이미 프로젝트의 일원입니다." + +#: taiga/projects/models.py:115 +msgid "default epic status" +msgstr "기본 에픽 상태" + +#: taiga/projects/models.py:119 +msgid "default US status" +msgstr "기본 유저 스토리 상태" + +#: taiga/projects/models.py:122 +msgid "default points" +msgstr "기본 포인트" + +#: taiga/projects/models.py:126 +msgid "default task status" +msgstr "기본 태스크 상태" + +#: taiga/projects/models.py:129 +msgid "default priority" +msgstr "기본 우선순위" + +#: taiga/projects/models.py:132 +msgid "default severity" +msgstr "기본 심각도" + +#: taiga/projects/models.py:136 +msgid "default issue status" +msgstr "기본 이슈 상태" + +#: taiga/projects/models.py:140 +msgid "default issue type" +msgstr "기본 이슈 형태" + +#: taiga/projects/models.py:156 +msgid "logo" +msgstr "로고" + +#: taiga/projects/models.py:166 +msgid "members" +msgstr "회원" + +#: taiga/projects/models.py:169 +msgid "total of milestones" +msgstr "마일스톤 합계" + +#: taiga/projects/models.py:170 +msgid "total story points" +msgstr "총 스토리 포인트" + +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "연락처 활성" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 +msgid "active epics panel" +msgstr "에픽 활성" + +#: taiga/projects/models.py:176 taiga/projects/models.py:755 +msgid "active backlog panel" +msgstr "백로그 활성" + +#: taiga/projects/models.py:178 taiga/projects/models.py:757 +msgid "active kanban panel" +msgstr "칸반 활성" + +#: taiga/projects/models.py:180 taiga/projects/models.py:759 +msgid "active wiki panel" +msgstr "위키 활성" + +#: taiga/projects/models.py:182 taiga/projects/models.py:761 +msgid "active issues panel" +msgstr "이슈 활성" + +#: taiga/projects/models.py:185 taiga/projects/models.py:768 +msgid "videoconference system" +msgstr "화상회의 시스템" + +#: taiga/projects/models.py:187 taiga/projects/models.py:770 +msgid "videoconference extra data" +msgstr "화상회의 추가 데이터" + +#: taiga/projects/models.py:193 +msgid "creation template" +msgstr "탬플릿 생성" + +#: taiga/projects/models.py:196 taiga/users/admin.py:62 +msgid "is private" +msgstr "비공개" + +#: taiga/projects/models.py:198 +msgid "anonymous permissions" +msgstr "익명 사용자 권한" + +#: taiga/projects/models.py:200 +msgid "user permissions" +msgstr "사용자 권한" + +#: taiga/projects/models.py:203 +msgid "is featured" +msgstr "추천됨" + +#: taiga/projects/models.py:206 taiga/projects/models.py:763 +msgid "is looking for people" +msgstr "구인중" + +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "구인 메모" + +#: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "프로젝트 이전 토큰" + +#: taiga/projects/models.py:226 +msgid "blocked code" +msgstr "코드 차단됨" + +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 +msgid "updated date time" +msgstr "날짜 시간 수정됨" + +#: taiga/projects/models.py:232 taiga/projects/models.py:244 +#: taiga/projects/votes/models.py:30 +msgid "count" +msgstr "개수" + +#: taiga/projects/models.py:235 +msgid "fans last week" +msgstr "지난 주의 좋아요" + +#: taiga/projects/models.py:238 +msgid "fans last month" +msgstr "지난 달의 좋아요" + +#: taiga/projects/models.py:241 +msgid "fans last year" +msgstr "작년의 좋아요" + +#: taiga/projects/models.py:248 +msgid "activity last week" +msgstr "지난 주의 활동" + +#: taiga/projects/models.py:252 +msgid "activity last month" +msgstr "지난 달의 활동" + +#: taiga/projects/models.py:256 +msgid "activity last year" +msgstr "작년의 활동" + +#: taiga/projects/models.py:507 +msgid "modules config" +msgstr "모듈 설정" + +#: taiga/projects/models.py:559 +msgid "is archived" +msgstr "보관됨" + +#: taiga/projects/models.py:563 +msgid "work in progress limit" +msgstr "작업 진행 한계" + +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 +msgid "value" +msgstr "값" + +#: taiga/projects/models.py:749 +msgid "default owner's role" +msgstr "소유자의 기본 역할" + +#: taiga/projects/models.py:772 +msgid "default options" +msgstr "기본 옵션" + +#: taiga/projects/models.py:773 +msgid "epic statuses" +msgstr "에픽 상태" + +#: taiga/projects/models.py:774 +msgid "us statuses" +msgstr "유저 스토리 상태" + +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 +#: taiga/projects/userstories/models.py:77 +msgid "points" +msgstr "포인트" + +#: taiga/projects/models.py:776 +msgid "task statuses" +msgstr "태스크 상태" + +#: taiga/projects/models.py:777 +msgid "issue statuses" +msgstr "이슈 상태" + +#: taiga/projects/models.py:778 +msgid "issue types" +msgstr "이슈 형태" + +#: taiga/projects/models.py:779 +msgid "priorities" +msgstr "우선순위" + +#: taiga/projects/models.py:780 +msgid "severities" +msgstr "심각도" + +#: taiga/projects/models.py:781 +msgid "roles" +msgstr "역할" + +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "에픽 사용자 정의 속성" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "유저 스토리 사용자 정의 속성" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "태스크 사용자 정의 속성" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "이슈 사용자 정의 속성" + +#: taiga/projects/notifications/choices.py:30 +msgid "Involved" +msgstr "관련됨" + +#: taiga/projects/notifications/choices.py:31 +msgid "All" +msgstr "모두" + +#: taiga/projects/notifications/choices.py:32 +msgid "None" +msgstr "없음" + +#: taiga/projects/notifications/models.py:64 +msgid "created date time" +msgstr "날짜 시간 생성됨" + +#: taiga/projects/notifications/models.py:68 +msgid "history entries" +msgstr "내역 항목" + +#: taiga/projects/notifications/models.py:71 +msgid "notify users" +msgstr "사용자 알림" + +#: taiga/projects/notifications/models.py:93 +#: taiga/projects/notifications/models.py:94 +msgid "Watched" +msgstr "구독됨" + +#: taiga/projects/notifications/services.py:65 +#: taiga/projects/notifications/services.py:79 +msgid "Notify exists for specified user and project" +msgstr "지정된 사용자 및 프로젝트에 대한 알림이 있습니다." + +#: taiga/projects/notifications/services.py:436 +msgid "Invalid value for notify level" +msgstr "알림 수준의 값이 유효하지 않습니다." + +#: taiga/projects/notifications/templates/emails/epics/epic-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Epic updated

\n" +"

Hello %(user)s,
%(changer)s has updated a epic on %(project)s\n" +"

Epic #%(ref)s %(subject)s

\n" +" See epic\n" +" " +msgstr "" +"\n" +"

에픽이 수정되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 에픽을 수정하셨습니다.

\n" +"

#%(ref)s %(subject)s 에픽

\n" +" 에픽 보기" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Epic updated\n" +"Hello %(user)s, %(changer)s has updated a epic on %(project)s\n" +"See epic #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"에픽이 수정되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 에픽을 수정" +"하셨습니다.\n" +"#%(ref)s %(subject)s 에픽 보기 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s %(subject)s 에픽이 수정되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New epic created

\n" +"

Hello %(user)s,
%(changer)s has created a new epic on " +"%(project)s

\n" +"

Epic #%(ref)s %(subject)s

\n" +" See epic\n" +"

The Taiga Team

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

새로운 에픽이 생성되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"에 새로운 에픽을 생성하셨습니다.

\n" +"

#%(ref)s %(subject)s 에픽

\n" +" 에픽 보기\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New epic created\n" +"Hello %(user)s, %(changer)s has created a new epic on %(project)s\n" +"See epic #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"새로운 에픽이 생성되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트에 새로운 에픽" +"이 생성하셨습니다.\n" +"#%(ref)s %(subject)s 에픽 보기 %(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s %(subject)s 에픽이 생성되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Epic deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a epic on %(project)s\n" +"

Epic #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

에픽이 삭제되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 에픽을 삭제하셨습니다.

\n" +"

#%(ref)s %(subject)s 에픽

\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Epic deleted\n" +"Hello %(user)s, %(changer)s has deleted a epic on %(project)s\n" +"Epic #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"에픽이 삭제되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 에픽을 삭제" +"하셨습니다.\n" +"#%(ref)s %(subject)s 에픽\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s %(subject)s 에픽이 삭제되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Issue updated

\n" +"

Hello %(user)s,
%(changer)s has updated an issue on %(project)s\n" +"

Issue #%(ref)s %(subject)s

\n" +" See issue\n" +" " +msgstr "" +"\n" +"

이슈가 수정되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 이슈를 수정하셨습니다.

\n" +"

#%(ref)s %(subject)s 이슈

\n" +" 이슈 보기" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Issue updated\n" +"Hello %(user)s, %(changer)s has updated an issue on %(project)s\n" +"See issue #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"이슈가 수정되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 이슈를 수정" +"하셨습니다.\n" +"#%(ref)s %(subject)s 이슈 보기 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s %(subject)s 이슈가 수정되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New issue created

\n" +"

Hello %(user)s,
%(changer)s has created a new issue on " +"%(project)s

\n" +"

Issue #%(ref)s %(subject)s

\n" +" See issue\n" +"

The Taiga Team

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

새로운 이슈가 생성되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"에 새로운 이슈를 생성하셨습니다.

\n" +"

#%(ref)s %(subject)s 이슈

\n" +" 이슈 보기\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New issue created\n" +"Hello %(user)s, %(changer)s has created a new issue on %(project)s\n" +"See issue #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"새로운 이슈가 생성되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트에 새로운 이슈" +"를 생성하셨습니다.\n" +"#%(ref)s %(subject)s 이슈 보기 %(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s %(subject)s 이슈가 생성되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Issue deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted an issue on %(project)s\n" +"

Issue #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

이슈가 삭제되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 이슈를 삭제하셨습니다.

\n" +"

#%(ref)s %(subject)s 이슈

\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Issue deleted\n" +"Hello %(user)s, %(changer)s has deleted an issue on %(project)s\n" +"Issue #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"이슈가 수정되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 이슈를 삭제" +"하셨습니다.\n" +"#%(ref)s %(subject)s 이슈\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s %(subject)s 이슈가 삭제되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Sprint updated

\n" +"

Hello %(user)s,
%(changer)s has updated an sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +" See sprint\n" +" " +msgstr "" +"\n" +"

스프린트가 수정되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 스프린트를 수정하셨습니다.

\n" +"

%(name)s 스프린트

\n" +" 스프린트 보기\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Sprint updated\n" +"Hello %(user)s, %(changer)s has updated a sprint on %(project)s\n" +"See sprint %(name)s at %(url)s\n" +msgstr "" +"\n" +"스프린트가 수정되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 스프린트를 " +"수정하셨습니다.\n" +"%(name)s 스프린트 보기 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the sprint \"%(milestone)s\"\n" +msgstr "" +"\n" +"[%(project)s] \"%(milestone)s\" 스프린트가 수정되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New sprint created

\n" +"

Hello %(user)s,
%(changer)s has created a new sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +" See " +"sprint\n" +"

The Taiga Team

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

스프린트가 생성되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"에 새로운 스프린트를 생성하셨습니다.

\n" +"

%(name)s 스프린트

\n" +" 스" +"프린트 보기\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New sprint created\n" +"Hello %(user)s, %(changer)s has created a new sprint on %(project)s\n" +"See sprint %(name)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"새로운 스프린트가 생성되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트에 새로운 스프" +"린트를 생성하셨습니다.\n" +"%(name)s 스프린트 보기 %(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the sprint \"%(milestone)s\"\n" +msgstr "" +"\n" +"[%(project)s] \"%(milestone)s\" 스프린트가 생성되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Sprint deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted an sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +"

The Taiga Team

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

스프린트가 삭제되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 스프린트를 삭제하셨습니다.

\n" +"

%(name)s 스프린트

\n" +"

타이가 팀

\n" +" " + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Sprint deleted\n" +"Hello %(user)s, %(changer)s has deleted an sprint on %(project)s\n" +"Sprint %(name)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"스프린트가 삭제되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 스프린트를 " +"삭제하셨습니다.\n" +"%(name)s 스프린트\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the Sprint \"%(milestone)s\"\n" +msgstr "" +"\n" +"[%(project)s] \"%(milestone)s\" 스프린트가 삭제되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Task updated

\n" +"

Hello %(user)s,
%(changer)s has updated a task on %(project)s\n" +"

Task #%(ref)s %(subject)s

\n" +" See task\n" +" " +msgstr "" +"\n" +"

태스크가 수정되었습니다

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 태스크를 수정하셨습니다.

\n" +"

#%(ref)s %(subject)s 태스크

\n" +" 태스크 보기" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Task updated\n" +"Hello %(user)s, %(changer)s has updated a task on %(project)s\n" +"See task #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"태스크가 수정되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 태스크를 수" +"정하셨습니다.\n" +"#%(ref)s %(subject)s 태스크 보기 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the task #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s \"%(subject)s\" 태스크가 수정되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New task created

\n" +"

Hello %(user)s,
%(changer)s has created a new task on " +"%(project)s

\n" +"

Task #%(ref)s %(subject)s

\n" +" See task\n" +"

The Taiga Team

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

새로운 태스크가 생성되었습니다

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"에 새로운 태스크를 생성하셨습니다.

\n" +"

태스크 #%(ref)s %(subject)s

\n" +" 태스크 보기\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New task created\n" +"Hello %(user)s, %(changer)s has created a new task on %(project)s\n" +"See task #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"태스크가 생성되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트에 새로운 태스" +"크를 생성하셨습니다.\n" +"#%(ref)s %(subject)s 태스크 보기 %(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the task #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s \"%(subject)s\" 태스크가 생성되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Task deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a task on %(project)s\n" +"

Task #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

태스크가 삭제되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 태스크를 삭제하셨습니다.

\n" +"

#%(ref)s %(subject)s 태스크

\n" +"

타이가 팀

\n" +" " + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Task deleted\n" +"Hello %(user)s, %(changer)s has deleted a task on %(project)s\n" +"Task #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"태스크가 삭제되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 태스크를 삭" +"제하셨습니다.\n" +"#%(ref)s %(subject)s 태스크\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the task #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s \"%(subject)s\" 태스크가 삭제되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

User Story updated

\n" +"

Hello %(user)s,
%(changer)s has updated a user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +" See user story\n" +" " +msgstr "" +"\n" +"

유저 스토리가 수정되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 유저 스토리를 수정하셨습니다.

\n" +"

#%(ref)s %(subject)s 유저 스토리

\n" +" 유저 스토리 보기\n" +" " + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"User story updated\n" +"Hello %(user)s, %(changer)s has updated a user story on %(project)s\n" +"See user story #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"유저 스토리가 수정되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 유저 스토리" +"를 수정하셨습니다.\n" +"#%(ref)s %(subject)s 유저 스토리 보기 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the US #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s \"%(subject)s\" 유저 스토리가 수정되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New user story created

\n" +"

Hello %(user)s,
%(changer)s has created a new user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +" See user story\n" +"

The Taiga Team

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

새로운 유저 스토리가 생성되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"에 새로운 유저 스토리를 생성하셨습니다.

\n" +"

#%(ref)s %(subject)s 유저 스토리

\n" +" 유저 스토리 보기\n" +"

타이가 팀

\n" +"\n" +" " + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New user story created\n" +"Hello %(user)s, %(changer)s has created a new user story on %(project)s\n" +"See user story #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"새로운 유저 스토리가 생성되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트에 새로운 유" +"저 스토리를 생성하셨습니다.\n" +"#%(ref)s %(subject)s 유저 스토리 보기 %(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the US #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s \"%(subject)s\" 유저 스토리가 생성되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

User Story deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

유저 스토리가 삭제되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 유저 스토리를 삭제하셨습니다.s

\n" +"

#%(ref)s %(subject)s 유저 스토리

\n" +"

타이가 팀

" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"User Story deleted\n" +"Hello %(user)s, %(changer)s has deleted a user story on %(project)s\n" +"User Story #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"유저 스토리가 삭제되었습니다.\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 유저 스토리" +"를 삭제하셨습니다.\n" +"#%(ref)s %(subject)s 유저 스토리\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the US #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] #%(ref)s \"%(subject)s\" 유저 스토리가 삭제되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Wiki Page updated

\n" +"

Hello %(user)s,
%(changer)s has updated a wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +" See Wiki Page\n" +" " +msgstr "" +"\n" +"

위키 페이지가 수정되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 위키 페이지를 수정하셨습니다.

\n" +"

%(page)s 위키 페이지

\n" +" " +"위키 페이지 보기\n" +" " + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Wiki Page updated\n" +"\n" +"Hello %(user)s, %(changer)s has updated a wiki page on %(project)s\n" +"\n" +"See wiki page %(page)s at %(url)s\n" +msgstr "" +"\n" +"위키 페이지가 수정되었습니다.\n" +"\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 위키 페이지" +"를 수정하셨습니다.\n" +"\n" +"%(page)s 위키 페이지 보기 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the Wiki Page \"%(page)s\"\n" +msgstr "" +"\n" +"[%(project)s] \"%(page)s\" 위키 페이지가 수정되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New wiki page created

\n" +"

Hello %(user)s,
%(changer)s has created a new wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +" See " +"wiki page\n" +"

The Taiga Team

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

새로운 위키 페이지가 생성되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s %(project)s 프로젝트에 새로" +"운 위키 페이지가 생성되었습니다.

\n" +"

%(page)s 위키 페이지

\n" +" 위키 " +"페이지 보기\n" +"

타이가 팀

\n" +" " + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New wiki page created\n" +"\n" +"Hello %(user)s, %(changer)s has created a new wiki page on %(project)s\n" +"\n" +"See wiki page %(page)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"새로운 위키 페이지가 생성되었습니다.\n" +"\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트에 새로운 위" +"키 페이지를 생성하셨습니다.\n" +"\n" +"%(page)s 위키 페이지보기 %(url)s\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the Wiki Page \"%(page)s\"\n" +msgstr "" +"\n" +"[%(project)s] \"%(page)s\" 위키 페이지가 생성되었습니다.\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Wiki page deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +"

The Taiga Team

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

위키 페이지가 삭제되었습니다.

\n" +"

안녕하세요 %(user)s 님,
%(changer)s 님께서 %(project)s 프로젝트" +"의 위키 페이지를 삭제하셨습니다.

\n" +"

%(page)s 위키 페이지

\n" +"

타이가 팀

\n" +" " + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Wiki page deleted\n" +"\n" +"Hello %(user)s, %(changer)s has deleted a wiki page on %(project)s\n" +"\n" +"Wiki page %(page)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"위키 페이지가 삭제되었습니다.\n" +"\n" +"안녕하세요 %(user)s 님, %(changer)s 님께서 %(project)s 프로젝트의 위키 페이지" +"를 삭제하셨습니다.\n" +"\n" +"%(page)s 위키 페이지\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the Wiki Page \"%(page)s\"\n" +msgstr "" +"\n" +"[%(project)s] \"%(page)s\" 위키 페이지가 삭제되었습니다.\n" + +#: taiga/projects/notifications/validators.py:48 +msgid "Watchers contains invalid users" +msgstr "구독중인 사람들중에 유효하지 않은 사용자가 포함되어 있습니다." + +#: taiga/projects/occ/mixins.py:37 +msgid "The version must be an integer" +msgstr "버전은 반드시 정수이어야 합니다." + +#: taiga/projects/occ/mixins.py:60 +msgid "The version parameter is not valid" +msgstr "버전 매개변수가 유효하지 않습니다." + +#: taiga/projects/occ/mixins.py:76 +msgid "The version doesn't match with the current one" +msgstr "현재 버전과 일치하지 않습니다." + +#: taiga/projects/occ/mixins.py:95 +msgid "version" +msgstr "버전" + +#: taiga/projects/permissions.py:44 +msgid "" +"You can't leave the project if you are the owner or there are no more admins" +msgstr "당신이 소유자이거나 다른 관리자가 없다면 프로젝트를 떠날 수 없습니다." + +#: taiga/projects/services/members.py:133 +msgid "Project without owner" +msgstr "소유자 없는 프로젝트" + +#: taiga/projects/services/members.py:138 +msgid "You have reached your current limit of memberships for private projects" +msgstr "당신의 현재 비공개 프로젝트 최대 수용 인원수에 도달하였습니다." + +#: taiga/projects/services/members.py:142 +msgid "You have reached your current limit of memberships for public projects" +msgstr "당신의 현재 공공 프로젝트 최대 수용 인원수에 도달하였습니다." + +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "당신의 현재 최대 보류중 회원수 제한에 도달하였습니다." + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 +msgid "You can't have more private projects" +msgstr "더 이상의 비공개 프로젝트를 소유하실 수 없습니다." + +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 +msgid "" +"This project reaches your current limit of memberships for private projects" +msgstr "" +"이 프로젝트는 당신의 비공개 프로젝트 최대 수용 인원수에 도달하였습니다." + +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 +msgid "You can't have more public projects" +msgstr "더 이상의 공공 프로젝트를 소유하실 수 없습니다." + +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 +msgid "" +"This project reaches your current limit of memberships for public projects" +msgstr "이 프로젝트는 당신의 공공 프로젝트 최대 수용 인원수에 도달하였습니다." + +#: taiga/projects/services/stats.py:197 +msgid "Future sprint" +msgstr "앞으로의 스프린트" + +#: taiga/projects/services/stats.py:217 +msgid "Project End" +msgstr "프로젝트 종료" + +#: taiga/projects/services/transfer.py:62 +#: taiga/projects/services/transfer.py:69 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 +msgid "Token is invalid" +msgstr "토큰이 유효하지 않습니다." + +#: taiga/projects/services/transfer.py:67 +msgid "Token has expired" +msgstr "토큰이 만료되었습니다." + +#: taiga/projects/tagging/fields.py:52 +#, python-brace-format +msgid "Invalid tag '{value}'. The color is not a valid HEX color or null." +msgstr "" +"'{value}' 태그는 유효하지 않습니다. 색이 유효하지 않은 HEX색 이거나 null 입니" +"다." + +#: taiga/projects/tagging/fields.py:55 +#, python-brace-format +msgid "" +"Invalid tag '{value}'. it must be the name or a pair '[\"name\", \"hex color/" +"\" | null]'." +msgstr "" +"'{value}' 태그는 유효하지 않습니다. 이름이나 쌍을 이루어야 합니다. '[\"name" +"\", \"hex color/\" | null]'." + +#: taiga/projects/tagging/fields.py:77 +#, python-brace-format +msgid "Invalid tag '{value}'. It must be the tag name." +msgstr "'{value}' 태그는 유효하지 않습니다. 태그 이름이어야 합니다." + +#: taiga/projects/tagging/models.py:27 +msgid "tags" +msgstr "태그" + +#: taiga/projects/tagging/models.py:35 +msgid "tags colors" +msgstr "태그 색" + +#: taiga/projects/tagging/validators.py:47 +#: taiga/projects/tagging/validators.py:74 +msgid "This tag already exists." +msgstr "이 태그는 이미 존재합니다." + +#: taiga/projects/tagging/validators.py:54 +#: taiga/projects/tagging/validators.py:81 +msgid "The color is not a valid HEX color." +msgstr "색이 유효하지 않은 HEX색 입니다." + +#: taiga/projects/tagging/validators.py:67 +#: taiga/projects/tagging/validators.py:101 +#: taiga/projects/tagging/validators.py:114 +#: taiga/projects/tagging/validators.py:121 +msgid "The tag doesn't exist." +msgstr "태그가 존재하지 않습니다." + +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 +msgid "You don't have permissions to set this sprint to this task." +msgstr "이 태스크의 스프린트를 설정할 권한이 없습니다." + +#: taiga/projects/tasks/api.py:101 +msgid "You don't have permissions to set this user story to this task." +msgstr "이 태스크의 유저 스토리를 설정할 권한이 없습니다." + +#: taiga/projects/tasks/api.py:104 +msgid "You don't have permissions to set this status to this task." +msgstr "이 태스크의 상태를 설정할 권한이 없습니다." + +#: taiga/projects/tasks/models.py:58 +msgid "us order" +msgstr "유저 스토리 순서" + +#: taiga/projects/tasks/models.py:60 +msgid "taskboard order" +msgstr "태스크보드 순서" + +#: taiga/projects/tasks/models.py:68 +msgid "is iocaine" +msgstr "아이오케인 입니다." + +#: taiga/projects/tasks/validators.py:61 +msgid "Invalid milestone id." +msgstr "마일스톤 아이디가 유효하지 않습니다." + +#: taiga/projects/tasks/validators.py:72 +msgid "Invalid task status id." +msgstr "태스크 상태 아이디가 유효하지 않습니다." + +#: taiga/projects/tasks/validators.py:85 +msgid "Invalid user story id." +msgstr "유저 스토리 아이디가 유효하지 않습니다." + +#: taiga/projects/tasks/validators.py:109 +msgid "Invalid task status id. The status must belong to the same project." +msgstr "" +"태스크 상태 아이디가 유효하지 않습니다. 상태는 반드시 같은 프로젝트에 속해야 " +"합니다." + +#: taiga/projects/tasks/validators.py:123 +msgid "Invalid user story id. The user story must belong to the same project." +msgstr "" +"유저 스토리 아이디가 유효하지 않습니다. 유저 스토리는 반드시 같은 프로젝트에 " +"속해야 합니다." + +#: taiga/projects/tasks/validators.py:135 +msgid "Invalid milestone id. The milestone must belong to the same project." +msgstr "" +"마일스톤 아이디가 유효하지 않습니다. 마일스톤은 반드시 같은 프로젝트에 속해" +"야 합니다." + +#: taiga/projects/tasks/validators.py:152 +msgid "" +"Invalid task ids. All tasks must belong to the same project and, if it " +"exists, to the same status, user story and/or milestone." +msgstr "" +"태스크 아이디가 유효하지 않습니다. 모든 태스크는 반드시 동일한 프로젝트에 속" +"해야하며, 만약 존재한다면, 같은 상태, 마일스톤이거나 상태, 유저 스토리, 마일" +"스톤이어야 합니다." + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:6 +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:4 +msgid "someone" +msgstr "누군가" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:11 +#, python-format +msgid "" +"\n" +"

You have been invited to Taiga!

\n" +"

Hi! %(full_name)s has sent you an invitation to join project " +"%(project)s in Taiga.
Taiga is a Free, open Source Agile Project " +"Management Tool.

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

타이가로 초대되었습니다!

\n" +"

안녕하세요! %(full_name)s 님이 타이가에서 관리되고 있는 %(project)s 프로젝트에 참여해달라는 초대장을 보냈습니다.
타이가는 무료이며 오픈" +"소스인 애자일 프로젝트 관리 툴입니다.

\n" +" " + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:17 +#, python-format +msgid "" +"\n" +"

And now a few words from the jolly good fellow or sistren
" +"who thought so kindly as to invite you

\n" +"

%(extra)s

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

당신을 친히 초대할만큼
유쾌하고 좋은 동료들이나 여자사람친" +"구들의 몇 마디

\n" +"

%(extra)s

\n" +" " + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:24 +msgid "Accept your invitation to Taiga" +msgstr "타이가로의 초대를 수락합니다." + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:24 +msgid "Accept your invitation" +msgstr "초대를 수락합니다." + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:25 +msgid "The Taiga Team" +msgstr "타이가 팀" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:6 +#, python-format +msgid "" +"\n" +"You, or someone you know, has invited you to Taiga\n" +"\n" +"Hi! %(full_name)s has sent you an invitation to join a project called " +"%(project)s which is being managed on Taiga, a Free, open Source Agile " +"Project Management Tool.\n" +msgstr "" +"\n" +"당신, 또는 당신을 아는 누군가가 타이가로 당신을 초대했습니다.\n" +"\n" +"안녕하세요! %(full_name)s 님이 타이가에서 관리되고 있는 %(project)s 프로젝트" +"에 참여해달라는 초대장을 보냈습니다. 무료이며 오픈소스인 애자일 프로젝트 관" +"리 툴입니다.\n" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:12 +#, python-format +msgid "" +"\n" +"And now a few words from the jolly good fellow or sistren who thought so " +"kindly as to invite you:\n" +"\n" +"%(extra)s\n" +" " +msgstr "" +"\n" +"당신을 친히 초대할만큼 유쾌하고 좋은 동료들이나 여자사람친구들의 몇 마디:\n" +"\n" +"%(extra)s\n" +" " + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:18 +msgid "Accept your invitation to Taiga following this link:" +msgstr "타이가로의 초대를 수락하려면 이 링크를 따라가십시오:" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:20 +msgid "" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/projects/templates/emails/membership_invitation-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Invitation to join to the project '%(project)s'\n" +msgstr "" +"\n" +"[타이가] '%(project)s' 프로젝트로 초대하는 초대장\n" + +#: taiga/projects/templates/emails/membership_notification-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

You have been added to a project

\n" +"

Hello %(full_name)s,
you have been added to the project " +"%(project)s

\n" +" Go to " +"project\n" +"

The Taiga Team

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

당신은 새로운 프로젝트를 추가하셨습니다.

\n" +"

안녕하세요 %(full_name)s 님,
당신은 %(project)s 프로젝트를 새로 " +"추가하셨습니다.

\n" +" 프로젝" +"트로 이동\n" +"

타이가 팀

" + +#: taiga/projects/templates/emails/membership_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"You have been added to a project\n" +"Hello %(full_name)s,you have been added to the project %(project)s\n" +"\n" +"See project at %(url)s\n" +msgstr "" +"\n" +"당신은 새로운 프로젝트를 추가하셨습니다.\n" +"안녕하세요 %(full_name)s 님, 당신은 %(project)s 프로젝트를 새로 추가하셨습니" +"다.\n" +"\n" +"%(url)s 에서 확인하세요.\n" + +#: taiga/projects/templates/emails/membership_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Added to the project '%(project)s'\n" +msgstr "" +"\n" +"[타이가] '%(project)s' 프로젝트가 추가되었습니다.\n" + +#: taiga/projects/templates/emails/transfer_accept-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(old_owner_name)s,

\n" +"

%(new_owner_name)s has accepted your offer and will become the " +"new project owner for \"%(project_name)s\".

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

안녕하세요 %(old_owner_name)s 님,

\n" +"

%(new_owner_name)s 님께서 당신의 제안을 받아들여 " +"\"%(project_name)s\" 프로젝트의 새로운 소유자가 되었습니다.\n" +"

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

%(new_owner_name)s says:

" +msgstr "

%(new_owner_name)s 님의 말:

" + +#: taiga/projects/templates/emails/transfer_accept-body-html.jinja:14 +msgid "" +"\n" +"

From now on, your new status for this project will be \"admin\".\n" +" " +msgstr "" +"\n" +"

이제부터, 이 프로젝트에서 당신의 새로운 지위는 \"관리자\"가 될 것" +"입니다.

\n" +" " + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(old_owner_name)s,\n" +"%(new_owner_name)s has accepted your offer and will become the new project " +"owner for \"%(project_name)s\".\n" +msgstr "" +"\n" +"안녕하세요 %(old_owner_name)s 님,\n" +"%(new_owner_name)s 님께서 당신의 제안을 받아들여 \"%(project_name)s\" 프로젝" +"트의 새로운 소유자가 되었습니다.\n" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:7 +#, python-format +msgid "%(new_owner_name)s says:" +msgstr "%(new_owner_name)s 님의 말:" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:11 +msgid "" +"\n" +"From now on, your new status for this project will be \"admin\".\n" +msgstr "" +"\n" +"이제부터, 이 프로젝트에서 당신의 새로운 지위는 \"관리자\"가 될 것입니다.\n" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:16 +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:19 +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:13 +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:18 +msgid "" +"\n" +"The Taiga Team\n" +msgstr "" +"\n" +"타이가 팀\n" + +#: taiga/projects/templates/emails/transfer_accept-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer offer accepted!\n" +msgstr "" +"\n" +"[%(project)s] 프로젝트 소유권 이전 승인!\n" + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(owner_name)s,

\n" +"

%(rejecter_name)s has declined your offer and will not become the " +"new project owner for \"%(project_name)s\".

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

안녕하세요 %(owner_name)s 님,

\n" +"

%(rejecter_name)s 님께서 \"%(project_name)s\" 프로젝트의 새로운 소" +"유자가 되지 않겠냐는 당신의 제안을 거절하셨습니다.

\n" +" " + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:10 +#, python-format +msgid "" +"\n" +"

%(rejecter_name)s says:

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

%(rejecter_name)s 님의 말:

\n" +" " + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:16 +msgid "" +"\n" +"

If you want, you can still try to transfer the project ownership to a " +"different person.

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

원한다면 프로젝트 소유권을 다른 사람에게 이전 할 수 있습니다.

\n" +" " + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:21 +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:22 +msgid "Request transfer to a different person" +msgstr "다른 사람에게 이전 요청" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(owner_name)s,\n" +"%(rejecter_name)s has declined your offer and will not become the new " +"project owner for \"%(project_name)s\".\n" +msgstr "" +"\n" +"안녕하세요 %(owner_name)s 님,\n" +"%(rejecter_name)s 님께서 \"%(project_name)s\" 프로젝트의 새로운 소유자가 되" +"지 않겠냐는 당신의 제안을 거절하셨습니다.\n" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:7 +#, python-format +msgid "%(rejecter_name)s says:" +msgstr "%(rejecter_name)s 님의 말:" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:11 +msgid "" +"\n" +"If you want, you can still try to transfer the project ownership to a " +"different person.\n" +msgstr "" +"\n" +"원한다면 프로젝트 소유권을 다른 사람에게 이전 할 수 있습니다.\n" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:15 +msgid "Request transfer to a different person:" +msgstr "다른 사람에게 이전 요청:" + +#: taiga/projects/templates/emails/transfer_reject-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer declined\n" +msgstr "" +"\n" +"[%(project)s] 프로젝트 소유권 이전 거절\n" + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(owner_name)s,

\n" +"

%(requester_name)s has requested to become the project owner for " +"\"%(project_name)s\".

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

안녕하세요 %(owner_name)s 님,\n" +"

\n" +"

%(requester_name)s 님께서 \"%(project_name)s\" 프로젝트의 소유자" +"가 되고 싶다고 요청하였습니다.

\n" +" " + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:9 +msgid "" +"\n" +"

Please, click on \"Continue\" if you would like to start the " +"project transfer from the administration panel.

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

관리자 패널에서 프로젝트 이전을 하고 싶으시다면 \"계속\"을 클릭해" +"주세요.

\n" +" " + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:14 +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:22 +msgid "Continue" +msgstr "계속" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(owner_name)s,\n" +"%(requester_name)s has requested to become the project owner for " +"\"%(project_name)s\".\n" +msgstr "" +"\n" +"안녕하세요 %(owner_name)s 님,\n" +"%(requester_name)s 님께서 \"%(project_name)s\" 프로젝트의 소유자가 되고 싶다" +"고 요청하였습니다.\n" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:6 +msgid "" +"\n" +"Please, go to your project settings if you would like to start the project " +"transfer from the administration panel.\n" +msgstr "" +"\n" +"관리자 패널에서 프로젝트 이전을 시작하고 싶으시면 프로젝트 설정으로 이동하세" +"요.\n" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:10 +msgid "Go to your project settings:" +msgstr "프로젝트 설정으로 이동:" + +#: taiga/projects/templates/emails/transfer_request-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer request\n" +msgstr "" +"\n" +"[%(project)s] 프로젝트 소유권 이전 요청\n" + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(receiver_name)s,

\n" +"

%(owner_name)s, the current project owner at \"%(project_name)s\" " +"would like you to become the new project owner.

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

안녕하세요 %(receiver_name)s 님,

\n" +"

\"%(project_name)s\" 프로젝트의 현재 소유자 %(owner_name)s 입니" +"다. 새로운 프로젝트 소유자가 되고 싶지 않으신가요?

\n" +" " + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:10 +#, python-format +msgid "" +"\n" +"

%(owner_name)s says:

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

%(owner_name)s 님의 말:

\n" +" " + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:17 +msgid "" +"\n" +"

Please, click on \"Continue\" to either accept or reject this " +"proposal.

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

이 요청을 수락하거나 거절하시려면 \"계속\"를 클릭하세요.

\n" +" " + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(receiver_name)s,\n" +"%(owner_name)s, the current project owner at \"%(project_name)s\" would like " +"you to become the new project owner.\n" +msgstr "" +"\n" +"안녕하세요 %(receiver_name)s 님,\n" +"\"%(project_name)s\" 프로젝트의 현재 소유자 %(owner_name)s 입니다. 새로운 프" +"로젝트 소유자가 되고 싶지 않으신가요?\n" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:6 +#, python-format +msgid "%(owner_name)s says:" +msgstr "%(owner_name)s 님의 말:" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:11 +msgid "" +"\n" +"Please, go to the following link to either accept or reject this proposal.\n" +msgstr "" +"\n" +"이 제안을 수락하거나 거부하려면 다음 링크로 이동하세요.

\n" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:15 +msgid "Accept or reject the project ownership transfer:" +msgstr "프로젝트 소유권 이전을 수락하거나 거부하세요.:" + +#: taiga/projects/templates/emails/transfer_start-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer offer\n" +msgstr "" +"\n" +"[%(project)s] 프로젝트 소유권 이전 제안\n" + +#. Translators: Name of scrum project template. +#: taiga/projects/translations.py:30 +msgid "Scrum" +msgstr "스크럼" + +#. Translators: Description of scrum project template. +#: taiga/projects/translations.py:32 +msgid "" +"The agile product backlog in Scrum is a prioritized features list, " +"containing short descriptions of all functionality desired in the product. " +"When applying Scrum, it's not necessary to start a project with a lengthy, " +"upfront effort to document all requirements. The Scrum product backlog is " +"then allowed to grow and change as more is learned about the product and its " +"customers" +msgstr "" +"스크럼의 애자일 제품 백 로그는 제품에 필요한 모든 기능의 간략한 설명이 포함" +"된 우선 순위 기능 목록입니다. 스크럼을 적용 할 때 모든 요구 사항을 문서화하" +"기 위해 오랜 시간을 투자하며 프로젝트를 시작할 필요가 없습니다. 스크럼 제품 " +"백 로그는 제품과 고객에 대해 더 많이 알게되면 확대되고 변경 될 수 있습니다." + +#. Translators: Name of kanban project template. +#: taiga/projects/translations.py:35 +msgid "Kanban" +msgstr "칸반" + +#. Translators: Description of kanban project template. +#: taiga/projects/translations.py:37 +msgid "" +"Kanban is a method for managing knowledge work with an emphasis on just-in-" +"time delivery while not overloading the team members. In this approach, the " +"process, from definition of a task to its delivery to the customer, is " +"displayed for participants to see and team members pull work from a queue." +msgstr "" +"칸반은 팀 구성원에게 과부하가 아닌 정시 전달에 중점을 둔 지식 작업 관리 방식" +"입니다. 이 작업 방식은 태스크 정의로부터 고객에게 전달될 때까지 참여자들이 " +"볼 수 있도록 표시되고 팀 구성원이 대기열로부터 일을 가져올 수 있습니다." + +#. Translators: User story point value (value = undefined) +#: taiga/projects/translations.py:45 +msgid "?" +msgstr "?" + +#. Translators: User story point value (value = 0) +#: taiga/projects/translations.py:47 +msgid "0" +msgstr "0" + +#. Translators: User story point value (value = 0.5) +#: taiga/projects/translations.py:49 +msgid "1/2" +msgstr "1/2" + +#. Translators: User story point value (value = 1) +#: taiga/projects/translations.py:51 +msgid "1" +msgstr "1" + +#. Translators: User story point value (value = 2) +#: taiga/projects/translations.py:53 +msgid "2" +msgstr "2" + +#. Translators: User story point value (value = 3) +#: taiga/projects/translations.py:55 +msgid "3" +msgstr "3" + +#. Translators: User story point value (value = 5) +#: taiga/projects/translations.py:57 +msgid "5" +msgstr "5" + +#. Translators: User story point value (value = 8) +#: taiga/projects/translations.py:59 +msgid "8" +msgstr "8" + +#. Translators: User story point value (value = 10) +#: taiga/projects/translations.py:61 +msgid "10" +msgstr "10" + +#. Translators: User story point value (value = 13) +#: taiga/projects/translations.py:63 +msgid "13" +msgstr "13" + +#. Translators: User story point value (value = 20) +#: taiga/projects/translations.py:65 +msgid "20" +msgstr "20" + +#. Translators: User story point value (value = 40) +#: taiga/projects/translations.py:67 +msgid "40" +msgstr "40" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:75 taiga/projects/translations.py:98 +#: taiga/projects/translations.py:114 +msgid "New" +msgstr "신규" + +#. Translators: User story status +#: taiga/projects/translations.py:78 +msgid "Ready" +msgstr "준비" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:81 taiga/projects/translations.py:100 +#: taiga/projects/translations.py:116 +msgid "In progress" +msgstr "작업중" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:84 taiga/projects/translations.py:102 +#: taiga/projects/translations.py:118 +msgid "Ready for test" +msgstr "테스트 준비 끝" + +#. Translators: User story status +#: taiga/projects/translations.py:87 +msgid "Done" +msgstr "완료됨" + +#. Translators: User story status +#: taiga/projects/translations.py:90 +msgid "Archived" +msgstr "보관됨" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:104 taiga/projects/translations.py:120 +msgid "Closed" +msgstr "완료됨" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:106 taiga/projects/translations.py:122 +msgid "Needs Info" +msgstr "정보 필요" + +#. Translators: Issue status +#: taiga/projects/translations.py:124 +msgid "Postponed" +msgstr "지연됨" + +#. Translators: Issue status +#: taiga/projects/translations.py:126 +msgid "Rejected" +msgstr "거부됨" + +#. Translators: Issue type +#: taiga/projects/translations.py:134 +msgid "Bug" +msgstr "버그" + +#. Translators: Issue type +#: taiga/projects/translations.py:136 +msgid "Question" +msgstr "질문" + +#. Translators: Issue type +#: taiga/projects/translations.py:138 +msgid "Enhancement" +msgstr "개선" + +#. Translators: Issue priority +#: taiga/projects/translations.py:146 +msgid "Low" +msgstr "낮음" + +#. Translators: Issue priority +#. Translators: Issue severity +#: taiga/projects/translations.py:148 taiga/projects/translations.py:161 +msgid "Normal" +msgstr "일반" + +#. Translators: Issue priority +#: taiga/projects/translations.py:150 +msgid "High" +msgstr "높음" + +#. Translators: Issue severity +#: taiga/projects/translations.py:157 +msgid "Wishlist" +msgstr "희망사항" + +#. Translators: Issue severity +#: taiga/projects/translations.py:159 +msgid "Minor" +msgstr "부가적" + +#. Translators: Issue severity +#: taiga/projects/translations.py:163 +msgid "Important" +msgstr "중요" + +#. Translators: Issue severity +#: taiga/projects/translations.py:165 +msgid "Critical" +msgstr "치명적" + +#. Translators: User role +#: taiga/projects/translations.py:172 +msgid "UX" +msgstr "UX" + +#. Translators: User role +#: taiga/projects/translations.py:174 +msgid "Design" +msgstr "디자인" + +#. Translators: User role +#: taiga/projects/translations.py:176 +msgid "Front" +msgstr "프론트" + +#. Translators: User role +#: taiga/projects/translations.py:178 +msgid "Back" +msgstr "이전" + +#. Translators: User role +#: taiga/projects/translations.py:180 +msgid "Product Owner" +msgstr "제품 소유자" + +#. Translators: User role +#: taiga/projects/translations.py:182 +msgid "Stakeholder" +msgstr "이해관계자" + +#: taiga/projects/userstories/api.py:128 +msgid "You don't have permissions to set this sprint to this user story." +msgstr "이 유저 스토리를 스프린트에 설정할 권한이 없습니다." + +#: taiga/projects/userstories/api.py:132 +msgid "You don't have permissions to set this status to this user story." +msgstr "이 유저 스토리의 상태를 설정할 권한이 없습니다." + +#: taiga/projects/userstories/api.py:222 +#, python-brace-format +msgid "Invalid role id '{role_id}'" +msgstr "'{role_id}' 역할 아이디는 유효하지 않습니다." + +#: taiga/projects/userstories/api.py:229 +#, python-brace-format +msgid "Invalid points id '{points_id}'" +msgstr "'{points_id}' 포인트 아이디는 유효하지 않습니다." + +#: taiga/projects/userstories/api.py:244 +#, python-brace-format +msgid "Generating the user story #{ref} - {subject}" +msgstr "유저 스토리 생성 #{ref} - {subject}" + +#: taiga/projects/userstories/models.py:41 +msgid "role" +msgstr "역할" + +#: taiga/projects/userstories/models.py:80 +msgid "backlog order" +msgstr "백로그 순서" + +#: taiga/projects/userstories/models.py:82 +msgid "sprint order" +msgstr "스프린트 순서" + +#: taiga/projects/userstories/models.py:84 +msgid "kanban order" +msgstr "칸반 순서" + +#: taiga/projects/userstories/models.py:92 +msgid "finish date" +msgstr "종료일" + +#: taiga/projects/userstories/models.py:107 +msgid "generated from issue" +msgstr "이슈로부터 생성됨" + +#: taiga/projects/userstories/validators.py:43 +msgid "There's no user story with that id" +msgstr "아이디를 가진 유저 스토리가 없습니다." + +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 +msgid "" +"Invalid user story status id. The status must belong to the same project." +msgstr "" +"유저 스토리 상태 아이디가 유효하지 않습니다. 상태는 반드시 동일한 프로젝트에 " +"속해야 합니다." + +#: taiga/projects/userstories/validators.py:121 +msgid "Invalid milestone id. The milistone must belong to the same project." +msgstr "" +"마일스톤 아이디가 유효하지 않습니다. 마일스톤은 반드시 같은 프로젝트에 속해" +"야 합니다." + +#: taiga/projects/userstories/validators.py:136 +msgid "" +"Invalid user story ids. All stories must belong to the same project and, if " +"it exists, to the same status and milestone." +msgstr "" +"유저 스토리 아이디가 유효하지 않습니다. 모든 유저 스토리는 반드시 같은 프로젝" +"트에 속해야 하고, 존재한다면, 같은 상태와 마일스톤을 가져야 합니다." + +#: taiga/projects/userstories/validators.py:160 +msgid "The milestone isn't valid for the project" +msgstr "마일스톤은 프로젝트에 유효하지 않습니다." + +#: taiga/projects/userstories/validators.py:170 +msgid "All the user stories must be from the same project" +msgstr "모든 유저 스토리는 반드시 동일한 프로젝트에 속해야 합니다." + +#: taiga/projects/validators.py:63 +msgid "There's no project with that id" +msgstr "아이디가 있는 프로젝트가 없습니다." + +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "이 프로젝트에는 아직 사용자가 없습니다." + +#: taiga/projects/validators.py:155 +msgid "Invalid role for the project" +msgstr "프로젝트에 유효하지 않은 역할입니다." + +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "사용자는 반드시 유효한 연락처이어야 합니다." + +#: taiga/projects/validators.py:191 +msgid "The project owner must be admin." +msgstr "프로젝트 소유자는 반드시 관리자이어야 합니다." + +#: taiga/projects/validators.py:195 +msgid "At least one user must be an active admin for this project." +msgstr "이 프로젝트의 최소 한 명의 사용자는 활성된 관리자이어야 합니다." + +#: taiga/projects/validators.py:240 +msgid "Invalid role ids. All roles must belong to the same project." +msgstr "" +"유효하지 않은 역할 아이디입니다. 모든 역할은 반드시 동일한 프로젝트에 속해야 " +"합니다." + +#: taiga/projects/validators.py:264 +msgid "Default options" +msgstr "기본 옵션" + +#: taiga/projects/validators.py:265 +msgid "User story's statuses" +msgstr "유저 스토리 상태" + +#: taiga/projects/validators.py:266 +msgid "Points" +msgstr "포인트" + +#: taiga/projects/validators.py:267 +msgid "Task's statuses" +msgstr "태스크 상태" + +#: taiga/projects/validators.py:268 +msgid "Issue's statuses" +msgstr "이슈 상태" + +#: taiga/projects/validators.py:269 +msgid "Issue's types" +msgstr "이슈 형태" + +#: taiga/projects/validators.py:270 +msgid "Priorities" +msgstr "우선순위" + +#: taiga/projects/validators.py:271 +msgid "Severities" +msgstr "심각도" + +#: taiga/projects/validators.py:272 +msgid "Roles" +msgstr "역할" + +#: taiga/projects/votes/models.py:33 taiga/projects/votes/models.py:34 +#: taiga/projects/votes/models.py:58 +msgid "Votes" +msgstr "투표" + +#: taiga/projects/votes/models.py:57 +msgid "Vote" +msgstr "투표" + +#: taiga/projects/wiki/api.py:77 +msgid "'content' parameter is mandatory" +msgstr "'content' 매개변수는 필수입니다." + +#: taiga/projects/wiki/api.py:80 +msgid "'project_id' parameter is mandatory" +msgstr "'project_id' 매개변수는 필수입니다." + +#: taiga/projects/wiki/models.py:42 +msgid "last modifier" +msgstr "마지막 수정자" + +#: taiga/projects/wiki/models.py:75 +msgid "href" +msgstr "href" + +#: taiga/timeline/signals.py:65 +msgid "Check the history API for the exact diff" +msgstr "확실한 차이점을 보려면 API 내역을 확인하십시오." + +#: taiga/users/admin.py:39 +msgid "Project Member" +msgstr "프로젝트 회원" + +#: taiga/users/admin.py:40 +msgid "Project Members" +msgstr "프로젝트 회원" + +#: taiga/users/admin.py:50 +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:122 +msgid "Permissions" +msgstr "권한" + +#: taiga/users/admin.py:123 +msgid "Restrictions" +msgstr "제한 사항" + +#: taiga/users/admin.py:125 +msgid "Important dates" +msgstr "중요한 날짜" + +#: taiga/users/api.py:132 +msgid "Duplicated email" +msgstr "중복된 이메일" + +#: taiga/users/api.py:134 +msgid "Not valid email" +msgstr "유효하지 않은 이메일" + +#: taiga/users/api.py:172 +msgid "Invalid username or email" +msgstr "아이디 또는 이메일이 유효하지 않습니다." + +#: taiga/users/api.py:181 +msgid "Mail sended successful!" +msgstr "성공적으로 메일을 전송했습니다!" + +#: taiga/users/api.py:219 +msgid "Current password parameter needed" +msgstr "현재 비밀번호 매개변수가 필요합니다." + +#: taiga/users/api.py:222 +msgid "New password parameter needed" +msgstr "새로운 비밀번호 매개변수가 필요합니다." + +#: taiga/users/api.py:225 +msgid "Invalid password length at least 6 charaters needed" +msgstr "" +"비밀번호의 길이가 유효하지 않습니다. 최소한 6자가 필요합니다.\n" +"Invalid password length at least 6 charaters needed" + +#: taiga/users/api.py:228 +msgid "Invalid current password" +msgstr "현재 비밀번호는 유효하지 않습니다.Invalid current password" + +#: taiga/users/api.py:275 taiga/users/api.py:281 +msgid "" +"Invalid, are you sure the token is correct and you didn't use it before?" +msgstr "유효하지 않음, 토큰이 정확하고 이전에 사용하지 않았습니까?" + +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 +msgid "Invalid, are you sure the token is correct?" +msgstr "유효하지 않음, 토큰이 정확합니까?are you sure the token is correct?" + +#: taiga/users/models.py:98 +msgid "superuser status" +msgstr "슈퍼유저 상태" + +#: taiga/users/models.py:99 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "이 사용자가 명시적인 할당을 받지 않고 모든 권한을 가지는지 지정합니다." + +#: taiga/users/models.py:129 +msgid "username" +msgstr "아이디" + +#: taiga/users/models.py:130 +msgid "" +"Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" +msgstr "필수. 30자 이하. 문자, 숫자 그리고 /./-/_ characters" + +#: taiga/users/models.py:133 +msgid "Enter a valid username." +msgstr "올바른 아이디를 입력해주세요." + +#: taiga/users/models.py:136 +msgid "active" +msgstr "활성" + +#: taiga/users/models.py:137 +msgid "" +"Designates whether this user should be treated as active. Unselect this " +"instead of deleting accounts." +msgstr "" +"이 사용자를 활성 상태로 취급할 것인지 지정합니다. 계정을 삭제하는 대신에 선택" +"을 해제하십시오." + +#: taiga/users/models.py:143 +msgid "biography" +msgstr "바이오그래피" + +#: taiga/users/models.py:146 +msgid "photo" +msgstr "사진" + +#: taiga/users/models.py:147 +msgid "date joined" +msgstr "가입한 날짜" + +#: taiga/users/models.py:149 +msgid "default language" +msgstr "기본 언어" + +#: taiga/users/models.py:151 +msgid "default theme" +msgstr "기본 테마" + +#: taiga/users/models.py:153 +msgid "default timezone" +msgstr "기본 시간대" + +#: taiga/users/models.py:155 +msgid "colorize tags" +msgstr "태그 색 칠하기" + +#: taiga/users/models.py:160 +msgid "email token" +msgstr "이메일 토큰" + +#: taiga/users/models.py:162 +msgid "new email address" +msgstr "새로운 이메일 주소" + +#: taiga/users/models.py:169 +msgid "max number of owned private projects" +msgstr "소유할 수 있는 비공개 프로젝트의 최대 개수" + +#: taiga/users/models.py:172 +msgid "max number of owned public projects" +msgstr "소유할 수 있는 공공 프로젝트의 최대 개수" + +#: taiga/users/models.py:175 +msgid "max number of memberships for each owned private project" +msgstr "소유한 비공개 프로젝트당 수용할 수 있는 최대 회원수" + +#: taiga/users/models.py:179 +msgid "max number of memberships for each owned public project" +msgstr "소유한 공공 프로젝트당 수용할 수 있는 최대 회원수" + +#: taiga/users/models.py:307 +msgid "permissions" +msgstr "권한" + +#: taiga/users/services.py:51 taiga/users/services.py:68 +msgid "Username or password does not matches user." +msgstr "아이디 또는 비밀번호가 올바르지 않습니다." + +#: taiga/users/templates/emails/change_email-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Change your email

\n" +"

Hello %(full_name)s,
please confirm your email

\n" +" Confirm " +"email\n" +"

You can ignore this message if you did not request.

\n" +"

The Taiga Team

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

이메일 변경

\n" +"

안녕하세요 %(full_name)s 님,
이메일을 확인해주세요.

\n" +" 이메일 확인\n" +"

직접 요청하시지 않았다면 이 메시지를 무시하실 수 있습니다.

\n" +"

타이가 팀

\n" +" " + +#: taiga/users/templates/emails/change_email-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(full_name)s, please confirm your email\n" +"\n" +"%(url)s\n" +"\n" +"You can ignore this message if you did not request.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"안녕하세요 %(full_name)s 님, 이메일을 확인해주세요.\n" +"\n" +"%(url)s\n" +"\n" +"직접 요청하지 않았다면 이 메시지를 무시하실 수 있습니다.\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/users/templates/emails/change_email-subject.jinja:1 +msgid "[Taiga] Change email" +msgstr "[타이가] 이메일 변경" + +#: taiga/users/templates/emails/password_recovery-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Recover your password

\n" +"

Hello %(full_name)s,
you asked to recover your password

\n" +"
Recover your password\n" +"

You can ignore this message if you did not request.

\n" +"

The Taiga Team

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

비밀번호를 복구합니다.

\n" +"

안녕하세요 %(full_name)s 님,
당신은 암호를 복구하도록 요청하셨" +"습니다.

\n" +" 비밀번" +"호 복구하기\n" +"

직접 요청하시지 않았다면 이 메시지를 무시하실 수 있습니다.

\n" +"

타이가 팀

\n" +" " + +#: taiga/users/templates/emails/password_recovery-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(full_name)s, you asked to recover your password\n" +"\n" +"%(url)s\n" +"\n" +"You can ignore this message if you did not request.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"안녕하세요 %(full_name)s 님, 당신은 암호를 복구하도록 요청하셨습니다.\n" +"\n" +"%(url)s\n" +"\n" +"직접 요청하시지 않았다면 이 메시지를 무시하실 수 있습니다.\n" +"\n" +"---\n" +"타이가 팀\n" + +#: taiga/users/templates/emails/password_recovery-subject.jinja:1 +msgid "[Taiga] Password recovery" +msgstr "[타이가] 비밀번호 복구" + +#: taiga/users/templates/emails/registered_user-body-html.jinja:6 +msgid "" +"\n" +"
\n" +" " +msgstr "" +"\n" +" \n" +" " + +#: taiga/users/templates/emails/registered_user-body-html.jinja:23 +#, python-format +msgid "" +"\n" +" You may remove your account from this service clicking " +"here\n" +" " +msgstr "" +"\n" +" 이 서비스에서 귀하의 계정을 삭제할 수 있습니다. 여기를 클릭하세요.\n" +" " + +#: taiga/users/templates/emails/registered_user-body-text.jinja:1 +msgid "" +"\n" +"Thank you for registering in Taiga\n" +"\n" +"We hope you enjoy it\n" +"\n" +"We built Taiga because we wanted the project management tool that sits open " +"on our computers all day long, to serve as a continued reminder of why we " +"love to collaborate, code and design.\n" +"\n" +"We built it to be beautiful, elegant, simple to use and fun - without " +"forsaking flexibility and power.\n" +"\n" +"--\n" +"The taiga Team\n" +msgstr "" +"\n" +"타이가에 가입해주셔서 감사합니다.\n" +"\n" +"우리는 당신이 즐기길 원합니다.\n" +"\n" +"우리는 타이가를 컴퓨터에 하루 종일 열려있는 프로젝트 관리 도구로, 왜 우리가 " +"협업과 코드와 디자인을 사랑하는지 계속해서 다시금 떠올릴 수 있도록 만들었습니" +"다.\n" +"\n" +"우리는 아름답고, 우아하고, 간결하며 재미있게 사용할 수 있도록 만들었습니다. " +"- 유연성과 강력함을 버리지 않고 말이죠.\n" +"\n" +"-\n" +"타이가 팀\n" + +#: taiga/users/templates/emails/registered_user-body-text.jinja:13 +#, python-format +msgid "" +"\n" +"You may remove your account from this service: %(url)s\n" +msgstr "" +"\n" +"이 서비스에서 귀하의 계정을 삭제할 수 있습니다: %(url)s\n" + +#: taiga/users/templates/emails/registered_user-subject.jinja:1 +msgid "You've been Taigatized!" +msgstr "타이가화 되었습니다!" + +#: taiga/users/validators.py:45 +msgid "invalid" +msgstr "유효하지 않음" + +#: taiga/users/validators.py:56 +msgid "Invalid username. Try with a different one." +msgstr "유효하지 않은 아이디입니다. 다른 것으로 다시 시도해주세요." + +#: taiga/userstorage/api.py:53 +msgid "" +"Duplicate key value violates unique constraint. Key '{}' already exists." +msgstr "키 값이 고유 제한 조건을 위반합니다. '{}'키가 이미 있습니다." + +#: taiga/userstorage/models.py:32 +msgid "key" +msgstr "키" + +#: taiga/webhooks/models.py:30 taiga/webhooks/models.py:40 +msgid "URL" +msgstr "URL" + +#: taiga/webhooks/models.py:31 +msgid "secret key" +msgstr "비밀 키" + +#: taiga/webhooks/models.py:41 +msgid "status code" +msgstr "상태 코드" + +#: taiga/webhooks/models.py:42 +msgid "request data" +msgstr "요청 데이터" + +#: taiga/webhooks/models.py:43 +msgid "request headers" +msgstr "요청 헤더" + +#: taiga/webhooks/models.py:44 +msgid "response data" +msgstr "응답 데이터" + +#: taiga/webhooks/models.py:45 +msgid "response headers" +msgstr "응답 헤더" + +#: taiga/webhooks/models.py:46 +msgid "duration" +msgstr "지속시간" diff --git a/taiga/locale/nb/LC_MESSAGES/django.po b/taiga/locale/nb/LC_MESSAGES/django.po index 0524d9ef..02037b90 100644 --- a/taiga/locale/nb/LC_MESSAGES/django.po +++ b/taiga/locale/nb/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/taiga-agile-llc/" "taiga-back/language/nb/)\n" @@ -19,15 +19,15 @@ msgstr "" "Language: nb\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Offentlig register er deaktivert." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "ugyldig registertype" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "ugyldig påloggingstype" @@ -47,17 +47,17 @@ msgstr "Poletten samvsarer ikke med noen gyldig invitasjon." msgid "User is already registered." msgstr "Brukeren er allerede registrert." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Denne brukeren er allerede et medlem i prosjektet." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Feil ved å lage ny bruker." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Ugyldig polett" @@ -78,110 +78,110 @@ msgstr "Dette feltet er obligatorisk." msgid "Invalid value." msgstr "Ugyldig verdi." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' verdi må være enten 'True' eller 'False'" -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Skriv inn en gyldig 'slug' bestående av bokstaver, tall, understreker eller " "bindestreker. " -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Gjør et gyldig valg. %(value)s er ikke et av de tilgjengelige valgene." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Skriv inn en gyldig epostadresse." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "Datoen har feil format. Bruk en av disse formatene istedet: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "Datotid har feil format. Bruk en av disse formatene istedet: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Tid har feil format. Bruk en av disse formatene istedet: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Skriv inn et heltall." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Sikre at denne verdien er mindre enn eller lik %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Sikre at denne verdien er større enn eller lik %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" verdi må være et desimaltall." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Skriv inn et nummer." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Pass på at det ikke er flere enn %s sifre totalt." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Pass på at det ikke er flere enn %s desimaler." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Pass på at det ikke er flere enn %s siffer før komma." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Ingen fil ble sendt. Kontroller kodingstypen på skjemaet." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Ingen fil ble sendt." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Den sendte filen er tom." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "" "Sikre at dette filnavnet har på det meste %(max)d tegn (det har %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Vennligst enten send inn en fil eller sjekk den klare sjekkboksen, ikke " "begge deler." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -190,26 +190,26 @@ msgstr "" "et ødelagt bilde." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Blokkert element" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "" "Siden er ikke 'sist', og den kan heller ikke konverteres til en integer." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Ugyldig side (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Ugyldig tilgangsdefinisjon." @@ -274,7 +274,7 @@ msgstr "Ikke funnet" msgid "Permission denied" msgstr "Tilgang nektet" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Server programfeil" @@ -353,11 +353,11 @@ msgstr "Forutsetningsfeil" msgid "No room left for more projects." msgstr "Ingen plass igjen til nye prosjekter." -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Feil i filterparameter typer" -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' må være et heltall" @@ -366,43 +366,43 @@ msgstr "'project' må være et heltall" msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Følg oss på Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Skaff koden på GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Besøk vår webside" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -668,6 +668,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -683,6 +684,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -701,6 +703,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "" @@ -754,7 +757,7 @@ msgid "It contain invalid custom fields." msgstr "Den inneholder ugyldige egendefinerte feilter" #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Navnet er duplisert for prosjektet" @@ -765,13 +768,13 @@ msgstr "Autentisering kreves" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "navn" @@ -783,12 +786,12 @@ msgstr "Ikon url" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "beskrivelse" @@ -797,36 +800,34 @@ msgstr "beskrivelse" msgid "Next url" msgstr "Neste url" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "bruker" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "applikasjon" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "fullt navn" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "epostadresse" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "kommentar" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -898,9 +899,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Payloaden er ikke gyldig json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Prosjektet eksisterer ikke" @@ -988,6 +989,221 @@ msgstr "Det refererte elementet finnes ikke" msgid "The status doesn't exist" msgstr "Statusen eksisterer ikke" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +"
Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Vis prosjekt" @@ -1156,83 +1372,83 @@ msgstr "Admin roller" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "eier" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Ufullstendige argumenter" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Ugyldig bildeformat" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Ikke et gyldig malnavn" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Ikke en gyldig malbeskrivelse" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "Ugyldig brukerid" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "Brukeren eksisterer ikke" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "Brukeren må allerede være et medlem i et prosjekt" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" @@ -1240,7 +1456,7 @@ msgstr "" "Prosjektet må ha en eier og minst en av brukerne må være en aktiv " "administrator" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Du har ikke tillatelser til å se det." @@ -1256,18 +1472,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Prosjekt ID matcher ikke mellom objekt og prosjekt" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "prosjekt" @@ -1281,9 +1497,9 @@ msgstr "objektid" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1301,14 +1517,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "er foreldet" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "rekkefølge" @@ -1344,19 +1564,71 @@ msgstr "Dette prosjektet er blokkert fordi eieren stakk" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Tekst" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Tekst med flere linjer" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Dato" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1390,54 +1662,59 @@ msgstr "hendelse" msgid "Already exists one with the same name." msgstr "Det finnes allerede en med samme navn." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "status" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "subjekt" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "farge" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "tildelt til" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "Er klientkrav" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "Er team behov" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "ekstern referanse" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1470,102 +1747,102 @@ msgstr "Opprett" msgid "Delete" msgstr "Slett" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s rollepoeng" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "fra" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "til" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "La til nytt vedlegg" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Oppdatert vedlegg" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "foreldet" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "ikke foreldet" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Slettede vedlegg" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "lagt til" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "fjernet" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Ikke tildelt" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-slettet-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "til:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "fra:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Lagt til" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Endret" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Slettet" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "lagt til: " -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "fjernet: " -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Fra: " -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Til: " @@ -1583,26 +1860,26 @@ msgstr "blokkert notat" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Du har ikke tillatelse til å sette denne sprinten til denne hendelsen." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Du har ikke tillatelse til å sette denne statusen til denne hendelsen." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "" "Du har ikke tillatelse til å sette denne alvorlighetsgraden til denne " "hendelsen." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "" "Du har ikke tillatelse til å sette denne prioriteten til denne hendelsen" -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Du har ikke tillatelse til å sette denne typen til denne hendelsen." @@ -1623,11 +1900,6 @@ msgstr "milepæl" msgid "finished date" msgstr "Sluttdato" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "ekstern referanse" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Liker" @@ -1636,33 +1908,33 @@ msgstr "Liker" msgid "Likes" msgstr "Liker" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "anslått startdato" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "anslått sluttdato" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "er lukket" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "" @@ -1674,6 +1946,14 @@ msgstr "" msgid "is blocked" msgstr "er blokkert" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1683,236 +1963,260 @@ msgstr "'{param}' parameter er obligatorisk" msgid "'project' parameter is mandatory" msgstr "'project' parameter er obligatorisk" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "epost" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "opprett ved" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "invitasjon ekstra tekst" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "bruker rekkefølge" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Denne brukeren er allerede medlem av prosjektet" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "standard brukerhistoriestatuser" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "standardpoeng" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "standard oppgavestatuser" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "standard prioriteter" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "standard alvorlighetsgrad" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "standard hendelsesstatuser" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "standard hendelsestyper" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "logo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "medlemmer" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "total av milepæler" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "total historiepoeng" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "aktivt backlogpanel" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "aktivt kanbanpanel" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "aktivt wikipanel" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "aktivt hendelsespanel" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "videokonferansesystem" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "videokonferanse ekstra data" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "skapelsesmal" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "er privat" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "anonymes rettigheter" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "brukerrettigheter" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "er omtalt" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "er søker etter folk" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "søker etter folk notat" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "" -#: taiga/projects/models.py:218 +#: taiga/projects/models.py:222 msgid "project transfer token" msgstr "prosjektflyttingstoken" -#: taiga/projects/models.py:222 +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "blokkert kode" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "oppdatert dato tid" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "antall" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "fans forrige uke" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "fans forrige måned" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "fans forrige år" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "aktivitet forrige uke" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "aktivitet forrige måned" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "aktivitet forrige år" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "modulkonfigurasjon" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "er arkivert" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "arbeid som pågår grense" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "verdi" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "standard eiers rolle" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "standardvalg" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "bh statuser" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "poeng" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "oppgavestatuser" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "hendelsesstatuser" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "hendelsestyper" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "prioriteter" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "alvorlighetsgrader" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "roller" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Involvert" @@ -1947,7 +2251,7 @@ msgstr "Fulgt" msgid "Notify exists for specified user and project" msgstr "" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Ugyldig verdi for varslingsnivå" @@ -2542,39 +2846,43 @@ msgstr "" "Du kan ikke forlate prosjektet hvis du er eieren eller det ikke er flere " "administratorer" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "Du har nådd din nåværende grense for medlemskap for private prosjekter" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" "Du har nådd din nåværende grense for medlemskap for offentlige prosjekter" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "Du kan ikke ha fler private prosjekter" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" "Dette prosjektet kommer til å nå din nåværende grense for medlemskap for " "private prosjekter" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "Du kan ikke ha flere offentlige prosjekter" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2591,8 +2899,8 @@ msgstr "Prosjektslutt" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Token er ugyldig" @@ -2642,16 +2950,16 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Du har ikke tillatelse til å sette denne sprinten til denne oppgaven." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" "Du har ikke tillatelse til å sette denne brukerhistorien til denne oppgaven." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Du har ikke tillatelse til å sette denne statusen til denne oppgaven." @@ -2667,31 +2975,31 @@ msgstr "Oppgavetavle rekkefølge" msgid "is iocaine" msgstr "Er Iocaine" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3267,39 +3575,31 @@ msgstr "Produkteier" msgid "Stakeholder" msgstr "Interessent" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Du har ikke tillatelse til å sette denne sprinten til denne brukerhistorien." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "Du har ikke tillatelse til å sette denne statusen til denne brukerhistorien." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Genererer brukerhistorien #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rolle" @@ -3328,87 +3628,91 @@ msgstr "" msgid "There's no user story with that id" msgstr "Det finnes ingen brukerhistorie med den id'en" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Det finnes ikke noe prosjekt med den id'en" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "E-postadressen er allerede tatt" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Ugyldig rolle for prosjektet" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "Prosjekteieren skal være admin." -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "Minst en bruker må være en aktiv administrator for dette prosjektet." -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Standardvalgene" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Brukerhistoriestatuser" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Poeng" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Oppgavestatuser" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Hendelsesstatuser" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Hendelsestyper" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioriteter" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Alvorlighetsgrad" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Roller" @@ -3437,7 +3741,7 @@ msgstr "sist endret av" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Sjekk historieAPI'et for den eksakte forskjellen" @@ -3477,77 +3781,77 @@ msgstr "Restriksjoner" msgid "Important dates" msgstr "Viktige datoer" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Duplikat e-post" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Ikke gyldig epost" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Ugyldig brukernavn eller epost" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Epost sendt!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Nåværende passord er nødvendig" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Nytt passord er nødvendig" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Ugyldig lengde på passord. Minst 6 tegn" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Ugyldig nåværende passord" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Ugyldig, er du sikker på at token er korrekt og at du ikke har brukt den før?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Ugyldig, er du sikker på at token er korrekt?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "superbrukerstatus" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "" "Angir at denne brukeren har alle tillatelser uten eksplisitt tildele dem." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "brukernavn" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Påkrevd. 30 tegn eller færre. Bokstaver, tall og /./-/_ tegn" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Skriv inn et gyldig brukernavn" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "aktiv" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3555,59 +3859,59 @@ msgstr "" "Betegner om denne brukeren bør behandles som aktiv. Velg bort dette i stedet " "for å slette kontoer." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografi" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "bilde" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "dato ble med" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "standardspråk" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "standard tema" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "standard tidssone" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "fargelegg etiketter" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "epost token" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "ny epostadresse" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "maks antall eide private prosjekter" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "maks antall eide offentlige prosjekter" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "maks antall medlemskap for hvert eide private prosjekt" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "maks antall medlemskap for hvetr eide offentlige prosjekt" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "rettigheter" diff --git a/taiga/locale/nl/LC_MESSAGES/django.po b/taiga/locale/nl/LC_MESSAGES/django.po index 0a0cd5ad..415ad207 100644 --- a/taiga/locale/nl/LC_MESSAGES/django.po +++ b/taiga/locale/nl/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Dutch (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/nl/)\n" @@ -20,15 +20,15 @@ msgstr "" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Publieke registratie is uitgeschakeld." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "ongeldig registratie type" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "ongeldig login type" @@ -48,17 +48,17 @@ msgstr "Token stemt niet overeen met een geldige uitnodiging." msgid "User is already registered." msgstr "Gebruiker is al geregistreerd." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Fout bij het aanmaken van een nieuwe gebruiker." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Ongeldig token" @@ -79,104 +79,104 @@ msgstr "Dit veld is verplicht." msgid "Invalid value." msgstr "Ongeldige waarde." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' waarde moet True of False zijn." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Geef een geldige 'slug' in bestaande uit letters, nummers, underscores of " "koppeltekens." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Selecteer een geldige keuze. %(value)s is niet één van de aanwezige " "keuzemogelijkheden." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Voeg een geldig e-mail adres toe." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "" "Datum heeft het verkeerde formaat. Gebruik één van de volgende formaten: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "" "Datum en tijd heeft het verkeerde formaat. Gebruik één van de volgende " "formaten: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "" "Tijd heeft een verkeerd formaat. Gebruik één van de volgende formaten: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Geef een geheel getal in." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Zorg ervoor dat deze waarde minder of gelijk is aan %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Zorg ervoor dat deze waarde groter of gelijk is aan %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" waarde dient een float te zijn." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Geef een getal in." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Zorg ervoor dat er niet meer dan %s nummers in totaal zijn." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Zorg ervoor dat er niet meer dan %s plaatsen na de comma zijn." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Zorg ervoor dat er niet meer dan %s nummers voor de comma staan." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Er was geen bestand aangegeven. Bekijken het type encoding in het formulier." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Er was geen bestand aangegeven." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Het gegeven bestand is leeg." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -184,13 +184,13 @@ msgstr "" "Zorg ervoor dat deze bestandsnaam maximaal %(max)d tekens lang is (de naam " "heeft %(length)d tekens)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Gelieve ofwel een bestand mee te geven ofwel de checkbox aan te tikken, niet " "beide." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -199,25 +199,25 @@ msgstr "" "een afbeelding ofwel een corrupte afbeelding." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Pagina is niet 'last', noch kan het omgezet worden naar een int." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Ongeldige pagina (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Ongeldige definitie van permissie." @@ -282,7 +282,7 @@ msgstr "Niet gevonden" msgid "Permission denied" msgstr "Toestemming geweigerd" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Server applicatie fout" @@ -361,11 +361,11 @@ msgstr "Preconditie fout" msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Fout in filter params types." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' moet een integer waarde zijn." @@ -374,43 +374,43 @@ msgstr "'project' moet een integer waarde zijn." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Volg ons op Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Haal de code op GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Bezoek onze website" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -698,6 +698,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -713,6 +714,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -731,6 +733,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -784,7 +787,7 @@ msgid "It contain invalid custom fields." msgstr "Het bevat ongeldige eigen velden:" #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Naam gedupliceerd voor het project" @@ -795,13 +798,13 @@ msgstr "" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "naam" @@ -813,12 +816,12 @@ msgstr "" msgid "web" msgstr "" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "omschrijving" @@ -827,36 +830,34 @@ msgstr "omschrijving" msgid "Next url" msgstr "" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "volledige naam" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "e-mail adres" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "commentaar" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -928,9 +929,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "De payload is geen geldige json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Het project bestaat niet" @@ -1018,6 +1019,221 @@ msgstr "Het element waarnaar verwezen wordt bestaat niet" msgid "The status doesn't exist" msgstr "De status bestaat niet" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Bekijk project" @@ -1186,89 +1402,89 @@ msgstr "Admin rollen" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "eigenaar" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Onvolledige argumenten" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Ongeldig afbeelding formaat" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Ongeldige template naam" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Ongeldige template omschrijving" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Je hebt geen toestamming om dat te bekijken." @@ -1284,18 +1500,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Project ID van object is niet gelijk aan die van het project" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "project" @@ -1309,9 +1525,9 @@ msgstr "object id" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1329,14 +1545,18 @@ msgstr "" msgid "is deprecated" msgstr "is verouderd" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "volgorde" @@ -1372,19 +1592,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 -msgid "Text" +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" msgstr "" #: taiga/projects/custom_attributes/choices.py:29 -msgid "Multi-Line Text" +msgid "Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:30 -msgid "Date" +msgid "Multi-Line Text" msgstr "" #: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1418,54 +1690,59 @@ msgstr "issue" msgid "Already exists one with the same name." msgstr "Er bestaat er al één met dezelfde naam." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "status" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "onderwerp" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "kleur" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "toegewezen aan" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "is requirement van de klant" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "is requirement van het team" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "externe referentie" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1498,102 +1775,102 @@ msgstr "Creëer" msgid "Delete" msgstr "Verwijder" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s rol punten" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "van" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "naar" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Nieuwe bijlage toegevoegd" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Bijlage bijgewerkt" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "verouderd" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "niet verouderd" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Bijlage verwijderd" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "toegevoegd" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "verwijderd" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Niet toegewezen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-verwijderd-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "naar:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "van:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Toegevoegd" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Veranderd" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Verwijderd" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "toegevoegd:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "verwijderd:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Van:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Naar:" @@ -1611,25 +1888,25 @@ msgstr "geblokkeerde notitie" msgid "sprint" msgstr "" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Je hebt geen toestemming om deze sprint op deze issue te zetten." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Je hebt geen toestemming om deze status toe te kennen aan dze issue." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "" "Je hebt geen toestemming om dit ernstniveau toe te kennen aan deze issue." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "" "Je hebt geen toestemming om deze prioriteit toe te kennen aan deze issue." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Je hebt geen toestemming om dit type toe te kennen aan deze issue." @@ -1650,11 +1927,6 @@ msgstr "milestone" msgid "finished date" msgstr "datum van afwerking" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "externe referentie" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Vind ik leuk" @@ -1663,33 +1935,33 @@ msgstr "Vind ik leuk" msgid "Likes" msgstr "Personen die dit leuk vinden" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "geschatte start datum" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "geschatte datum van afwerking" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "is gesloten" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "beschikbaarheid" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "The geschatte start moet vroeger zijn dan het geschatte einde." @@ -1701,6 +1973,14 @@ msgstr "" msgid "is blocked" msgstr "is geblokkeerd" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1710,236 +1990,260 @@ msgstr "'{param}' parameter is verplicht" msgid "'project' parameter is mandatory" msgstr "'project' parameter is verplicht" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "e-mail" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "aangemaakt op" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "uitnodiging extra text" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "gebruiker volgorde" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "The gebruikers is al lid van het project" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "standaard US status" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "standaard punten" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "default taak status" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "standaard prioriteit" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "standaard ernstniveau" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "standaard issue status" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "standaard issue type" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "leden" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "totaal van de milestones" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "totaal story points" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "actief backlog paneel" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "actief kanban paneel" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "actief wiki paneel" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "actief issues paneel" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "videoconference systeem" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "aanmaak template" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "is privé" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "anonieme toestemmingen" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "gebruikers toestemmingen" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "gewijzigde datum en tijd" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "module config" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "is gearchiveerd" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "work in progress limiet" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "waarde" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "standaard rol eigenaar" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "standaard instellingen" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "us statussen" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "punten" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "taak statussen" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "issue statussen" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "issue types" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "prioriteiten" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "ernstniveaus" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "rollen" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "" @@ -1974,7 +2278,7 @@ msgstr "" msgid "Notify exists for specified user and project" msgstr "Verwittiging bestaat voor gespecifieerde gebruiker en project" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "" @@ -2597,36 +2901,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2641,8 +2949,8 @@ msgstr "Project einde" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Token is ongeldig" @@ -2692,15 +3000,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "" @@ -2716,31 +3024,31 @@ msgstr "takenbord volgorde" msgid "is iocaine" msgstr "is iocaine" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3340,37 +3648,29 @@ msgstr "Product Owner" msgid "Stakeholder" msgstr "Stakeholder" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rol" @@ -3399,87 +3699,91 @@ msgstr "gegenereerd van issue" msgid "There's no user story with that id" msgstr "Er is geen user story met dat id" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Er is geen project met dat is" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "E-mail adres is al in gebruik" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Ongeldige rol voor project" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Standaard opties" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Status van User story" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Punten" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Statussen van taken" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Statussen van Issues" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Types van issue" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioriteiten" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Ernstniveaus" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Rollen" @@ -3508,7 +3812,7 @@ msgstr "gebruiker met laatste wijziging" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "" @@ -3548,52 +3852,52 @@ msgstr "" msgid "Important dates" msgstr "Belangrijke data" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Gedupliceerde e-mail" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Ongeldige e-mail" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Ongeldige gebruikersnaam of e-mail" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Mail met succes verzonden!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Huidig wachtwoord parameter vereist" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Nieuw wachtwoord parameter vereist" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Ongeldige lengte van wachtwoord, minstens 6 tekens vereist" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Ongeldig huidig wachtwoord" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "Ongeldig, weet je zeker dat het token correct en ongebruikt is?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Ongeldig, weet je zeker dat het token correct is?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "superuser status" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -3601,24 +3905,24 @@ msgstr "" "Beduidt dat deze gebruik alle toestemmingen heeft zonder deze expliciet toe " "te wijzen." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "gebruikersnaam" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Vereist. 30 of minder karakters. Letters, nummers en /./-/_ karakters" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Geef een geldige gebruikersnaam in" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "actief" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3626,59 +3930,59 @@ msgstr "" "Beduidt of deze gebruiker als actief moet behandeld worden. Deselecteer dit " "i.p.v. accounts te verwijderen." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografie" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "foto" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "toetrededatum" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "standaard taal" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "standaard tijdzone" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "kleur tags" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "e-mail token" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "nieuw e-mail adres" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "toestemmingen" diff --git a/taiga/locale/permissions.py b/taiga/locale/permissions.py index 3146d58e..fc22c58c 100644 --- a/taiga/locale/permissions.py +++ b/taiga/locale/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/locale/pl/LC_MESSAGES/django.po b/taiga/locale/pl/LC_MESSAGES/django.po index b40c46b2..234fc090 100644 --- a/taiga/locale/pl/LC_MESSAGES/django.po +++ b/taiga/locale/pl/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Polish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/pl/)\n" @@ -19,18 +19,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>=14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Publiczna rejestracja jest wyłączona" -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "Nieprawidłowy typ rejestracji" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "Nieprawidłowy typ logowania" @@ -50,17 +51,17 @@ msgstr "Token nie zgadza się z żadnym zaproszeniem" msgid "User is already registered." msgstr "Użytkownik już zarejestrowany" -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Błąd przy tworzeniu użytkownika." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Nieprawidłowy token" @@ -81,98 +82,98 @@ msgstr "To pole jest wymagane." msgid "Invalid value." msgstr "Nieprawidłowa wartość." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' wartość musi przyjąć True albo False," -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Podaj prawidłowy 'slug' zawierający litery, cyfry, podkreślenia lub myślniki." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Dokonał właściwego wyboru. Wartość %(value)s nie jest jedną z dostępnych " "opcji." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Podaj właściwy adres email." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "Zły format. Użyj jednego z tych formatów: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "Zły format. Użyj jednego z tych formatów: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Zły format. Użyj jednego z tych formatów: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Wpisz cały numer" -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Upewnij się, że wartość jest mniejsza lub równa od %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Upewnij się, że wartość jest większa lub równa od %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" wartość musi być zmiennoprzecinkowa." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Wpisz numer." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Upewnij się że nie podałeś więcej niż %s znaków." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Upewnij się, że nie ma więcej niż %s miejsc po przecinku." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Upewnij się, że nie ma więcej niż %s cyfr przed przecinkiem." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Plik nie został wysłany. Sprawdź kodowanie znaków w formularzu." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Plik nie został wysłany." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Wysłany plik jest pusty." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -180,11 +181,11 @@ msgstr "" "Upewnij się, że nazwa pliku ma maksymalnie %(max)d znaków.(Ilość znaków to: " "%(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Proszę wybrać jedną z opcji, nie obie." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -193,25 +194,25 @@ msgstr "" "jest uszkodzony." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Strona nie jest ostatnią i nie może zostać zmieniona na int." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Niewłaściwa strona (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Nieprawidłowa definicja uprawnień." @@ -277,7 +278,7 @@ msgstr "Nie znaleziono" msgid "Permission denied" msgstr "Dostęp zabroniony" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Błąd aplikacji serwera" @@ -356,11 +357,11 @@ msgstr "Błąd warunków wstępnych" msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Błąd w parametrach typów filtrów." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' musi być wartością typu int." @@ -369,43 +370,43 @@ msgstr "'project' musi być wartością typu int." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Obserwuj nas na Twitterze" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Pobierz kog z GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Odwiedź naszą stronę www" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -723,6 +724,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -748,6 +750,7 @@ msgstr "" " " #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -780,6 +783,7 @@ msgstr "" "Zespół Taiga\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -852,7 +856,7 @@ msgid "It contain invalid custom fields." msgstr "Zawiera niewłaściwe pola niestandardowe." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Nazwa projektu zduplikowana" @@ -863,13 +867,13 @@ msgstr "" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "nazwa" @@ -881,12 +885,12 @@ msgstr "" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "opis" @@ -895,36 +899,34 @@ msgstr "opis" msgid "Next url" msgstr "Następny url" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "użytkownik" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "aplikacja" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "Imię i Nazwisko" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "adres e-mail" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "komentarz" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -996,9 +998,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Źródło nie jest prawidłowym plikiem json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Projekt nie istnieje" @@ -1086,6 +1088,221 @@ msgstr "Element referencyjny nie istnieje" msgid "The status doesn't exist" msgstr "Status nie istnieje" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Zobacz projekt" @@ -1254,89 +1471,89 @@ msgstr "Administruj rolami" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "właściciel" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Pola niekompletne" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Niepoprawny format obrazka" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Nieprawidłowa nazwa szablonu" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Nieprawidłowy opis szablonu" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Nie masz uprawnień by to zobaczyć." @@ -1352,18 +1569,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "ID nie pasuje pomiędzy obiektem a projektem" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "projekt" @@ -1377,9 +1594,9 @@ msgstr "id obiektu" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1397,14 +1614,18 @@ msgstr "" msgid "is deprecated" msgstr "jest przestarzałe" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "kolejność" @@ -1440,19 +1661,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Tekst" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Teks wielowierszowy" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1486,54 +1759,59 @@ msgstr "zgłoszenie" msgid "Already exists one with the same name." msgstr "Już istnieje jeden z taką nazwą." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "status" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "temat" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "kolor" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "przypisane do" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "wymaganie klienta" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "wymaganie zespołu" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "źródło zgłoszenia" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1566,102 +1844,102 @@ msgstr "Utwórz" msgid "Delete" msgstr "Usuń" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s punkty roli" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "od" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "do" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Dodano nowy załącznik" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Zaktualizowany załącznik" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "przestarzałe" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "nie przestarzałe" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Usuń załącznik" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "dodane" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "usuniete" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Nieprzypisane" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-usunięte-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "do:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "od:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Dodane" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Zmienione" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Usunięte" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "dodane:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "usunięte:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Od:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Do:" @@ -1679,23 +1957,23 @@ msgstr "zaglokowana notatka" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Nie masz uprawnień do połączenia tego zgłoszenia ze sprintem." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Nie masz uprawnień do ustawienia statusu dla tego zgłoszenia." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Nie masz uprawnień do ustawienia ważności dla tego zgłoszenia." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "Nie masz uprawnień do ustawienia priorytetu dla tego zgłoszenia." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Nie masz uprawnień do ustawienia typu dla tego zgłoszenia." @@ -1716,11 +1994,6 @@ msgstr "kamień milowy" msgid "finished date" msgstr "data zakończenia" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "źródło zgłoszenia" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "" @@ -1729,33 +2002,33 @@ msgstr "" msgid "Likes" msgstr "" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "szacowana data rozpoczecia" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "szacowana data zakończenia" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "jest zamknięte" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "dostępność" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "Szacowana data rozpoczęcia musi być wcześniejsza niż data zakończenia." @@ -1767,6 +2040,14 @@ msgstr "" msgid "is blocked" msgstr "jest zablokowane" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1776,236 +2057,260 @@ msgstr "'{param}' parametr jest obowiązkowy" msgid "'project' parameter is mandatory" msgstr "'project' parametr jest obowiązkowy" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "e-mail" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "utwórz na" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "dodatkowy tekst w zaproszeniu" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "kolejność użytkowników" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Użytkownik już jest członkiem tego projektu" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "domyślny status dla HU" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "domyślne punkty" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "domyślny status dla zadania" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "domyślny priorytet" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "domyślna ważność" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "domyślny status dla zgłoszenia" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "domyślny typ dla zgłoszenia" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "członkowie" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "wszystkich kamieni milowych" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "wszystkich punktów " -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "aktywny panel backlog" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "aktywny panel Kanban" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "aktywny panel Wiki" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "aktywny panel zgłoszeń " -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "system wideokonferencji" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "dodatkowe dane dla wideokonferencji" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "szablon " -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "jest prywatna" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "uprawnienia anonimowych" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "uprawnienia użytkownika" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "data aktualizacji" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "ilość" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "konfiguracja modułów" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "zarchiwizowane" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "limit postępu prac" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "wartość" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "domyśla rola właściciela" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "domyślne opcje" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "statusy HU" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "pinkty" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "statusy zadań" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "statusy zgłoszeń" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "typy zgłoszeń" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "priorytety" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "ważność" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "role" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "" @@ -2040,7 +2345,7 @@ msgstr "Obserwowane" msgid "Notify exists for specified user and project" msgstr "Powiadomienie istnieje dla określonego użytkownika i projektu" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Nieprawidłowa wartość dla poziomu notyfikacji" @@ -2894,36 +3199,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2938,8 +3247,8 @@ msgstr "Zakończenie projektu" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Nieprawidłowy token." @@ -2989,16 +3298,16 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Nie masz uprawnień do ustawiania sprintu dla tego zadania." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" "Nie masz uprawnień do ustawiania historyjki użytkownika dla tego zadania" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Nie masz uprawnień do ustawiania statusu dla tego zadania" @@ -3014,31 +3323,31 @@ msgstr "Kolejność tablicy zadań" msgid "is iocaine" msgstr "Iokaina" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3665,39 +3974,31 @@ msgstr "Właściciel produktu" msgid "Stakeholder" msgstr "Interesariusz" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Nie masz uprawnień do ustawiania sprintu dla tej historyjki użytkownika." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "Nie masz uprawnień do ustawiania statusu do tej historyjki użytkownika." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rola" @@ -3726,87 +4027,91 @@ msgstr "wygenerowane ze zgłoszenia" msgid "There's no user story with that id" msgstr "Nie ma historyjki użytkownika z takim ID" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Nie ma projektu z takim ID" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Tena adres e-mail jest już w użyciu" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Nieprawidłowa rola w projekcie" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Domyślne opcje" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Statusy historyjek użytkownika" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Punkty" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Statusy zadań" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Statusy zgłoszeń" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Typu zgłoszeń" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Priorytety" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Ważność" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Role" @@ -3835,7 +4140,7 @@ msgstr "ostatnio zmodyfikowane przez" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Dla pełengo diffa sprawdź API historii" @@ -3875,56 +4180,56 @@ msgstr "" msgid "Important dates" msgstr "Ważne daty" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Zduplikowany adres e-mail" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Niepoprawny adres e-mail" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Nieprawidłowa nazwa użytkownika lub adrs e-mail" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "E-mail wysłany poprawnie!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Należy podać bieżące hasło" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Należy podać nowe hasło" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "" "Nieprawidłowa długość hasła - wymagane jest co najmniej 6 znaków" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Podałeś nieprawidłowe bieżące hasło" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Niepoprawne, jesteś pewien, że token jest poprawny i nie używałeś go " "wcześniej? " -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Niepoprawne, jesteś pewien, że token jest poprawny?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "status SUPERUSER" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -3932,24 +4237,24 @@ msgstr "" "Oznacza, że ten użytkownik posiada wszystkie uprawnienia bez konieczności " "ich przydzielania." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "nazwa użytkownika" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Wymagane. 30 znaków. Liter, cyfr i znaków /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Wprowadź poprawną nazwę użytkownika" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "aktywny" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3957,59 +4262,59 @@ msgstr "" "Oznacza, że ten użytkownik ma być traktowany jako aktywny. Możesz to " "odznaczyć zamiast usuwać konto." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografia" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "zdjęcie" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "data dołączenia" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "domyślny język Taiga" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "domyślny szablon Taiga" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "domyśla strefa czasowa" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "kolory tagów" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "tokem e-mail" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "nowy adres e-mail" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "uprawnienia" diff --git a/taiga/locale/pt_BR/LC_MESSAGES/django.po b/taiga/locale/pt_BR/LC_MESSAGES/django.po index 6014decf..2b7b2c5e 100644 --- a/taiga/locale/pt_BR/LC_MESSAGES/django.po +++ b/taiga/locale/pt_BR/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -13,6 +13,7 @@ # Lennon Jesus , 2016 # Mairieli Wessel , 2016 # Marlon Carvalho , 2015 +# Michel Wilhelm , 2016 # pedromvm , 2015 # Renato Prado , 2015 # Thiago Almeida , 2016 @@ -22,8 +23,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+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" @@ -33,15 +34,15 @@ msgstr "" "Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Registro público está desabilitado. " -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "tipo de registro inválido" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "tipo de login inválido" @@ -61,17 +62,17 @@ msgstr "Esse token não bate com nenhum convite." msgid "User is already registered." msgstr "Este usuário já está registrado." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "O usuário já é membro do projeto." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Erro ao criar um novo usuário." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Token inválido" @@ -92,97 +93,97 @@ msgstr "Este campo é obrigatório." msgid "Invalid value." msgstr "Valor inválido." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "O valor de '%s' deve ser ou True ou False." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Entre uma 'slug' válida, consistindo de letras, números, underscores ou " "hífens." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Escolha uma alternativa válida. %(value)s não está disponível." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Preencha com um e-mail válido." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "A data está no formato errado. Use um desses no lugar: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "Formato da data e hora errado. Use um destes: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Hora com formato errado. Use um destes: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Insira um número inteiro." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Garanta que o valor é menor ou igual a %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Garanta que o valor é maior ou igual a %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "O valor de \"%s\" deve ser decimal (float)." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Insira um número." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Garanta que não há mais que %s dígitos no total." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Garanta que não há mais que %s casas decimais." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Garanta que não há mais que %s dígitos antes do ponto decimal." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Nenhum arquivo enviado. Verifique o tipo de codificação no formulário." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Nenhum arquivo enviado." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "O arquivo enviado está vazio." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -190,11 +191,11 @@ msgstr "" "Garanta que o nome do arquivo tem no máximo %(max)d caracteres (no momento " "tem %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Envie um arquivo ou marque o checkbox \"vazio\", não ambos." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -203,25 +204,25 @@ msgstr "" "está corrompido." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Elemento bloqeado" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Página não é \"última\", nem pode ser convertída para um inteiro." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Página inválida (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Definição de permissão inválida." @@ -287,7 +288,7 @@ msgstr "Não encontrado" msgid "Permission denied" msgstr "Permissão negada" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Erro no servidor da aplicação" @@ -366,11 +367,11 @@ msgstr "Erro de pré-condição" msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Erro nos tipos de parâmetros do filtro." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'projeto' deve ser um valor inteiro." @@ -379,43 +380,43 @@ msgstr "'projeto' deve ser um valor inteiro." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Siga-nos no Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Pegue o código no GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Visite o nosso website" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -755,6 +756,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -780,6 +782,7 @@ msgstr "" " " #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -811,6 +814,7 @@ msgstr "" "O Time Taiga\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -883,7 +887,7 @@ msgid "It contain invalid custom fields." msgstr "Contém campos personalizados inválidos" #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Nome duplicado para o projeto" @@ -894,13 +898,13 @@ msgstr "Autenticação necessária" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "Nome" @@ -912,12 +916,12 @@ msgstr "Ícone da url" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "descrição" @@ -926,36 +930,34 @@ msgstr "descrição" msgid "Next url" msgstr "Próxima url" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "chave secreta para cifrar os tokens da aplicação" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "usuário" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "aplicação" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "nome completo" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "endereço de e-mail" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "comentário" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -1027,9 +1029,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "A carga não é um json válido" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "O projeto não existe" @@ -1076,7 +1078,7 @@ msgstr "Informação de problema inválida" #: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 msgid "unknown user" -msgstr "" +msgstr "usuário desconhecido" #: taiga/hooks/event_hooks.py:156 #, python-brace-format @@ -1117,6 +1119,221 @@ msgstr "O elemento referenciado não existe" msgid "The status doesn't exist" msgstr "O estatus não existe" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Ver projeto" @@ -1207,7 +1424,7 @@ msgstr "Modificar tarefa" #: taiga/permissions/choices.py:56 msgid "Comment task" -msgstr "" +msgstr "Comentar tarefa" #: taiga/permissions/choices.py:57 msgid "Delete task" @@ -1283,85 +1500,85 @@ msgstr "Funções Admin" #: taiga/projects/admin.py:100 msgid "Privacity" -msgstr "" +msgstr "Privacidade" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" -msgstr "" +msgstr "Módulos" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" -msgstr "" +msgstr "Valores padrão" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" -msgstr "" +msgstr "Atividades" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" -msgstr "" +msgstr "Fãs" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "dono" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Argumentos incompletos" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Formato de imagem inválida" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Nome de template inválido" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Descrição de template inválida" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "Id de usuário inválido" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "O usuário não existe" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "O usuário deve ser um membro do projeto" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" @@ -1369,7 +1586,7 @@ msgstr "" "O projeto deve ter um dono e pelo menos um dos usuários precisa ser um " "administrador ativo" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Você não tem permissão para ver isso" @@ -1385,18 +1602,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "ID do projeto não combina entre objeto e projeto" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "projeto" @@ -1410,9 +1627,9 @@ msgstr "identidade de objeto" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1430,14 +1647,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "está obsoleto" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "ordem" @@ -1473,19 +1694,71 @@ msgstr "Este projeto está bloqueado porque o proprietário deixou o projeto" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Texto" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Multi-linha" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Data" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1519,54 +1792,59 @@ msgstr "problema" msgid "Already exists one with the same name." msgstr "Já existe um com o mesmo nome." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "status" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "assunto" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "cor" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "assinado a" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "É requerimento do cliente" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "É requerimento do time" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "referência externa" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1599,102 +1877,102 @@ msgstr "Criar" msgid "Delete" msgstr "Apagar" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s pontos de função" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "de" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "para" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Adicionar novos anexos" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Atualizar anexo" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "obsoleto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "não-obsoleto" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Anexo apagado" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "adicionado" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "removido" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Não-atribuído" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-apagado-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "para:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "de:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Adicionado" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Alterado" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Apagado" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "acrescentado:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "removido:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "De:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Para:" @@ -1712,24 +1990,24 @@ msgstr "nota bloqueada" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Você não tem permissão para colocar essa sprint para esse problema." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Você não tem permissão para colocar esse status para esse problema." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Você não tem permissão para colocar essa gravidade para esse problema." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "" "Você não tem permissão para colocar essa prioridade para esse problema." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Você não tem permissão para colocar esse tipo para esse problema." @@ -1750,11 +2028,6 @@ msgstr "marco de progresso" msgid "finished date" msgstr "data de término" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "referência externa" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Curtir" @@ -1763,33 +2036,33 @@ msgstr "Curtir" msgid "Likes" msgstr "Curtidas" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slug" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "data de início estimada" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "data de encerramento estimada" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "está fechado" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibilidade" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "A estimativa de inicio deve ser anterior a estimativa de encerramento" @@ -1801,6 +2074,14 @@ msgstr "" msgid "is blocked" msgstr "está bloqueado" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1810,236 +2091,260 @@ msgstr "'{param}' parametro é mandatório" msgid "'project' parameter is mandatory" msgstr "'project' parametro é mandatório" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "email" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "criado em" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "token" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "texto extra de convite" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "ordem de usuário" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "O usuário já é membro do projeto" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "status de US padrão" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "pontos padrão" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "status padrão de tarefa" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "prioridade padrão" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "severidade padrão" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "status padrão de problema" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "tipo padrão de problema" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "logotipo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "membros" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "total de marcos de progresso" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "pontos totais de US" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "painel de backlog ativo" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "painel de kanban ativo" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "painel de wiki ativo" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "painel de problemas ativo" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "sistema de vídeo conferência" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "informação extra de vídeo conferência" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "template de criação" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "é privado" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "permissão anônima" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "permissão de usuário" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "é destaque" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "está procurando colaboradores" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "data de atualização" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "contagem" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "atividades da última semana" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "atividades do último mês" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "atividades do último ano" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "configurações de módulos" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "está arquivado" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "trabalho no limite de progresso" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "valor" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "função padrão para dono " -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "opções padrão" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "status de US" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "pontos" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "status de tarefa" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "status de problemas" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "tipos de problema" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "prioridades" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "severidades" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "funções" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Envolvido" @@ -2074,7 +2379,7 @@ msgstr "Observado" msgid "Notify exists for specified user and project" msgstr "Existe notificação para usuário e projeto especifcado" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Valor inválido para nível de notificação" @@ -2910,37 +3215,41 @@ msgstr "" "Você não pode deixar o projeto se você é o dono é não há outros " "administradores" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "Você atingiu o seu limite atual de membros para projetos privados" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "Você atingiu o seu limite atual de membros para projetos públicos" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "Você não pode ter mais projetos privados" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" "Este projeto atingiu o seu limite atual de membros para projetos privados" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "Você não pode ter mais projetos públicos" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2956,8 +3265,8 @@ msgstr "Fim do projeto" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Token é inválido" @@ -3007,17 +3316,17 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Você não tem permissão para colocar esse sprint para essa tarefa." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" "Você não tem permissão para colocar essa história de usuário para essa " "tarefa." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Você não tem permissão para colocar esse status para essa tarefa." @@ -3033,31 +3342,31 @@ msgstr "ordenar por quadro de tarefa" msgid "is iocaine" msgstr "é Iocaine" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3695,41 +4004,33 @@ msgstr "Product Owner" msgid "Stakeholder" msgstr "Stakeholder" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Você não tem permissão para colocar esse sprint para essa história de " "usuário." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "Você não tem permissão para colocar esse status para essa história de " "usuário." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Gerando a história de usuário #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "função" @@ -3758,88 +4059,92 @@ msgstr "Gerado do problema" msgid "There's no user story with that id" msgstr "Não há história de usuário com esse id" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Não há projeto com esse id" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Endereço de e-mail já utilizado" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Função inválida para projeto" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "O dono do projeto deve ser um administrador." -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" "Pelo menos one dos usuários deve ser um administrador ativo neste projeto." -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Opções padrão" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Status de história de usuário" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Pontos" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Status de tarefas" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Status de problemas" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Tipos de problemas" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioridades" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Severidades" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Funções" @@ -3868,7 +4173,7 @@ msgstr "último modificador" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Verifique o histórico da API para a exata diferença" @@ -3886,11 +4191,11 @@ msgstr "id" #: taiga/users/admin.py:81 msgid "Project Ownership" -msgstr "" +msgstr "Proprietário do projeto" #: taiga/users/admin.py:82 msgid "Project Ownerships" -msgstr "" +msgstr "Proprietários do projeto" #: taiga/users/admin.py:119 msgid "Personal info" @@ -3908,54 +4213,54 @@ msgstr "Restrições" msgid "Important dates" msgstr "Datas importantes" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "E-mail duplicado" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Não é um e-mail válido" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Usuário ou e-mail inválido" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "E-mail enviado com sucesso" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Parâmetro de senha atual necessário" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Parâmetro de nova senha necessário" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Comprimento de senha inválido, pelo menos 6 caracteres necessários" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Senha atual inválida" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Inválido, você está certo que o token está correto e não foi usado " "anteriormente?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Inválido, tem certeza que o token está correto?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "status de superuser" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -3963,24 +4268,24 @@ msgstr "" "Designa que esse usuário tem todas as permissões sem explicitamente assiná-" "las" -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "usuário" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Requerido. 30 caracteres ou menos. Letras, números e caracteres /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Digite um usuário válido" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "ativo" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3988,59 +4293,59 @@ msgstr "" "Designa quando esse usuário deve ser tratado como ativo. desmarque isso em " "vez de deletar contas." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografia" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "foto" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "data ingressado" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "lingua padrão" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "tema padrão" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "fuso horário padrão" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "tags coloridas" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "token de e-mail" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "novo endereço de email" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "permissões" diff --git a/taiga/locale/ru/LC_MESSAGES/django.po b/taiga/locale/ru/LC_MESSAGES/django.po index e4d316a6..2049794e 100644 --- a/taiga/locale/ru/LC_MESSAGES/django.po +++ b/taiga/locale/ru/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -10,14 +10,16 @@ # Egor Poderyagin , 2016 # Igor Bezukladnikov , 2016 # ilyar, 2016 -# ivan tkachenko , 2016 +# ratijas , 2016 +# Sergey Kustov , 2016 # Марат , 2015 +# Никита Евстропов , 2016 msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:55+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Russian (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/ru/)\n" @@ -29,15 +31,15 @@ msgstr "" "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%100>=11 && n%100<=14)? 2 : 3);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Публичная регистрация отключена." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "неправильный тип регистрации" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "неправильный тип логина" @@ -57,17 +59,17 @@ msgstr "Токен не подходит ни под одно корректно msgid "User is already registered." msgstr "Пользователь уже зарегистрирован." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Этот пользователь уже является участником данного проекта" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Ошибка при создании нового пользователя." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Неверный токен" @@ -88,102 +90,102 @@ msgstr "Это поле обязательно." msgid "Invalid value." msgstr "Неправильное значение." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "значение '%s' должно быть True - верно - или False - ложно." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Введите корректное 'ссылочное имя' состоящее из букв, чисел, подчёркиваний и " "дефисов." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Выберите правильное значение. %(value)s не является одним из доступных " "значений." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Введите правильный адрес email." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "Дата имеет неверный формат. Воспользуйтесь одним из этих форматов: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "" "Дата и время имеют неправильный формат. Воспользуйтесь одним из этих " "форматов: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "" "Время имеет неправильный формат. Воспользуйтесь одним из этих форматов: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Введите целое число." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Убедитесь, что это значение меньше или равно %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Убедитесь, что это значение больше или равно %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" значение должно быть числом с плавающей точкой." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Введите число." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Убедитесь, что здесь всего не больше %s цифр." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Убедитесь, что здесь не больше %s цифр после точкой." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Убедитесь, что здесь не больше %s цифр перед точкой." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Файл не был отправлен. Проверьте тип кодировки на форме." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Файл не был отправлен." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Отправленный файл пуст." -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -191,11 +193,11 @@ msgstr "" "Убедитесь, что имя этого файла имеет не больше %(max)d букв (сейчас - " "%(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "Пожалуйста, или отправьте файл, или снимите флажок." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -204,25 +206,25 @@ msgstr "" "изображение, либо не корректное изображение." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Заблокированный элемент" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Страница не является 'последней' и не может быть приведена к int." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Неправильная страница (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Неправильное определение разрешения" @@ -287,7 +289,7 @@ msgstr "Не найдено" msgid "Permission denied" msgstr "Доступ запрещён" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Ошибка приложения на сервере" @@ -366,11 +368,11 @@ msgstr "Ошибка предусловия" msgid "No room left for more projects." msgstr "Не осталось места для проектов" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Ошибка в типах фильтров для параметров." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' должно быть целым значением." @@ -379,43 +381,43 @@ msgstr "'project' должно быть целым значением." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Следите за нами в Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Скачайте код на GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Посетите наш вебсайт" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -549,7 +551,7 @@ msgstr "ошибка импорта историй от пользователе #: taiga/export_import/services/store.py:790 msgid "error importing epics" -msgstr "" +msgstr "ошибка импорта эпосов" #: taiga/export_import/services/store.py:794 msgid "error importing tasks" @@ -747,6 +749,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -771,6 +774,7 @@ msgstr "" "

Команда Taiga

" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -802,6 +806,7 @@ msgstr "" "Команда Taiga\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -874,7 +879,7 @@ msgid "It contain invalid custom fields." msgstr "Содержит неверные специальные поля" #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Уже есть такое имя для проекта" @@ -885,13 +890,13 @@ msgstr "Необходима аутентификация" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "имя" @@ -903,12 +908,12 @@ msgstr "url иконки" msgid "web" msgstr "веб" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "описание" @@ -917,36 +922,34 @@ msgstr "описание" msgid "Next url" msgstr "Следующий url" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "секретный ключ для шифрования токенов приложения" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "пользователь" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "приложение" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "полное имя" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "адрес email" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "комментарий" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -1018,9 +1021,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Нагрузочный файл не является правильным json-файлом" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Проект не существует" @@ -1044,6 +1047,9 @@ msgid "" "\n" "> {comment_message}" msgstr "" +"Комментарий из {platform}:\n" +"\n" +"> {comment_message}" #: taiga/hooks/event_hooks.py:84 msgid "Invalid issue comment information" @@ -1059,7 +1065,7 @@ msgstr "" #: taiga/hooks/event_hooks.py:107 #, python-brace-format msgid "Issue created from {platform}." -msgstr "" +msgstr "Запрос создан из {platform}." #: taiga/hooks/event_hooks.py:120 msgid "Invalid issue information" @@ -1067,7 +1073,7 @@ msgstr "Неверная информация о запросе" #: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 msgid "unknown user" -msgstr "" +msgstr "неизвестный пользователь" #: taiga/hooks/event_hooks.py:156 #, python-brace-format @@ -1108,6 +1114,221 @@ msgstr "Указанный элемент не существует" msgid "The status doesn't exist" msgstr "Статус не существует" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Просмотреть проект" @@ -1118,7 +1339,7 @@ msgstr "Просмотреть вехи" #: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 msgid "View epic" -msgstr "" +msgstr "Посмотреть эпосы" #: taiga/permissions/choices.py:26 msgid "View user stories" @@ -1154,19 +1375,19 @@ msgstr "Удалить веху" #: taiga/permissions/choices.py:42 msgid "Add epic" -msgstr "" +msgstr "Добавить эпос" #: taiga/permissions/choices.py:43 msgid "Modify epic" -msgstr "" +msgstr "Изменить эпос" #: taiga/permissions/choices.py:44 msgid "Comment epic" -msgstr "" +msgstr "Комментировать эпос" #: taiga/permissions/choices.py:45 msgid "Delete epic" -msgstr "" +msgstr "Удалить эпос" #: taiga/permissions/choices.py:47 msgid "View user story" @@ -1182,7 +1403,7 @@ msgstr "Изменить пользовательскую историю" #: taiga/permissions/choices.py:50 msgid "Comment user story" -msgstr "" +msgstr "Комментировать пользовательскую историю" #: taiga/permissions/choices.py:51 msgid "Delete user story" @@ -1198,7 +1419,7 @@ msgstr "Изменить задачу" #: taiga/permissions/choices.py:56 msgid "Comment task" -msgstr "" +msgstr "Комментировать задачу" #: taiga/permissions/choices.py:57 msgid "Delete task" @@ -1214,7 +1435,7 @@ msgstr "Изменить запрос" #: taiga/permissions/choices.py:62 msgid "Comment issue" -msgstr "" +msgstr "Комментировать запрос" #: taiga/permissions/choices.py:63 msgid "Delete issue" @@ -1230,7 +1451,7 @@ msgstr "Изменить wiki-страницу" #: taiga/permissions/choices.py:68 msgid "Comment wiki page" -msgstr "" +msgstr "Комментировать wiki-страницу" #: taiga/permissions/choices.py:69 msgid "Delete wiki page" @@ -1274,85 +1495,85 @@ msgstr "Управлять ролями" #: taiga/projects/admin.py:100 msgid "Privacity" -msgstr "" +msgstr "Приватность" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" -msgstr "" +msgstr "Модули" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" -msgstr "" +msgstr "Значения по-умолчанию" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" -msgstr "" +msgstr "Активность" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "владелец" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." -msgstr "" +msgstr "{count} успешно переведено в статус публичных." -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" -msgstr "" +msgstr "Сделать публичным" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." -msgstr "" +msgstr "{count} успешно переведено в статус приватных." -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" -msgstr "" +msgstr "Сделать приватным" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" -msgstr "" +msgstr "Удалить выбранные %(verbose_name_plural)s" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Список аргументов неполон" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Неправильный формат изображения" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Неверное название шаблона" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Неверное описание шаблона" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "Неправильный id пользователя" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "Пользователь не существует" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "Пользователь должен быть участником проекта" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" @@ -1360,7 +1581,7 @@ msgstr "" "У проекта должен быть владелец и по крайней мере один пользователь должен " "быть активным администратором" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "У вас нет разрешения на просмотр." @@ -1376,18 +1597,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Идентификатор проекта не подходит к этому объекту" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "проект" @@ -1401,9 +1622,9 @@ msgstr "идентификатор объекта" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1421,14 +1642,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "устаревшее" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "порядок" @@ -1458,25 +1683,77 @@ msgstr "Проект заблокирован администраторами" #: taiga/projects/choices.py:37 msgid "This project is blocked because the owner left" -msgstr "Проект заблокирован, потому-что владелец ушёл" +msgstr "Проект заблокирован, потому что владелец ушёл" #: taiga/projects/choices.py:38 msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Текст" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Многострочный текст" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Дата" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1491,7 +1768,7 @@ msgstr "значения" #: taiga/projects/custom_attributes/models.py:105 msgid "epic" -msgstr "" +msgstr "эпос" #: taiga/projects/custom_attributes/models.py:121 #: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 @@ -1510,65 +1787,70 @@ msgstr "запрос" msgid "Already exists one with the same name." msgstr "Это имя уже используется." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." -msgstr "" +msgstr "У вас нет разрешения для установки статуса этому эпосу" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "Ссылка" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "cтатус" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" -msgstr "" +msgstr "порядок эпосов" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "тема" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "цвет" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "назначено" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "является требованием клиента" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "является требованием команды" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" -msgstr "" +msgstr "пользовательские истории" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "внешняя ссылка" #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" -msgstr "" +msgstr "Не существует эпоса с таким идентификатором" #: taiga/projects/history/api.py:93 msgid "comment is required" -msgstr "" +msgstr "необходим комментарий" #: taiga/projects/history/api.py:96 msgid "deleted comments can't be edited" -msgstr "" +msgstr "удаленные комментарии не могут быть отредактированы" #: taiga/projects/history/api.py:130 msgid "Comment already deleted" @@ -1590,102 +1872,102 @@ msgstr "Создать" msgid "Delete" msgstr "Удалить" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "очки для роли %(role)s" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "от" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "кому" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Добавлено новое вложение" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Вложение обновлено" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "устаревшее" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "не устаревшее" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Удалённое вложение" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "добавлено" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "удалено" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Не назначено" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-удалено-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "кому:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "от:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Добавлено" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Изменено" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Удалено" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "добавлено:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "удалено:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "От:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Кому:" @@ -1703,27 +1985,27 @@ msgstr "Заметка о блокировке" msgid "sprint" msgstr "спринт" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "" "У вас нет прав для того чтобы установить такой спринт для этого запроса" -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "" "У вас нет прав для того чтобы установить такой статус для этого запроса" -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "" "У вас нет прав для того чтобы установить такую важность для этого запроса" -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "" "У вас нет прав для того чтобы установить такой приоритет для этого запроса" -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "У вас нет прав для того чтобы установить такой тип для этого запроса" @@ -1744,11 +2026,6 @@ msgstr "веха" msgid "finished date" msgstr "дата завершения" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "внешняя ссылка" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Лайк" @@ -1757,33 +2034,33 @@ msgstr "Лайк" msgid "Likes" msgstr "Лайки" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "ссылочное имя" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "предполагаемая дата начала" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "предполагаемая дата завершения" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "закрыто" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "доступность" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "" "Предполагаемая дата начала должна предшествовать предполагаемой дате " @@ -1791,12 +2068,20 @@ msgstr "" #: taiga/projects/milestones/validators.py:33 msgid "There's no milestone with that id" -msgstr "" +msgstr "Не существует вехи с таким идентификатором" #: taiga/projects/mixins/blocked.py:31 msgid "is blocked" msgstr "заблокировано" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1806,236 +2091,260 @@ msgstr "параметр '{param}' является обязательным" msgid "'project' parameter is mandatory" msgstr "параметр 'project' является обязательным" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "электронная почта" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "создано" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "идентификатор" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "дополнительный текст к приглашению" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "порядок пользователей" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Этот пользователем уже является участником проекта" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" -msgstr "" +msgstr "удалить статус эпоса" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "статусы ПИ по умолчанию" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "очки по умолчанию" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "статус задачи по умолчанию" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "приоритет по умолчанию" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "важность по умолчанию" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "статус запроса по умолчанию" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "тип запроса по умолчанию" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "лготип" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "участники" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "общее количество вех" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "очки истории" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 -msgid "active epics panel" +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:174 taiga/projects/models.py:753 +msgid "active epics panel" +msgstr "активная панель эпосов" + +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "активная панель списка задач" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "активная панель kanban" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "активная wiki-панель" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "панель активных запросов" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "система видеоконференций" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "дополнительные данные системы видеоконференций" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "шаблон для создания" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "личное" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "права анонимов" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "права пользователя" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "особенность" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "ищут людей" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "ищем замечания людей" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "" -#: taiga/projects/models.py:218 +#: taiga/projects/models.py:222 msgid "project transfer token" msgstr "токен передачи проекта" -#: taiga/projects/models.py:222 +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "заблокированный код" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "дата и время обновления" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "количество" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "фанатов на прошлой недели " -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "фанатов в прошлом месяце" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "фанатов в прошлом году" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "активность за неделю" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "активность за месяц" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "активность за год" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "конфигурация модулей" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "архивировано" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "ограничение на активную работу" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "значение" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "роль владельца по умолчанию" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "параметры по умолчанию" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" -msgstr "" +msgstr "статусы эпоса" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "статусы ПИ" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "очки" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "статусы задач" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "статусы запросов" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "типы запросов" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "приоритеты" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "степени важности" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "роли" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Вовлеченные" @@ -2070,7 +2379,7 @@ msgstr "Просмотренные" msgid "Notify exists for specified user and project" msgstr "Уведомление существует для данных пользователя и проекта" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Неверное значение для уровня уведомлений" @@ -2147,6 +2456,10 @@ msgid "" "

The Taiga Team

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

Эпос удалён

Здравствуйте, %(user)s,
%(changer)s удалил эпос " +"для %(project)s

Эпос #%(ref)s %(subject)s

The Taiga " +"Team

" #: taiga/projects/notifications/templates/emails/epics/epic-delete-body-text.jinja:1 #, python-format @@ -2166,6 +2479,8 @@ msgid "" "\n" "[%(project)s] Deleted the epic #%(ref)s \"%(subject)s\"\n" msgstr "" +"\n" +"[%(project)s] Удален эпос #%(ref)s \"%(subject)s\"\n" #: taiga/projects/notifications/templates/emails/issues/issue-change-body-html.jinja:4 #, python-format @@ -2911,36 +3226,40 @@ msgid "" msgstr "" "Вы не можете покинуть проект, если вы владелец или нет других администраторов" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" -msgstr "" +msgstr "Проект без владельца" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "Вы достигли лимита участников для частного проекта" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "Вы достигли лимита участников для публичного проекта" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "Вы не можете иметь больше частных проектов" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "В этом частном проекте достигнут лимит участников" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "Вы не можете иметь больше публичных проектов" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "В этом публичном проекте достигнут лимит участников" @@ -2955,8 +3274,8 @@ msgstr "Окончание проекта" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Неверный токен" @@ -2968,6 +3287,8 @@ msgstr "Срок действия токена истёк" #, python-brace-format msgid "Invalid tag '{value}'. The color is not a valid HEX color or null." msgstr "" +"Неверный тэг '{value}'. Цвет должен быть в шестнадцатеричном формате или " +"отсутствовать." #: taiga/projects/tagging/fields.py:55 #, python-brace-format @@ -2975,11 +3296,13 @@ msgid "" "Invalid tag '{value}'. it must be the name or a pair '[\"name\", \"hex color/" "\" | null]'." msgstr "" +"Неверный тэг '{value}'. Это должно быть имя или пара '[\"name\", \"hex color/" +"\" | null]'." #: taiga/projects/tagging/fields.py:77 #, python-brace-format msgid "Invalid tag '{value}'. It must be the tag name." -msgstr "" +msgstr "Неверный тэг '{value}'. Это должно быть имя тэга." #: taiga/projects/tagging/models.py:27 msgid "tags" @@ -2992,30 +3315,30 @@ msgstr "цвета тэгов" #: taiga/projects/tagging/validators.py:47 #: taiga/projects/tagging/validators.py:74 msgid "This tag already exists." -msgstr "" +msgstr "Такой тэг уже существует." #: taiga/projects/tagging/validators.py:54 #: taiga/projects/tagging/validators.py:81 msgid "The color is not a valid HEX color." -msgstr "" +msgstr "Этот некорректный шестнадцатеричный формат для цвета" #: taiga/projects/tagging/validators.py:67 #: taiga/projects/tagging/validators.py:101 #: taiga/projects/tagging/validators.py:114 #: taiga/projects/tagging/validators.py:121 msgid "The tag doesn't exist." -msgstr "" +msgstr "Тэг не существует" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "У вас нет прав, чтобы назначить этот спринт для этой задачи." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "" "У вас нет прав, чтобы назначить эту историю от пользователя этой задаче." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "У вас нет прав, чтобы установить этот статус для этой задачи." @@ -3031,31 +3354,34 @@ msgstr "порядок панели задач" msgid "is iocaine" msgstr "- иокаин" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." -msgstr "" +msgstr "Неверный id вехи" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." -msgstr "" +msgstr "Неверный id статуса задачи" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." -msgstr "" +msgstr "Неверный id пользовательской истории" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" +"Неверный id статуса задачи. Статус должен принадлежать тому же проекту." -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" +"Неверный id пользовательской истории. Пользовательская история должна " +"принадлежать тому же проекту." -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." -msgstr "" +msgstr "Неверный id вехи. Веха должна принадлежать тому же проекту." -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3762,39 +4088,31 @@ msgstr "Владелец продукта" msgid "Stakeholder" msgstr "Заинтересованная сторона" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "У вас нет прав чтобы установить спринт для этой пользовательской истории." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "У вас нет прав чтобы установить статус для этой пользовательской истории." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" -msgstr "" +msgstr "Неверный id роли '{role_id}'" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Генерируется пользовательская история #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "роль" @@ -3809,7 +4127,7 @@ msgstr "порядок спринтов" #: taiga/projects/userstories/models.py:84 msgid "kanban order" -msgstr "" +msgstr "порядок kanban" #: taiga/projects/userstories/models.py:92 msgid "finish date" @@ -3823,89 +4141,95 @@ msgstr "создано из запроса" msgid "There's no user story with that id" msgstr "Не существует пользовательской истории с таким идентификатором" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" +"Неверный id статуса пользовательской истории. Статус должен принадлежать " +"тому же проекту." -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." -msgstr "" +msgstr "Неверный id вехи. Веха должна принадлежать тому же проекту." -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" -msgstr "" +msgstr "Это недопустимая веха для проекта" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" -msgstr "" +msgstr "Все пользовательские истории должны быть из того же проекта" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Не существует проекта с таким идентификатором" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "Этот почтовый адрес уже используется" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Неверная роль для этого проекта" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "Владелец проекта должен быть администратором" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" "По крайней мере один пользователь должен быть администратором для этого " "проекта" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." -msgstr "" +msgstr "Неверный id роли. Роль должна принадлежать тому же проекту." -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Параметры по умолчанию" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Статусу пользовательских историй" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Очки" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Статусы задачи" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Статусы запроса" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Типы запроса" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Приоритеты" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Степени важности" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Роли" @@ -3934,7 +4258,7 @@ msgstr "последний отредактировавший" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Свертесть с историей API для получения изменений" @@ -3974,133 +4298,133 @@ msgstr "Ограничения" msgid "Important dates" msgstr "Важные даты" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "Этот email уже используется" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Невалидный email" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Неверное имя пользователя или e-mail" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Письмо успешно отправлено!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Поле \"текущий пароль\" является обязательным" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Поле \"новый пароль\" является обязательным" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Неверная длина пароля, требуется как минимум 6 символов" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Неверно указан текущий пароль" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "Неверно, вы уверены что токен правильный и не использовался ранее?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Неверно, вы уверены что токен правильный?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "статус суперпользователя" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "Выбранный пользователь имеет все разрешения, ему не чего назначит." -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "имя пользователя" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "Обязательно. 30 символов или меньше. Буквы, числа и символы /./-/_" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Введите корректное имя пользователя." -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "активный" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "Выбранный пользователь активен. Отменить выбор для удаления аккаунта." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "биография" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "фотография" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "когда присоединился" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "язык по умолчанию" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "тема по умолчанию" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "временная зона по умолчанию" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "установить цвета для тэгов" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "email токен" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "новый email адрес" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "максимальное число частных проектов" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "максимальное число публичных проектов" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "максимальное число участников для каждого частного проекта" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "максимальное число участников для каждого публичного проекта" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "разрешения" diff --git a/taiga/locale/sv/LC_MESSAGES/django.po b/taiga/locale/sv/LC_MESSAGES/django.po index 09cbdb49..09ac5ea3 100644 --- a/taiga/locale/sv/LC_MESSAGES/django.po +++ b/taiga/locale/sv/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Swedish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/sv/)\n" @@ -19,15 +19,15 @@ msgstr "" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "Publikt register är avvaktiverad." -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "Felaktigt registertyp" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "Invalid inloggningstyp" @@ -47,17 +47,17 @@ msgstr "Förekomsten passar inte invitationen. " msgid "User is already registered." msgstr "Användaren finns redan." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Ett fel uppstod når användaren skapades. " #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Felaktig förekomst. " @@ -78,99 +78,99 @@ msgstr "Fältet är obligatoriskt." msgid "Invalid value." msgstr "Felaktigt värde. " -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' värdet måste vara sann eller falskt. " -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Skriv in ett giltigt 'slugg' som består av bokstäver, nummer, understreck " "och bindestreck." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Välj korrekt. %(value)s är inte ett giltigt val. " -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Skriv in en giltig e-postadress" -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "Felaktigt datumformat. Använd ett av dessa formaten istället: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "Tidsdatum har fel format. Bruk ett av dessa formaten istället: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Felaktigt tidsformat. Bruk ett av dessa formaten istället: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Skriv ett helt nummer." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Försäkra dig om att värdet är mindre eller lika med %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Försäkra dig om att värdet är större eller lika med %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" värde måste vara flyttal." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Skriv in ett nummer." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Försäkra dig om att det inge är mera än %s siffror i totalen. " -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Försäkra dig om att det inte är mera än %s decimaler." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "" "Försäkra dig om det inte är mera än %s siffror till vänster om " "decimalpunkten." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Inga filer skickades. Check kodningstypen på formularet. " -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Skickade ingen fil. " -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "Den insända filen är tom. " -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -178,12 +178,12 @@ msgstr "" "Försäkra dig om att filnamnet har som mest %(max)d tecken (det har " "%(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Vänligen lämna in en fil eller kontrollera kryssrutan för klar, inte båda." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -192,26 +192,26 @@ msgstr "" "eller en skadad bild." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "" "Sidan är inte \"sist\", och inte heller kan den omvandlas till ett heltal." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Felaktig sida (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Ogiltigt definition för behörighet." @@ -276,7 +276,7 @@ msgstr "Hittade inte" msgid "Permission denied" msgstr "Du har inte behöriget" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Serverprogramfel." @@ -355,11 +355,11 @@ msgstr "Förutsättningsfel" msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Fel i filterparametertyper." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'Projektet\" måste vara ett heltal." @@ -368,43 +368,43 @@ msgstr "'Projektet\" måste vara ett heltal." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Följ oss på Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "Få koden på GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Besök vår webbplats" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -665,6 +665,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -680,6 +681,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -698,6 +700,7 @@ msgid "" msgstr "" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -751,7 +754,7 @@ msgid "It contain invalid custom fields." msgstr "Innehåller felaktigt anpassad fält." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Namnet är upprepad för projektet" @@ -762,13 +765,13 @@ msgstr "Verifiering krävs" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "namn" @@ -780,12 +783,12 @@ msgstr "Ikonlänk" msgid "web" msgstr "Internet" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "beskrivning" @@ -794,36 +797,34 @@ msgstr "beskrivning" msgid "Next url" msgstr "Nästa länk" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "hemlig nyckel för kryptering av programtecken " - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "användare" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "program" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "hela namnet" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "e-postadress" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "kommentera" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -879,9 +880,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "Datasträngen är inte korrekt json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Projektet existerar inte" @@ -969,6 +970,221 @@ msgstr "Referenselementet existerar inte" msgid "The status doesn't exist" msgstr "Statusen existerar inte" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Visa projekt" @@ -1137,89 +1353,89 @@ msgstr "Administratorroller" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "ägare" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Felaktiga argument" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Felaktigt bildformat" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Inget giltigt mallnamn" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Inte giltigt mallbeskrivning" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Du har inte behörighet att se det. " @@ -1235,18 +1451,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Projekt-ID stämmer inte mellan objekt och projekt" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "projekt" @@ -1260,9 +1476,9 @@ msgstr "objekt-ID" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1280,14 +1496,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "undviks" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "sortera" @@ -1323,19 +1543,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Text" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Text med flera rader" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Datum" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1369,54 +1641,59 @@ msgstr "Ärende" msgid "Already exists one with the same name." msgstr "Existerar redan med samma namn. " -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "status" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "titel" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "färg" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "Tilldelad till" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "är ett beställarkrav" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "är ett krav från arbetsgruppen" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "extern referens" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1449,102 +1726,102 @@ msgstr "Skapa" msgid "Delete" msgstr "Ta bort" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s rollpoäng" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "från" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "till " -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Lagt till ny bilaga" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Uppdaterad bilaga" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "borttagen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "inte borttagen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Bilaga borttagen" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "lagt till" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "borttaget" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Ej tilldelad" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-raderad-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "till:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "från:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Lagt till" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Ändrad" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Raderad" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "lagt till:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "borttaget:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Från:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Till:" @@ -1562,23 +1839,23 @@ msgstr "blockerad notering" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Du har inte behörighet att sätta sprinten till det här ärendet." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Du har inte behörighet att sätta status till det här ärendet. " -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Du har inte behörighet att sätta allvarsgrad till det här ärendet. " -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "Du har inte behörighet att sätta prioriteten för det här ärendet. " -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Du har inte behörighet att lägga till typen till ärendet. " @@ -1599,11 +1876,6 @@ msgstr "milstolpe" msgid "finished date" msgstr "färdig datum" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "extern referens" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Gillar" @@ -1612,33 +1884,33 @@ msgstr "Gillar" msgid "Likes" msgstr "Gillar" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "slugg" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "Beräknad startdatum" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "Beräknad slutdato" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "är stängd" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponerar" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "Beräknad startdatum måste vara tidigare än beräknad slutdatum. " @@ -1650,6 +1922,14 @@ msgstr "" msgid "is blocked" msgstr "är blockerad" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1659,236 +1939,260 @@ msgstr "'{param}' parameter är obligatoriskt" msgid "'project' parameter is mandatory" msgstr "'project' parameter är obligatoriskt" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "e-post" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "skapa som" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "textsträng" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "Invitation - extra text" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "användarorder" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Användaren är redan medlem i projekt" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "standard US-poäng" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "standardpoäng" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "standard status för uppgift" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "standard prioritet" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "standard allvarsgrad" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "standard status för ärende" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "standard typ för ärende" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "medlemmar" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "totalt antal milstolpar" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "totalt antal historiepoäng" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "aktivt panel för inkorg" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "aktiv kanban-panel" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "aktiv wiki-panel" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "aktiv panel för ärenden" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "videokonferensssystem" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "videokonferens - extra data" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "mall skapas" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "är privat" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "anonyma rättigheter" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "användarbehörigheter" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "uppdaterad dato och tid" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "räkna" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "konfigurera moduler" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "är arkiverad" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "begränsad arbete pågår" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "värde" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "ägarens standardroll" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "standard val" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "US statuser" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "poäng" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "statuser för uppgifter" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "status för ärenden" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "ärendentyper" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "prioriteter" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "allvarsgrad" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "roller" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Involverad" @@ -1923,7 +2227,7 @@ msgstr "Visad" msgid "Notify exists for specified user and project" msgstr "Notifiering finns för användaren och projektet" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Felaktigt värde för notifieringen" @@ -2516,36 +2820,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2560,8 +2868,8 @@ msgstr "Projektslut" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Textsträngen är ogiltig" @@ -2611,15 +2919,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Du har inte behörighet åt att sätta sprinten till en uppgift" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "Du har inte behörighet att sätta använderhistorien till en uppgift." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Du har inte behörighet att sätta status till en uppgift. " @@ -2635,31 +2943,31 @@ msgstr "Sortera uppgiftstavlan" msgid "is iocaine" msgstr "är Iocaine" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3245,40 +3553,32 @@ msgstr "Produktägare" msgid "Stakeholder" msgstr "Intressent" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "" "Du har inte behörighet för att lägga sprinten till den här användarhistorien" -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "" "Du har inte behörighet till att sätta den här statusen till " "användarhistorien." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "Skapar användarhistorie #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "roll" @@ -3307,87 +3607,91 @@ msgstr "skapad från ärende" msgid "There's no user story with that id" msgstr "Det är inga användarhistoria med det ID-numret" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Det är inga projekt med det ID-numret" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "E-postadressen är redan använd" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Fel roll for projektet" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Standardval" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Status för användarhistorien" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Poäng" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Status för uppgifter" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Status för ärenden" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Ärendetyper" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Prioritet" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Allvarsgrad" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Roller" @@ -3416,7 +3720,7 @@ msgstr "senastste ändring" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "Kolla historie API för exakt skillnad" @@ -3456,54 +3760,54 @@ msgstr "" msgid "Important dates" msgstr "Viktiga datum" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "E-post-dublett" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Ingen giltig e-postadress" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Ogiltigt användarnamn eller e-postadress" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "E-posten skickades korrekt" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "Parameter för nuvarande lösenord krävs" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Parameter för nytt lösenord krävs" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Felaktig längd på lösenord. Minst 6 alfanumeriska tecken krävs." -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "Fel lösenord" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Fel. Är du säker på att strängen är korrekt och att du inte har använt det " "tidigare?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Fel, är du säker på att textsträngen är korrekt? " -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "status för administratorn" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -3511,25 +3815,25 @@ msgstr "" "Anger om användaren har alla behörigheter utan att uttryckligen tilldela " "dem. " -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "användarnamn" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" "Obligatoriskt. 30 eller färre alfanumeriska tecken, bokstäver och /./-/_ . " -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Skriv in ett giltigt användarnamn" -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "aktiv" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -3537,59 +3841,59 @@ msgstr "" "Anger om användaren ska betraktas som aktiv. Avmarkera detta i stället för " "att ta bort kontot." -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biografi" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "foto" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "blev medlem datum" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "standardspråk" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "standardtema" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "standard tidzon" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "farglägg taggar" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "e-poststräng" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "ny e-postadress" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "behörigheter" diff --git a/taiga/locale/tr/LC_MESSAGES/django.po b/taiga/locale/tr/LC_MESSAGES/django.po index d070f778..7fb9c720 100644 --- a/taiga/locale/tr/LC_MESSAGES/django.po +++ b/taiga/locale/tr/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Turkish (http://www.transifex.com/taiga-agile-llc/taiga-back/" "language/tr/)\n" @@ -21,15 +21,15 @@ msgstr "" "Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "" -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "geçersiz kayıt tipi" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "geçersiz giriş tipi" @@ -49,17 +49,17 @@ msgstr "Kupon geçerli hiç bir davetle uyuşmuyor." msgid "User is already registered." msgstr "Kullanıcı zaten kayıtlı." -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "Bu kullanızı halihazırda zaten projenin bir üyesi." -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "Yeni kullanıcı oluşturulurken hata meydana geldi." #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "Geçersiz kupon" @@ -81,103 +81,103 @@ msgstr "Bu alan zorunlu." msgid "Invalid value." msgstr "Geçersiz değer." -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "%s' değeri ya Doğru ya da Yanlış olmalıdır." -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Harfler, rakamlar, altçizgi ve kesme işaretinden oluşan geçerli bir 'satır' " "girin." -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Geçerli bir seçenek belirleyin. %(value)s değeri mevcut seçenekler arasında " "yok." -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "Geçerli bir e-posta adresi girin." -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "Tarih biçemi yanlış. Belirtilen biçemlerden birini kullanın: %s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "Tarih saat biçemi yanlış. Belirtilen biçemlerden birini kullanın: %s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "Zaman biçemi yanlış. Belirtilen biçemlerden birini kullanın: %s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "Bir tam sayı girin." -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" "Bu değerin %(limit_value)s değerine eşit ya da daha az olduğundan emin olun." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" "Bu değerin %(limit_value)s değerine eşit ya da daha fazla olduğundan emin " "olun." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" değeri kesirli bir sayı olmalıdır." -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "Bir sayın girin." -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Toplamda %s basamaktan fazla olmadığından emin olun." -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "%s ondalık değerinden fazla olmalıdığından emin olun." -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "" "Virgülden önceki rakamların %s basamaktan fazla olmadığından emin olun." -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "Dosya ibraz edilmedi. Formdan kodlama tipini kontrol edin." -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "Dosya ibraz edilmedi." -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "İbraz edilen dosya boş" -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -185,13 +185,13 @@ msgstr "" "Bu dosya adının en fazla %(max)d karakterden oluştuğundan (uzunluğunun " "%(length)d olduğundan) emin olun" -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" "Lütfen bir dosya ibraz edin ya da onay kutusunu seçmeyin, ikisini birden " "olmaz." -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -200,25 +200,25 @@ msgstr "" "resim dosyası değil." #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "Engellenmiş nesne" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "Sayfa 'last'(son) değil, tamsayıya da çevrilemiyor." -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "Geçersiz sayfa (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "Geçersiz izin tanımı." @@ -282,7 +282,7 @@ msgstr "Bulunamadı" msgid "Permission denied" msgstr "İzin verilmedi" -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "Sunucu uygulaması hatası" @@ -361,11 +361,11 @@ msgstr "Ön şart hatası" msgid "No room left for more projects." msgstr "Daha fazla proje için yer kalmadı." -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "Parametre tipleri filtresinde hata." -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "'project' değeri numerik olmalı." @@ -374,43 +374,43 @@ msgstr "'project' değeri numerik olmalı." msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "Twitter da bizi takip edin" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "Twitter" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "GitHub dan kodu elde edin" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "Web sayfamızı ziyaret edin" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -719,6 +719,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -743,6 +744,7 @@ msgstr "" "

The Taiga Team

" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -775,6 +777,7 @@ msgstr "" "Taiga Takımı\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -846,7 +849,7 @@ msgid "It contain invalid custom fields." msgstr "Geçersiz özel alanlar içeriyor." #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "Aynı isimde proje bulunmakta" @@ -857,13 +860,13 @@ msgstr "Kimlik doğrulama gerekli" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "isim" @@ -875,12 +878,12 @@ msgstr "İkon url" msgid "web" msgstr "web" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "tanı" @@ -889,36 +892,34 @@ msgstr "tanı" msgid "Next url" msgstr "Sonraki url" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "kullanıcı" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "uygulama" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "tam ad" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "e-posta adresi" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "yorum" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -988,9 +989,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "Proje mevcut değil." @@ -1078,6 +1079,221 @@ msgstr "Referans gösterilmiş varlık mevcut değil" msgid "The status doesn't exist" msgstr "Durum mevcut değil" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "Projeyi gör" @@ -1246,89 +1462,89 @@ msgstr "Yönetici rolleri" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "sahip" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "Eksik parametreq" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "Geçersiz resim biçemi" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "Geçersiz şablon adı" -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "Geçersiz şablon tanımı" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "Geçersiz kullanıcı id" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "Kullanıcı mevcut değil" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "Kullanıcı zaten proje üyesi durumunda" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "Görebilmek için yetkiniz yok." @@ -1344,18 +1560,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "Proje ve nesne arasında Proje ID uyuşmazlığı mevcut" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "proje" @@ -1369,9 +1585,9 @@ msgstr "nesne id" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1389,14 +1605,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "kaldırıldı" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "sıra" @@ -1432,19 +1652,71 @@ msgstr "Yetkili kalmadığı için proje bloklandı" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "Metin" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "Çoklu-satır metin" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "Tarih" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "Url" @@ -1478,54 +1750,59 @@ msgstr "talep" msgid "Already exists one with the same name." msgstr "Aynı isimler bir tane daha mevcut." -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "durum" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "konu" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "renk" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "atanmış" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "istemci gereksinimi" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "takım gereksinimi" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "dış referans" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1558,102 +1835,102 @@ msgstr "Oluştur" msgid "Delete" msgstr "Sil" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s role puanları" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "den" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "kime" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "Yeni ek ekle" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "Güncellenmiş ek" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "kaldırıldı" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "kaldırılmadı" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "Silinmiş ek" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "eklendi" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "silindi" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "Atanmamış" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-silinmiş-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "kime:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "kimden:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "Eklenmiş" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "Değiştirilmiş" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "Silinmiş" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "eklenmiş:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "silinmiş:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "Kimden:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "Kime:" @@ -1671,23 +1948,23 @@ msgstr "engellenmiş not" msgid "sprint" msgstr "sprint" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "Bu talep için bu sprinti ayarlamaya yetkiniz yok." -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "Bu talep için bu durumu ayarlamaya yetkiniz yok." -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "Bu talep için bu kritiklik derecesini ayarlamaya yetkiniz yok." -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "Bu talep için bu öncelik durumunu ayarlamaya yetkiniz yok." -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "Bu talep için bu tipi ayarlamaya yetkiniz yok." @@ -1708,11 +1985,6 @@ msgstr "aşama" msgid "finished date" msgstr "bitirme tarihi" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "dış referans" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "Beğen" @@ -1721,33 +1993,33 @@ msgstr "Beğen" msgid "Likes" msgstr "Beğeniler" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "satır" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "yaklaşık başlama tarihi" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "yaklaşık bitiş tarihi" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "kapatılmış" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "taşınabilirlik" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "Tahmini başlangıç, tahmini bitişten önce olmalı" @@ -1759,6 +2031,14 @@ msgstr "" msgid "is blocked" msgstr "engellenmiş" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1768,236 +2048,260 @@ msgstr "'{param}' parametresi zorunlu" msgid "'project' parameter is mandatory" msgstr "'proje' parametresi zorunlu" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "e-posta" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "kupon" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "Davetiye ekstra metni" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "kullanıcı sırası" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "Kullanıcı zaten projenin üyesi" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "varsayılan KH durumu" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "varsayılan puanlar" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "varsayılan görev durumu" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "varsayılan öncelik" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "varsayılan önem derecesi" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "varsayılan talep durumu" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "varsayılan talep tipi" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "logo" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "üyeler" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "aşamaların toplamı" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "toplam hikaye puanı" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "aktif birikmiş iler paneli" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "aktif kanban paneli" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "aktif wiki paneli" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "aktif talep paneli" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "video konferans sistemi" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "videokonferans ekstra verisi" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "oluşturma şablonu" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "gizli" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "anonim izinler" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "kullanıcı izinleri" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr "vitrinde" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "insan arıyor" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "engellenmiş kod" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "yükleme tarih-saati" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "sayı" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "geçen hafta fanları" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "geçen ayın fanları" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "geçen yılın fanları" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "geçen haftanın aktiviteleri" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "geçen ayın aktiviteleri" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "geçen yılın aktiviteleri" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "modül ayarları" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "arşivlenmiş" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "değer" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "varsayılan sahip rolü" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "varsayılan ayarlar" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "kh durumları" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "puanlar" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "görev durumları" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "talep durumları" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "talep tipleri" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "öncelikler" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "önem durumları" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "roller" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "Müdahil" @@ -2032,7 +2336,7 @@ msgstr "İzlenen" msgid "Notify exists for specified user and project" msgstr "Belirtilen kullanıcı ve proje için bilgilendirme mevcut" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "Bildirim düzeyi için geçersiz değer" @@ -2691,36 +2995,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2735,8 +3043,8 @@ msgstr "Proje Sonu" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "Kupon geçersiz" @@ -2786,15 +3094,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "Bu görev için sprint ayarlamanız için izniniz yok." -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "Bu görev için kullanıcı hikayesi ayarlama izniniz yok." -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "Bu görev için bu durumu ayarlama izniniz yok." @@ -2810,31 +3118,31 @@ msgstr "görev panosu sırası" msgid "is iocaine" msgstr "baldıran zehri" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3422,37 +3730,29 @@ msgstr "Ürün Sahibi" msgid "Stakeholder" msgstr "Paydaş" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "Bu kullanıcı hikayesine bu sprinti ayarlama izniniz yok." -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "Bu kullanıcı hikayesine bu durumu ayarlama yetkiniz yok." -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "rol" @@ -3481,87 +3781,91 @@ msgstr "talepden oluştur" msgid "There's no user story with that id" msgstr "Bu id ye sahip kullanıcı hikayesi yok" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "Bu id ye sahip proje yok" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "E-posta adresi önceden alınmış" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "Proje için geçersiz rol" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "Varsayılan ayarlar" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "Kullanıcı hikayelerinin durumları" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "Puanlar" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "Görevlerin durumları" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "Taleplerin durumları" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "Taleplerin tipleri" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "Öncelikler" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "Önem dereceleri" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "Roller" @@ -3590,7 +3894,7 @@ msgstr "son düzenleyen" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "" @@ -3630,136 +3934,136 @@ msgstr "" msgid "Important dates" msgstr "Önemli tarihler" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "Geçersiz e-posta" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "Geçersiz kullanıcı adı ya da e-posta" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "Posta başarıyla gönderildi!" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "Yeni parola parametresi gerekli" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "Geçersiz parola uzunluğu, en az 6 karaktere ihtiyaç var" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Geçersiz geçerli bir kupona sahip olduğunuzdan ve bu kuponu daha önce " "kullanmadığınızdan emin misiniz?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "Geçersiz, kuponun doğru olduğuna emin misin?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "superuser durumu" -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "" -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "kullanıcı adı" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "" "Zorunlu. 30 karakter ya da daha azı. Harfler, sayılar ve /./-/_ karakterleri" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "Geçerli bir kullanıcı adı girin." -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "aktif" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "biyografi" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "fotoğraf" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "katılma tarihi" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "varsayılan dil" -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "varsayılan tema" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "varsayılan saat dilimi" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "etiketleri renklendir" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "e-posta kuponu" -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "yeni e-posta adresi" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "izinler" diff --git a/taiga/locale/zh-Hans/LC_MESSAGES/django.po b/taiga/locale/zh-Hans/LC_MESSAGES/django.po new file mode 100644 index 00000000..0a8a11d2 --- /dev/null +++ b/taiga/locale/zh-Hans/LC_MESSAGES/django.po @@ -0,0 +1,4652 @@ +# taiga-back.taiga. +# Copyright (C) 2014-2017 Taiga Dev Team +# This file is distributed under the same license as the taiga-back package. +# +# Translators: +# gm l , 2016 +# Hanbing Yin , 2016 +# ifelse , 2015 +# Longyang Zhang , 2015 +# Qi Fan , 2016 +# waring id , 2016 +# wuwenbin , 2015 +# Yang Yu , 2016 +# yonee , 2015 +# 5791113 , 2016 +# 5791113 , 2016 +msgid "" +msgstr "" +"Project-Id-Version: taiga-back\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:55+0000\n" +"Last-Translator: Taiga Dev Team \n" +"Language-Team: Chinese Simplified (http://www.transifex.com/taiga-agile-llc/" +"taiga-back/language/zh-Hans/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh-Hans\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: taiga/auth/api.py:74 +msgid "Public register is disabled." +msgstr "注册功能未开放" + +#: taiga/auth/api.py:101 +msgid "invalid register type" +msgstr "无效的注册类型" + +#: taiga/auth/api.py:117 +msgid "invalid login type" +msgstr "无效的登录类型" + +#: taiga/auth/services.py:76 +msgid "Username is already in use." +msgstr "用户名已经被使用" + +#: taiga/auth/services.py:79 +msgid "Email is already in use." +msgstr "电子邮件已经被使用" + +#: taiga/auth/services.py:95 +msgid "Token not matches any valid invitation." +msgstr "令牌不合法" + +#: taiga/auth/services.py:123 +msgid "User is already registered." +msgstr "用户已注册" + +#: taiga/auth/services.py:140 +msgid "This user is already a member of the project." +msgstr "该用户已经是此项目成员。" + +#: taiga/auth/services.py:164 +msgid "Error on creating new user." +msgstr "无法创建新用户" + +#: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 +msgid "Invalid token" +msgstr "无效令牌" + +#: taiga/auth/validators.py:37 taiga/users/validators.py:44 +msgid "invalid username" +msgstr "无效用户名" + +#: taiga/auth/validators.py:42 taiga/users/validators.py:50 +msgid "" +"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'" +msgstr "必填,长度小于255的数字、英文字母、“-”、“_”" + +#: taiga/base/api/fields.py:294 +msgid "This field is required." +msgstr "此字段是必需的。" + +#: taiga/base/api/fields.py:295 taiga/base/api/relations.py:337 +msgid "Invalid value." +msgstr "无效值。" + +#: taiga/base/api/fields.py:484 +#, python-format +msgid "'%s' value must be either True or False." +msgstr "'%s' 值必须为True或False。" + +#: taiga/base/api/fields.py:549 +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "输入一个有效的字符,由字母、数字、下划线或横线组成" + +#: taiga/base/api/fields.py:564 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "请选择一个有效的选项。%(value)s 不是一个可用的选项。" + +#: taiga/base/api/fields.py:638 +msgid "You email domain is not allowed" +msgstr "不允许您的邮件域名" + +#: taiga/base/api/fields.py:647 +msgid "Enter a valid email address." +msgstr "请输入合法的邮件地址。" + +#: taiga/base/api/fields.py:689 +#, python-format +msgid "Date has wrong format. Use one of these formats instead: %s" +msgstr "日期格式错误。使用以下一种格式替代:%s" + +#: taiga/base/api/fields.py:753 +#, python-format +msgid "Datetime has wrong format. Use one of these formats instead: %s" +msgstr "日期格式错误。使用以下一种格式替代:%s" + +#: taiga/base/api/fields.py:823 +#, python-format +msgid "Time has wrong format. Use one of these formats instead: %s" +msgstr "时间格式错误。使用以下一种格式替代:%s" + +#: taiga/base/api/fields.py:880 +msgid "Enter a whole number." +msgstr "输入一个整数" + +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "确认此数值应小于或等于 %(limit_value)s" + +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "确认此数值应大于或等于 %(limit_value)s" + +#: taiga/base/api/fields.py:912 +#, python-format +msgid "\"%s\" value must be a float." +msgstr "'%s' 值必须为 浮点数。" + +#: taiga/base/api/fields.py:933 +msgid "Enter a number." +msgstr "输入一个数字" + +#: taiga/base/api/fields.py:936 +#, python-format +msgid "Ensure that there are no more than %s digits in total." +msgstr "确保总数不超过%s个数字" + +#: taiga/base/api/fields.py:937 +#, python-format +msgid "Ensure that there are no more than %s decimal places." +msgstr "确保小数不超过%s位" + +#: taiga/base/api/fields.py:938 +#, python-format +msgid "Ensure that there are no more than %s digits before the decimal point." +msgstr "确保小数点前不超过%s位数字" + +#: taiga/base/api/fields.py:1005 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "未提交任何文件。请检查表单的编码类型" + +#: taiga/base/api/fields.py:1006 +msgid "No file was submitted." +msgstr "没有文件提交" + +#: taiga/base/api/fields.py:1007 +msgid "The submitted file is empty." +msgstr "提交的文件是空的" + +#: taiga/base/api/fields.py:1008 +#, python-format +msgid "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr "确保文件名不超过 %(max)d 个字符 (当前 %(length)d 个)" + +#: taiga/base/api/fields.py:1009 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "请提交文件或选中清除复选框,二选一" + +#: taiga/base/api/fields.py:1049 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "请上传一张有效的图片。所上传的不是图片或已损坏" + +#: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 +msgid "Blocked element" +msgstr "冻结的元素" + +#: taiga/base/api/pagination.py:228 +msgid "Page is not 'last', nor can it be converted to an int." +msgstr "页面不是‘最后’一个, 也不能转换成整数。" + +#: taiga/base/api/pagination.py:232 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "无效页面 (%(page_number)s): %(message)s" + +#: taiga/base/api/permissions.py:65 +msgid "Invalid permission definition." +msgstr "无效的授权定义" + +#: taiga/base/api/relations.py:247 +#, python-format +msgid "Invalid pk '%s' - object does not exist." +msgstr "无效主键 '%s' - 对象不存在" + +#: taiga/base/api/relations.py:248 +#, python-format +msgid "Incorrect type. Expected pk value, received %s." +msgstr "错误的类型。期望收到主键, 但是收到了'%s'" + +#: taiga/base/api/relations.py:336 +#, python-format +msgid "Object with %s=%s does not exist." +msgstr "没有满足条件的结果: %s=%s" + +#: taiga/base/api/relations.py:372 +msgid "Invalid hyperlink - No URL match" +msgstr "无效的超链接 - 无匹配的URL地址" + +#: taiga/base/api/relations.py:373 +msgid "Invalid hyperlink - Incorrect URL match" +msgstr "无效的超链接 - 错误的URL匹配" + +#: taiga/base/api/relations.py:374 +msgid "Invalid hyperlink due to configuration error" +msgstr "无效的超链接 - 配置错误" + +#: taiga/base/api/relations.py:375 +msgid "Invalid hyperlink - object does not exist." +msgstr "无效的超链接 - 对象不存在" + +#: taiga/base/api/relations.py:376 +#, python-format +msgid "Incorrect type. Expected url string, received %s." +msgstr "错误的数据类型。期望收到URL, 但是收到了 %s" + +#: taiga/base/api/serializers.py:324 +msgid "Invalid data" +msgstr "无效的数据" + +#: taiga/base/api/serializers.py:416 +msgid "No input provided" +msgstr "没提供输入数据" + +#: taiga/base/api/serializers.py:579 +msgid "Cannot create a new item, only existing items may be updated." +msgstr "不能创建新项目,可能只是现有项目被修改。" + +#: taiga/base/api/serializers.py:590 +msgid "Expected a list of items." +msgstr "期望一个项目列表" + +#: taiga/base/api/views.py:126 +msgid "Not found" +msgstr "找不到" + +#: taiga/base/api/views.py:129 +msgid "Permission denied" +msgstr "无此权限" + +#: taiga/base/api/views.py:492 +msgid "Server application error" +msgstr "服务器端错误" + +#: taiga/base/connectors/exceptions.py:26 +msgid "Connection error." +msgstr "连接错误" + +#: taiga/base/exceptions.py:79 +msgid "Malformed request." +msgstr "错误请求" + +#: taiga/base/exceptions.py:84 +msgid "Incorrect authentication credentials." +msgstr "用户名密码验证错误" + +#: taiga/base/exceptions.py:89 +msgid "Authentication credentials were not provided." +msgstr "密码未提供" + +#: taiga/base/exceptions.py:94 +msgid "You do not have permission to perform this action." +msgstr "你未被授权做此操作" + +#: taiga/base/exceptions.py:99 +#, python-format +msgid "Method '%s' not allowed." +msgstr "不允许用此方法:'%s'" + +#: taiga/base/exceptions.py:107 +msgid "Could not satisfy the request's Accept header" +msgstr "请求的Header头不允许" + +#: taiga/base/exceptions.py:116 +#, python-format +msgid "Unsupported media type '%s' in request." +msgstr "媒体类型'%s'不支持" + +#: taiga/base/exceptions.py:124 +msgid "Request was throttled." +msgstr "请求被限制" + +#: taiga/base/exceptions.py:125 +#, python-format +msgid "Expected available in %d second%s." +msgstr "预期可在%d 秒%s." + +#: taiga/base/exceptions.py:139 +msgid "Unexpected error" +msgstr "异常错误" + +#: taiga/base/exceptions.py:151 +msgid "Not found." +msgstr "未找到" + +#: taiga/base/exceptions.py:156 +msgid "Method not supported for this endpoint." +msgstr "请求方法不支持" + +#: taiga/base/exceptions.py:164 taiga/base/exceptions.py:172 +msgid "Wrong arguments." +msgstr "错误参数" + +#: taiga/base/exceptions.py:176 +msgid "Data validation error" +msgstr "数据验证错误" + +#: taiga/base/exceptions.py:188 +msgid "Integrity Error for wrong or invalid arguments" +msgstr "参数缺失或非法" + +#: taiga/base/exceptions.py:195 +msgid "Precondition error" +msgstr "前提条件错误" + +#: taiga/base/exceptions.py:219 +msgid "No room left for more projects." +msgstr "没空间添加新项目了" + +#: taiga/base/filters.py:81 taiga/base/filters.py:463 +msgid "Error in filter params types." +msgstr "参数类型错误" + +#: taiga/base/filters.py:136 taiga/base/filters.py:243 +#: taiga/projects/filters.py:64 +msgid "'project' must be an integer value." +msgstr "'project'必须是一个整数值" + +#: taiga/base/templates/emails/base-body-html.jinja:6 +msgid "Taiga" +msgstr "Taiga" + +#: taiga/base/templates/emails/base-body-html.jinja:421 +#: taiga/base/templates/emails/hero-body-html.jinja:380 +#: taiga/base/templates/emails/updates-body-html.jinja:442 +msgid "Follow us on Twitter" +msgstr "在推特上关注我们" + +#: taiga/base/templates/emails/base-body-html.jinja:421 +#: taiga/base/templates/emails/hero-body-html.jinja:380 +#: taiga/base/templates/emails/updates-body-html.jinja:442 +msgid "Twitter" +msgstr "Twitter" + +#: taiga/base/templates/emails/base-body-html.jinja:422 +#: taiga/base/templates/emails/hero-body-html.jinja:381 +#: taiga/base/templates/emails/updates-body-html.jinja:443 +msgid "Get the code on GitHub" +msgstr "从Github上获取代码" + +#: taiga/base/templates/emails/base-body-html.jinja:422 +#: taiga/base/templates/emails/hero-body-html.jinja:381 +#: taiga/base/templates/emails/updates-body-html.jinja:443 +msgid "GitHub" +msgstr "Github" + +#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/hero-body-html.jinja:382 +#: taiga/base/templates/emails/updates-body-html.jinja:444 +msgid "Visit our website" +msgstr "访问我们的网站" + +#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/hero-body-html.jinja:382 +#: taiga/base/templates/emails/updates-body-html.jinja:444 +msgid "Taiga.io" +msgstr "Taiga.io" + +#: taiga/base/templates/emails/base-body-html.jinja:438 +#: taiga/base/templates/emails/hero-body-html.jinja:397 +#: taiga/base/templates/emails/updates-body-html.jinja:459 +#, python-format +msgid "" +"\n" +" Taiga Support:\n" +" %(support_url)s\n" +"
\n" +" Contact us:\n" +" \n" +" %(support_email)s\n" +" \n" +"
\n" +" Mailing list:\n" +" \n" +" %(mailing_list_url)s\n" +" \n" +" " +msgstr "" +"\n" +" 帮助:\n" +" %(support_url)s\n" +"
\n" +" 联系我们:\n" +" \n" +" %(support_email)s\n" +" \n" +"
\n" +" 邮件列表:\n" +" \n" +" %(mailing_list_url)s\n" +" \n" +" " + +#: taiga/base/templates/emails/hero-body-html.jinja:6 +msgid "You have been Taigatized" +msgstr "您已成为Taiga的一员" + +#: taiga/base/templates/emails/hero-body-html.jinja:359 +msgid "" +"\n" +"

You have been Taigatized!" +"

\n" +"

Welcome to Taiga, an Open " +"Source, Agile Project Management Tool

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

您已经成为Taiga的一员!\n" +"

欢迎您加入Taiga这个开源" +"的,敏捷项目管理工具大家庭

\n" +" " + +#: taiga/base/templates/emails/updates-body-html.jinja:6 +msgid "[Taiga] Updates" +msgstr "[Taiga] 更新" + +#: taiga/base/templates/emails/updates-body-html.jinja:417 +msgid "Updates" +msgstr "更新" + +#: taiga/base/templates/emails/updates-body-html.jinja:423 +#, python-format +msgid "" +"\n" +"

comment:" +"

\n" +"

" +"%(comment)s

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

评论:" +"

\n" +"

" +"%(comment)s

\n" +" " + +#: taiga/base/templates/emails/updates-body-text.jinja:6 +#, python-format +msgid "" +"\n" +" Comment: %(comment)s\n" +" " +msgstr "" +"\n" +" 评论: %(comment)s\n" +" " + +#: taiga/export_import/api.py:127 +msgid "We needed at least one role" +msgstr "我们至少需要1个角色" + +#: taiga/export_import/api.py:323 +msgid "Needed dump file" +msgstr "需要备份文件" + +#: taiga/export_import/api.py:333 +msgid "Invalid dump format" +msgstr "非法的备份文件格式" + +#: taiga/export_import/services/store.py:718 +#: taiga/export_import/services/store.py:736 +msgid "error importing project data" +msgstr "无法导入项目数据" + +#: taiga/export_import/services/store.py:743 +msgid "error importing roles" +msgstr "角色导入错误" + +#: taiga/export_import/services/store.py:748 +msgid "error importing memberships" +msgstr "成员关系导入错误" + +#: taiga/export_import/services/store.py:759 +msgid "error importing lists of project attributes" +msgstr "项目属性列表导入错误" + +#: taiga/export_import/services/store.py:763 +msgid "error importing default project attributes values" +msgstr "默认项目属性值导入错误" + +#: taiga/export_import/services/store.py:774 +msgid "error importing custom attributes" +msgstr "客户化属性导入错误" + +#: taiga/export_import/services/store.py:778 +msgid "error importing sprints" +msgstr "sprints导入错误" + +#: taiga/export_import/services/store.py:782 +msgid "error importing issues" +msgstr "问题导入错误" + +#: taiga/export_import/services/store.py:786 +msgid "error importing user stories" +msgstr "用户故事导入错误" + +#: taiga/export_import/services/store.py:790 +msgid "error importing epics" +msgstr "史诗导入错误" + +#: taiga/export_import/services/store.py:794 +msgid "error importing tasks" +msgstr "任务导入错误" + +#: taiga/export_import/services/store.py:798 +msgid "error importing wiki pages" +msgstr "维基页面导入错误" + +#: taiga/export_import/services/store.py:802 +msgid "error importing wiki links" +msgstr "维基链接导入错误" + +#: taiga/export_import/services/store.py:806 +msgid "error importing tags" +msgstr "标签导入错误" + +#: taiga/export_import/services/store.py:810 +msgid "error importing timelines" +msgstr "时间线导入错误" + +#: taiga/export_import/services/store.py:832 +msgid "unexpected error importing project" +msgstr "导入项目发生未知错误" + +#: taiga/export_import/tasks.py:62 taiga/export_import/tasks.py:63 +msgid "Error generating project dump" +msgstr "生成项目备份文件出错" + +#: taiga/export_import/tasks.py:91 +#, python-brace-format +msgid "" +"\n" +"\n" +"Error loading dump by {user_full_name} <{user_email}>:\"\n" +"\n" +"\n" +"REASON:\n" +"-------\n" +"{reason}\n" +"\n" +"DETAILS:\n" +"--------\n" +"{details}\n" +"\n" +"TRACE ERROR:\n" +"------------" +msgstr "" +"\n" +"\n" +"按名字读取备份错误:{user_full_name} <{user_email}>\n" +"\n" +"原因:\n" +"---------\n" +"{reason}\n" +"\n" +"细节:\n" +"---------\n" +"{details}\n" +"\n" +"错误跟踪:\n" +"---------" + +#: taiga/export_import/tasks.py:120 +msgid "Error loading project dump" +msgstr "读取项目备份错误" + +#: taiga/export_import/tasks.py:121 +msgid "Error loading your project dump file" +msgstr "读取你的项目备份文件错误" + +#: taiga/export_import/tasks.py:135 +msgid " -- no detail info --" +msgstr "-- 无详细信息 --" + +#: taiga/export_import/templates/emails/dump_project-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Project dump generated

\n" +"

Hello %(user)s,

\n" +"

Your dump from project %(project)s has been correctly generated.\n" +"

You can download it here:

\n" +" Download the dump file\n" +"

This file will be deleted on %(deletion_date)s.

\n" +"

The Taiga Team

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

项目备份

\n" +"

您好%(user)s,

\n" +"

%(project)s 的备份文件已经生成。

\n" +"

您可以从此处下载下载:

\n" +" 下" +"载备份文件\n" +"

%(deletion_date)s后备份文件将删除

\n" +"

Taiga团队

\n" +" " + +#: taiga/export_import/templates/emails/dump_project-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your dump from project %(project)s has been correctly generated. You can " +"download it here:\n" +"\n" +"%(url)s\n" +"\n" +"This file will be deleted on %(deletion_date)s.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"您好%(user)s,\n" +"\n" +"%(project)s的备份文件已经成功生成. 您可以从此处下载:\n" +"\n" +"%(url)s\n" +"\n" +"备份文件将在%(deletion_date)s后删除.\n" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/export_import/templates/emails/dump_project-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your project dump has been generated" +msgstr "[%(project)s] 您的项目备份文件已经生成" + +#: taiga/export_import/templates/emails/export_error-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

%(error_message)s

\n" +"

Hello %(user)s,

\n" +"

Your project %(project)s has not been exported correctly.

\n" +"

The Taiga system administrators have been informed.
Please, try " +"it again or contact with the support team at\n" +" %(support_email)s

\n" +"

The Taiga Team

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

%(error_message)s

\n" +"

您好%(user)s,

\n" +"

%(project)s项目导出失败

\n" +"

已经通知系统管理员.
请重试,或向Taiga支持团队寻求帮助\n" +" %(support_email)s

\n" +"

Taiga团队

\n" +" " + +#: taiga/export_import/templates/emails/export_error-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"%(error_message)s\n" +"Your project %(project)s has not been exported correctly.\n" +"\n" +"The Taiga system administrators have been informed.\n" +"\n" +"Please, try it again or contact with the support team at %(support_email)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"您好%(user)s,\n" +"\n" +"%(error_message)s\n" +"%(project)s项目导出失败.\n" +"\n" +"已经通知系统管理员.\n" +"\n" +"请重试或联系Taiga支持团队%(support_email)s\n" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/export_import/templates/emails/export_error-subject.jinja:1 +#, python-format +msgid "[%(project)s] %(error_subject)s" +msgstr "[%(project)s] %(error_subject)s" + +#: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

%(error_message)s

\n" +"

Hello %(user)s,

\n" +"

Your project has not been importer correctly.

\n" +"

The Taiga system administrators have been informed.
Please, try " +"it again or contact with the support team at\n" +" %(support_email)s

\n" +"

The Taiga Team

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

%(error_message)s

\n" +"

您好%(user)s,

\n" +"

您的项目导出失败。

\n" +"

已经通知系统管理员
请重试,或联系支持团队\n" +" %(support_email)s

\n" +"

Taiga团队

\n" +" " + +#: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"%(error_message)s\n" +"\n" +"Your project has not been importer correctly.\n" +"\n" +"The Taiga system administrators have been informed.\n" +"\n" +"Please, try it again or contact with the support team at %(support_email)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"您好%(user)s,\n" +"\n" +"%(error_message)s\n" +"\n" +"您的项目导出失败。\n" +"\n" +"已经通知系统管理员。\n" +"\n" +"请重试,或联系支持团队%(support_email)s\n" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 +#, python-format +msgid "[Taiga] %(error_subject)s" +msgstr "[Taiga] %(error_subject)s" + +#: taiga/export_import/templates/emails/load_dump-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Project dump imported

\n" +"

Hello %(user)s,

\n" +"

Your project dump has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

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

项目导出成功

\n" +"

您好%(user)s,

\n" +"

您的项目备份文件已经成导出

\n" +" 跳转至%(project)s\n" +"

Taiga团队

\n" +" " + +#: taiga/export_import/templates/emails/load_dump-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your project dump has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"您好%(user)s,\n" +"\n" +"您的项目备份文件已经被成功导出。\n" +"\n" +"您可以从下面地址浏览项目%(project)s:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/export_import/templates/emails/load_dump-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your project dump has been imported" +msgstr "[%(project)s] 项目备份文件已经被导出" + +#: taiga/export_import/validators/fields.py:144 +msgid "{}=\"{}\" not found in this project" +msgstr "{}=\"{}\" 在这个项目没找到" + +#: taiga/export_import/validators/validators.py:150 +#: taiga/projects/custom_attributes/validators.py:109 +msgid "Invalid content. It must be {\"key\": \"value\",...}" +msgstr "内容非法. 正确格式 {\"key\": \"value\",...}" + +#: taiga/export_import/validators/validators.py:165 +#: taiga/projects/custom_attributes/validators.py:124 +msgid "It contain invalid custom fields." +msgstr "包含非法的自定义栏目" + +#: taiga/export_import/validators/validators.py:245 +#: taiga/projects/validators.py:54 +msgid "Name duplicated for the project" +msgstr "项目名称重复" + +#: taiga/external_apps/api.py:43 taiga/external_apps/api.py:70 +#: taiga/external_apps/api.py:77 +msgid "Authentication required" +msgstr "需要身份认证" + +#: taiga/external_apps/models.py:35 +#: taiga/projects/custom_attributes/models.py:36 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 +msgid "name" +msgstr "名称" + +#: taiga/external_apps/models.py:37 +msgid "Icon url" +msgstr "图标地址" + +#: taiga/external_apps/models.py:38 +msgid "web" +msgstr "网页" + +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 +#: taiga/projects/custom_attributes/models.py:37 +#: taiga/projects/epics/models.py:56 +#: taiga/projects/history/templatetags/functions.py:25 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 +#: taiga/projects/userstories/models.py:95 +msgid "description" +msgstr "描述" + +#: taiga/external_apps/models.py:41 +msgid "Next url" +msgstr "下一个URL" + +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 +msgid "user" +msgstr "用户" + +#: taiga/external_apps/models.py:59 +msgid "application" +msgstr "应用" + +#: taiga/feedback/models.py:25 taiga/users/models.py:140 +msgid "full name" +msgstr "全名" + +#: taiga/feedback/models.py:27 taiga/users/models.py:135 +msgid "email address" +msgstr "邮件地址" + +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 +msgid "comment" +msgstr "评论" + +#: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 +#: taiga/projects/custom_attributes/models.py:46 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 +#: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 +#: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 +#: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 +msgid "created date" +msgstr "创建日期" + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Feedback

\n" +"

Taiga has received feedback from %(full_name)s <%(email)s>

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

反馈

\n" +"

Taiga收到%(full_name)s <%(email)s>的反馈

\n" +" " + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:9 +#, python-format +msgid "" +"\n" +"

Comment

\n" +"

%(comment)s

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

评论

\n" +"

%(comment)s

\n" +" " + +#: taiga/feedback/templates/emails/feedback_notification-body-html.jinja:18 +#: taiga/projects/admin.py:106 taiga/users/admin.py:120 +msgid "Extra info" +msgstr "更多信息" + +#: taiga/feedback/templates/emails/feedback_notification-body-text.jinja:1 +#, python-format +msgid "" +"---------\n" +"- From: %(full_name)s <%(email)s>\n" +"---------\n" +"- Comment:\n" +"%(comment)s\n" +"---------" +msgstr "" +"---------\n" +"- 来自: %(full_name)s <%(email)s>\n" +"---------\n" +"- 评论:\n" +"%(comment)s\n" +"---------" + +#: taiga/feedback/templates/emails/feedback_notification-body-text.jinja:8 +msgid "- Extra info:" +msgstr " - 更多信息:" + +#: taiga/feedback/templates/emails/feedback_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Feedback from %(full_name)s <%(email)s>\n" +msgstr "" +"\n" +"[Taiga] 反馈来自%(full_name)s <%(email)s>\n" + +#: taiga/hooks/api.py:54 +msgid "The payload is not a valid json" +msgstr "内容不是一个合法的JSON" + +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 +msgid "The project doesn't exist" +msgstr "项目不存在" + +#: taiga/hooks/api.py:66 +msgid "Bad signature" +msgstr "签名错误" + +#: taiga/hooks/event_hooks.py:66 +#, python-brace-format +msgid "" +"[@{user_name}]({user_url} \"See @{user_name}'s {platform} profile\") says in " +"[{platform}#{number}]({comment_url} \"Go to comment\"):\n" +"\n" +"\"{comment_message}\"" +msgstr "" +"[@{user_name}]({user_url} \"查看@{user_name}'s {platform} profile\") 来自 " +"[{platform}#{number}]({comment_url} \"跳转至评论\"):\n" +"\n" +"\"{comment_message}\"" + +#: taiga/hooks/event_hooks.py:71 +#, python-brace-format +msgid "" +"Comment From {platform}:\n" +"\n" +"> {comment_message}" +msgstr "" +"评论来自 {platform}:\n" +"\n" +"> {comment_message}" + +#: taiga/hooks/event_hooks.py:84 +msgid "Invalid issue comment information" +msgstr "无效的问题评论信息" + +#: taiga/hooks/event_hooks.py:103 +#, python-brace-format +msgid "" +"Issue created by [@{user_name}]({user_url} \"See @{user_name}'s {platform} " +"profile\") from [{platform}#{number}]({url} \"Go to issue\")." +msgstr "" +"问题创建自 [@{user_name}]({user_url} \"See @{user_name}'s {platform} profile" +"\") from [{platform}#{number}]({url} \"跳转至特性\")." + +#: taiga/hooks/event_hooks.py:107 +#, python-brace-format +msgid "Issue created from {platform}." +msgstr "问题创建自{platform}." + +#: taiga/hooks/event_hooks.py:120 +msgid "Invalid issue information" +msgstr "无效的问题信息" + +#: taiga/hooks/event_hooks.py:149 taiga/hooks/event_hooks.py:171 +msgid "unknown user" +msgstr "未知用户" + +#: taiga/hooks/event_hooks.py:156 +#, python-brace-format +msgid "" +"{user_text} changed the status from [{platform} commit]({commit_url} \"See " +"commit '{commit_id} - {commit_message}'\")\n" +"\n" +" - Status: **{src_status}** → **{dst_status}**" +msgstr "" +"{user_text} 改变状态 [{platform} commit]({commit_url} \"查看提交 " +"'{commit_id} - {commit_message}'\")\n" +"\n" +" - 状态: **{src_status}** → **{dst_status}**" + +#: taiga/hooks/event_hooks.py:161 +#, python-brace-format +msgid "" +"Changed status from {platform} commit.\n" +"\n" +" - Status: **{src_status}** → **{dst_status}**" +msgstr "" +"改变状态,从 {platform} 提交.\n" +"\n" +" - 状态: **{src_status}** → **{dst_status}**" + +#: taiga/hooks/event_hooks.py:179 +#, python-brace-format +msgid "" +"This {type_name} has been mentioned by {user_text} in the [{platform} commit]" +"({commit_url} \"See commit '{commit_id} - {commit_message}'\") " +"\"{commit_message}\"" +msgstr "" +"这个{type_name}已经被{user_text}提到,从这个[{platform} commit]({commit_url} " +"\"查看提交 '{commit_id} - {commit_message}'\") \"{commit_message}\"" + +#: taiga/hooks/event_hooks.py:184 +#, python-brace-format +msgid "" +"This issue has been mentioned in the {platform} commit \"{commit_message}\"" +msgstr "在 {platform} 的提交\"{commit_message}\"中提到这个特性" + +#: taiga/hooks/event_hooks.py:206 +msgid "The referenced element doesn't exist" +msgstr "引用的元素不存在" + +#: taiga/hooks/event_hooks.py:222 +msgid "The status doesn't exist" +msgstr "状态不存在" + +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + +#: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 +msgid "View project" +msgstr "查看项目" + +#: taiga/permissions/choices.py:24 taiga/permissions/choices.py:36 +msgid "View milestones" +msgstr "查看里程碑" + +#: taiga/permissions/choices.py:25 taiga/permissions/choices.py:41 +msgid "View epic" +msgstr "查看史诗" + +#: taiga/permissions/choices.py:26 +msgid "View user stories" +msgstr "查看用户故事" + +#: taiga/permissions/choices.py:27 taiga/permissions/choices.py:53 +msgid "View tasks" +msgstr "查看任务" + +#: taiga/permissions/choices.py:28 taiga/permissions/choices.py:59 +msgid "View issues" +msgstr "查看问题" + +#: taiga/permissions/choices.py:29 taiga/permissions/choices.py:65 +msgid "View wiki pages" +msgstr "查看维基页" + +#: taiga/permissions/choices.py:30 taiga/permissions/choices.py:71 +msgid "View wiki links" +msgstr "查看维基链接" + +#: taiga/permissions/choices.py:37 +msgid "Add milestone" +msgstr "新增里程碑" + +#: taiga/permissions/choices.py:38 +msgid "Modify milestone" +msgstr "修改里程碑" + +#: taiga/permissions/choices.py:39 +msgid "Delete milestone" +msgstr "删除里程碑" + +#: taiga/permissions/choices.py:42 +msgid "Add epic" +msgstr "添加史诗" + +#: taiga/permissions/choices.py:43 +msgid "Modify epic" +msgstr "修改史诗" + +#: taiga/permissions/choices.py:44 +msgid "Comment epic" +msgstr "评论史诗" + +#: taiga/permissions/choices.py:45 +msgid "Delete epic" +msgstr "删除史诗" + +#: taiga/permissions/choices.py:47 +msgid "View user story" +msgstr "查看用户故事" + +#: taiga/permissions/choices.py:48 +msgid "Add user story" +msgstr "新增用户故事" + +#: taiga/permissions/choices.py:49 +msgid "Modify user story" +msgstr "修改用户故事" + +#: taiga/permissions/choices.py:50 +msgid "Comment user story" +msgstr "评论用户故事" + +#: taiga/permissions/choices.py:51 +msgid "Delete user story" +msgstr "删除用户故事" + +#: taiga/permissions/choices.py:54 +msgid "Add task" +msgstr "新增任务" + +#: taiga/permissions/choices.py:55 +msgid "Modify task" +msgstr "修改任务" + +#: taiga/permissions/choices.py:56 +msgid "Comment task" +msgstr "评论任务" + +#: taiga/permissions/choices.py:57 +msgid "Delete task" +msgstr "删除任务" + +#: taiga/permissions/choices.py:60 +msgid "Add issue" +msgstr "新增问题" + +#: taiga/permissions/choices.py:61 +msgid "Modify issue" +msgstr "修改问题" + +#: taiga/permissions/choices.py:62 +msgid "Comment issue" +msgstr "评论问题" + +#: taiga/permissions/choices.py:63 +msgid "Delete issue" +msgstr "删除问题" + +#: taiga/permissions/choices.py:66 +msgid "Add wiki page" +msgstr "新增维基页" + +#: taiga/permissions/choices.py:67 +msgid "Modify wiki page" +msgstr "修改维基页" + +#: taiga/permissions/choices.py:68 +msgid "Comment wiki page" +msgstr "评论维基页" + +#: taiga/permissions/choices.py:69 +msgid "Delete wiki page" +msgstr "删除维基页" + +#: taiga/permissions/choices.py:72 +msgid "Add wiki link" +msgstr "新增维基链接" + +#: taiga/permissions/choices.py:73 +msgid "Modify wiki link" +msgstr "修改维基页" + +#: taiga/permissions/choices.py:74 +msgid "Delete wiki link" +msgstr "删除维基链接" + +#: taiga/permissions/choices.py:78 +msgid "Modify project" +msgstr "修改项目" + +#: taiga/permissions/choices.py:79 +msgid "Delete project" +msgstr "删除项目" + +#: taiga/permissions/choices.py:80 +msgid "Add member" +msgstr "增加新成员" + +#: taiga/permissions/choices.py:81 +msgid "Remove member" +msgstr "删除成员" + +#: taiga/permissions/choices.py:82 +msgid "Admin project values" +msgstr "管理项目" + +#: taiga/permissions/choices.py:83 +msgid "Admin roles" +msgstr "管理角色" + +#: taiga/projects/admin.py:100 +msgid "Privacity" +msgstr "私有" + +#: taiga/projects/admin.py:111 +msgid "Modules" +msgstr "模块" + +#: taiga/projects/admin.py:119 +msgid "Default values" +msgstr "预设值" + +#: taiga/projects/admin.py:125 +msgid "Activity" +msgstr "动态" + +#: taiga/projects/admin.py:130 +msgid "Fans" +msgstr "粉丝" + +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 +#: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 +#: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 +#: taiga/users/admin.py:69 taiga/userstorage/models.py:27 +msgid "owner" +msgstr "所有者" + +#: taiga/projects/admin.py:192 +#, python-brace-format +msgid "{count} successfully made public." +msgstr "{count} 成功公开。" + +#: taiga/projects/admin.py:193 +msgid "Make public" +msgstr "公开" + +#: taiga/projects/admin.py:207 +#, python-brace-format +msgid "{count} successfully made private." +msgstr "{count} 成功的私有。" + +#: taiga/projects/admin.py:208 +msgid "Make private" +msgstr "私有" + +#: taiga/projects/admin.py:238 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "删除选中的%(verbose_name_plural)s" + +#: taiga/projects/api.py:160 taiga/users/api.py:244 +msgid "Incomplete arguments" +msgstr "不完整的参数" + +#: taiga/projects/api.py:164 taiga/users/api.py:249 +msgid "Invalid image format" +msgstr "非法的图片格式" + +#: taiga/projects/api.py:225 +msgid "Not valid template name" +msgstr "非法的模板名称" + +#: taiga/projects/api.py:228 +msgid "Not valid template description" +msgstr "无效的模板说明" + +#: taiga/projects/api.py:354 +msgid "Invalid user id" +msgstr "无效用户ID" + +#: taiga/projects/api.py:360 +msgid "The user doesn't exist" +msgstr "用户不存在" + +#: taiga/projects/api.py:364 +msgid "The user must be already a project member" +msgstr "用户必须属于某一个项目" + +#: taiga/projects/api.py:785 +msgid "" +"The project must have an owner and at least one of the users must be an " +"active admin" +msgstr "该项目必须有一个所有者,至少一个用户必须是一个积极的管理员" + +#: taiga/projects/api.py:819 +msgid "You don't have permisions to see that." +msgstr "你无权访问" + +#: taiga/projects/attachments/api.py:54 +msgid "Partial updates are not supported" +msgstr "不支持部分更新" + +#: taiga/projects/attachments/api.py:69 +msgid "Object id issue isn't exists" +msgstr "此问题不存在" + +#: taiga/projects/attachments/api.py:72 +msgid "Project ID not matches between object and project" +msgstr "对象和项目间的项目ID不匹配" + +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 +#: taiga/projects/custom_attributes/models.py:43 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 +#: taiga/projects/notifications/models.py:74 +#: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 +#: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 +msgid "project" +msgstr "项目" + +#: taiga/projects/attachments/models.py:43 +msgid "content type" +msgstr "内容类别" + +#: taiga/projects/attachments/models.py:45 +msgid "object id" +msgstr "对象id" + +#: taiga/projects/attachments/models.py:51 +#: taiga/projects/custom_attributes/models.py:48 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 +#: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 +#: taiga/userstorage/models.py:31 +msgid "modified date" +msgstr "修改日期" + +#: taiga/projects/attachments/models.py:56 +msgid "attached file" +msgstr "附加文件" + +#: taiga/projects/attachments/models.py:58 +msgid "sha1" +msgstr "SHA1" + +#: taiga/projects/attachments/models.py:60 +msgid "is deprecated" +msgstr "已废弃" + +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 +#: taiga/projects/custom_attributes/models.py:41 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 +msgid "order" +msgstr "次序" + +#: taiga/projects/choices.py:23 +msgid "AppearIn" +msgstr "AppearIn" + +#: taiga/projects/choices.py:24 +msgid "Jitsi" +msgstr "Jitsi" + +#: taiga/projects/choices.py:25 +msgid "Custom" +msgstr "自定" + +#: taiga/projects/choices.py:26 +msgid "Talky" +msgstr "Talky" + +#: taiga/projects/choices.py:35 +msgid "This project is blocked due to payment failure" +msgstr "这个项目因为交付失败被封锁" + +#: taiga/projects/choices.py:36 +msgid "This project is blocked by admin staff" +msgstr "这个项目被管理员封锁" + +#: taiga/projects/choices.py:37 +msgid "This project is blocked because the owner left" +msgstr "这个因为所有者离开被封锁" + +#: taiga/projects/choices.py:38 +msgid "This project is blocked while it's deleted" +msgstr "这个项目因为删除被封锁" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 +msgid "Text" +msgstr "单行文字" + +#: taiga/projects/custom_attributes/choices.py:30 +msgid "Multi-Line Text" +msgstr "多行文字" + +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 +msgid "Date" +msgstr "日期" + +#: taiga/projects/custom_attributes/choices.py:33 +msgid "Url" +msgstr "链接" + +#: taiga/projects/custom_attributes/models.py:40 +#: taiga/projects/issues/models.py:45 +msgid "type" +msgstr "类型" + +#: taiga/projects/custom_attributes/models.py:95 +msgid "values" +msgstr "值" + +#: taiga/projects/custom_attributes/models.py:105 +msgid "epic" +msgstr "史诗" + +#: taiga/projects/custom_attributes/models.py:121 +#: taiga/projects/tasks/models.py:35 taiga/projects/userstories/models.py:38 +msgid "user story" +msgstr "用户故事" + +#: taiga/projects/custom_attributes/models.py:137 +msgid "task" +msgstr "任务" + +#: taiga/projects/custom_attributes/models.py:153 +msgid "issue" +msgstr "问题" + +#: taiga/projects/custom_attributes/validators.py:58 +msgid "Already exists one with the same name." +msgstr "已经存在" + +#: taiga/projects/epics/api.py:94 +msgid "You don't have permissions to set this status to this epic." +msgstr "你无权设置这个史诗的状态" + +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 +#: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 +msgid "ref" +msgstr "参照" + +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 +#: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 +msgid "status" +msgstr "状态" + +#: taiga/projects/epics/models.py:46 +msgid "epics order" +msgstr "史诗次序" + +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 +#: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 +msgid "subject" +msgstr "主题" + +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 +msgid "color" +msgstr "颜色" + +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 +#: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 +msgid "assigned to" +msgstr "指派给" + +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 +msgid "is client requirement" +msgstr "客户需求" + +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 +msgid "is team requirement" +msgstr "团队需求" + +#: taiga/projects/epics/models.py:70 +msgid "user stories" +msgstr "用户故事" + +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "外部引用" + +#: taiga/projects/epics/validators.py:37 +msgid "There's no epic with that id" +msgstr "指定ID无史诗" + +#: taiga/projects/history/api.py:93 +msgid "comment is required" +msgstr "需要评论" + +#: taiga/projects/history/api.py:96 +msgid "deleted comments can't be edited" +msgstr "删除的评论无法编辑" + +#: taiga/projects/history/api.py:130 +msgid "Comment already deleted" +msgstr "评论已删除" + +#: taiga/projects/history/api.py:151 +msgid "Comment not deleted" +msgstr "评论未删除" + +#: taiga/projects/history/choices.py:31 +msgid "Change" +msgstr "变更" + +#: taiga/projects/history/choices.py:32 +msgid "Create" +msgstr "创建" + +#: taiga/projects/history/choices.py:33 +msgid "Delete" +msgstr "删除" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 +#, python-format +msgid "%(role)s role points" +msgstr "%(role)s 角色权重" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 +msgid "from" +msgstr "来自" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 +msgid "to" +msgstr "至" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 +msgid "Added new attachment" +msgstr "添加新的附件" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 +msgid "Updated attachment" +msgstr "更新附件" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +msgid "deprecated" +msgstr "已废弃" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 +msgid "not deprecated" +msgstr "未废弃" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 +msgid "Deleted attachment" +msgstr "删除附件:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 +msgid "added" +msgstr "已加入" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 +msgid "removed" +msgstr "移除" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 +#: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 +msgid "Unassigned" +msgstr "未指派" + +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 +msgid "-deleted-" +msgstr "-删除-" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 +msgid "to:" +msgstr "发送给:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 +msgid "from:" +msgstr "来自:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 +msgid "Added" +msgstr "已添加" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 +msgid "Changed" +msgstr "已变更" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 +msgid "Deleted" +msgstr "已删除" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 +msgid "added:" +msgstr "已添加:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 +msgid "removed:" +msgstr "已移除:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 +msgid "From:" +msgstr "来自:" + +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 +msgid "To:" +msgstr "发送给:" + +#: taiga/projects/history/templatetags/functions.py:26 +#: taiga/projects/wiki/models.py:38 +msgid "content" +msgstr "内容" + +#: taiga/projects/history/templatetags/functions.py:27 +#: taiga/projects/mixins/blocked.py:33 +msgid "blocked note" +msgstr "封锁的笔记" + +#: taiga/projects/history/templatetags/functions.py:28 +msgid "sprint" +msgstr "冲刺任务" + +#: taiga/projects/issues/api.py:157 +msgid "You don't have permissions to set this sprint to this issue." +msgstr "您没有权限设置此冲刺到这一问题。" + +#: taiga/projects/issues/api.py:161 +msgid "You don't have permissions to set this status to this issue." +msgstr "你无权设置此状态到这个问题。" + +#: taiga/projects/issues/api.py:165 +msgid "You don't have permissions to set this severity to this issue." +msgstr "您没有权限设置此严重程度这一问题。" + +#: taiga/projects/issues/api.py:169 +msgid "You don't have permissions to set this priority to this issue." +msgstr "您没有权限设置此优先级这一问题。" + +#: taiga/projects/issues/api.py:173 +msgid "You don't have permissions to set this type to this issue." +msgstr "您没有权限设置此类型这一问题。" + +#: taiga/projects/issues/models.py:41 +msgid "severity" +msgstr "严重程度" + +#: taiga/projects/issues/models.py:43 +msgid "priority" +msgstr "优先级" + +#: taiga/projects/issues/models.py:48 taiga/projects/tasks/models.py:46 +#: taiga/projects/userstories/models.py:65 +msgid "milestone" +msgstr "里程碑" + +#: taiga/projects/issues/models.py:57 taiga/projects/tasks/models.py:53 +msgid "finished date" +msgstr "完成日期" + +#: taiga/projects/likes/models.py:36 +msgid "Like" +msgstr "点赞" + +#: taiga/projects/likes/models.py:37 +msgid "Likes" +msgstr "点赞" + +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 +msgid "slug" +msgstr "代称" + +#: taiga/projects/milestones/models.py:45 +msgid "estimated start date" +msgstr "预估开始日期" + +#: taiga/projects/milestones/models.py:46 +msgid "estimated finish date" +msgstr "预估结束日期" + +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 +msgid "is closed" +msgstr "是关闭的" + +#: taiga/projects/milestones/models.py:55 +msgid "disponibility" +msgstr "不受约束" + +#: taiga/projects/milestones/models.py:79 +msgid "The estimated start must be previous to the estimated finish." +msgstr "预估的开始日期必须早于完成日期" + +#: taiga/projects/milestones/validators.py:33 +msgid "There's no milestone with that id" +msgstr "此ID无里程碑" + +#: taiga/projects/mixins/blocked.py:31 +msgid "is blocked" +msgstr "封锁" + +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "ref 参数必需" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + +#: taiga/projects/mixins/ordering.py:49 +#, python-brace-format +msgid "'{param}' parameter is mandatory" +msgstr "'{param}' 参数必填" + +#: taiga/projects/mixins/ordering.py:53 +msgid "'project' parameter is mandatory" +msgstr "'project' 参数必填" + +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 +msgid "email" +msgstr "电子邮件" + +#: taiga/projects/models.py:81 +msgid "create at" +msgstr "创建自" + +#: taiga/projects/models.py:83 taiga/users/models.py:157 +msgid "token" +msgstr "令牌" + +#: taiga/projects/models.py:89 +msgid "invitation extra text" +msgstr "需要更多的文本内容" + +#: taiga/projects/models.py:92 taiga/projects/models.py:741 +msgid "user order" +msgstr "用户故事次序" + +#: taiga/projects/models.py:108 +msgid "The user is already member of the project" +msgstr "该用户已经是此项目成员。" + +#: taiga/projects/models.py:115 +msgid "default epic status" +msgstr "默认史诗状态" + +#: taiga/projects/models.py:119 +msgid "default US status" +msgstr "默认用户故事状态" + +#: taiga/projects/models.py:122 +msgid "default points" +msgstr "默认点数" + +#: taiga/projects/models.py:126 +msgid "default task status" +msgstr "默认任务状态" + +#: taiga/projects/models.py:129 +msgid "default priority" +msgstr "默认优先级" + +#: taiga/projects/models.py:132 +msgid "default severity" +msgstr "默认严重程度" + +#: taiga/projects/models.py:136 +msgid "default issue status" +msgstr "默认问题状态" + +#: taiga/projects/models.py:140 +msgid "default issue type" +msgstr "默认问题类别" + +#: taiga/projects/models.py:156 +msgid "logo" +msgstr "标志" + +#: taiga/projects/models.py:166 +msgid "members" +msgstr "成员" + +#: taiga/projects/models.py:169 +msgid "total of milestones" +msgstr "里程碑总数" + +#: taiga/projects/models.py:170 +msgid "total story points" +msgstr "总故事点数" + +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 +msgid "active epics panel" +msgstr "活动史诗面板" + +#: taiga/projects/models.py:176 taiga/projects/models.py:755 +msgid "active backlog panel" +msgstr "活动积压面板" + +#: taiga/projects/models.py:178 taiga/projects/models.py:757 +msgid "active kanban panel" +msgstr "活动看板面板" + +#: taiga/projects/models.py:180 taiga/projects/models.py:759 +msgid "active wiki panel" +msgstr "活动维基面板" + +#: taiga/projects/models.py:182 taiga/projects/models.py:761 +msgid "active issues panel" +msgstr "活动问题面板" + +#: taiga/projects/models.py:185 taiga/projects/models.py:768 +msgid "videoconference system" +msgstr "视频会议系统" + +#: taiga/projects/models.py:187 taiga/projects/models.py:770 +msgid "videoconference extra data" +msgstr "视像会议的额外数据" + +#: taiga/projects/models.py:193 +msgid "creation template" +msgstr "创建模板" + +#: taiga/projects/models.py:196 taiga/users/admin.py:62 +msgid "is private" +msgstr "私有的" + +#: taiga/projects/models.py:198 +msgid "anonymous permissions" +msgstr "匿名权限" + +#: taiga/projects/models.py:200 +msgid "user permissions" +msgstr "用户权限" + +#: taiga/projects/models.py:203 +msgid "is featured" +msgstr "被推荐" + +#: taiga/projects/models.py:206 taiga/projects/models.py:763 +msgid "is looking for people" +msgstr "招募成员" + +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" +msgstr "" + +#: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "项目转让令牌" + +#: taiga/projects/models.py:226 +msgid "blocked code" +msgstr "已锁定的代码" + +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 +msgid "updated date time" +msgstr "更新的日期时间" + +#: taiga/projects/models.py:232 taiga/projects/models.py:244 +#: taiga/projects/votes/models.py:30 +msgid "count" +msgstr "计数" + +#: taiga/projects/models.py:235 +msgid "fans last week" +msgstr "上周粉丝" + +#: taiga/projects/models.py:238 +msgid "fans last month" +msgstr "上月粉丝" + +#: taiga/projects/models.py:241 +msgid "fans last year" +msgstr "去年粉丝" + +#: taiga/projects/models.py:248 +msgid "activity last week" +msgstr "上周活动" + +#: taiga/projects/models.py:252 +msgid "activity last month" +msgstr "上月活动" + +#: taiga/projects/models.py:256 +msgid "activity last year" +msgstr "去年活动" + +#: taiga/projects/models.py:507 +msgid "modules config" +msgstr "模块配置" + +#: taiga/projects/models.py:559 +msgid "is archived" +msgstr "已归档" + +#: taiga/projects/models.py:563 +msgid "work in progress limit" +msgstr "工作进度限制" + +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 +msgid "value" +msgstr "值" + +#: taiga/projects/models.py:749 +msgid "default owner's role" +msgstr "默认所有者角色" + +#: taiga/projects/models.py:772 +msgid "default options" +msgstr "默认选项" + +#: taiga/projects/models.py:773 +msgid "epic statuses" +msgstr "史诗状态" + +#: taiga/projects/models.py:774 +msgid "us statuses" +msgstr "用户故事状态" + +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 +#: taiga/projects/userstories/models.py:77 +msgid "points" +msgstr "点数" + +#: taiga/projects/models.py:776 +msgid "task statuses" +msgstr "任务状态" + +#: taiga/projects/models.py:777 +msgid "issue statuses" +msgstr "问题状态" + +#: taiga/projects/models.py:778 +msgid "issue types" +msgstr "问题类型" + +#: taiga/projects/models.py:779 +msgid "priorities" +msgstr "优先级" + +#: taiga/projects/models.py:780 +msgid "severities" +msgstr "严重程度" + +#: taiga/projects/models.py:781 +msgid "roles" +msgstr "角色" + +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + +#: taiga/projects/notifications/choices.py:30 +msgid "Involved" +msgstr "涉入" + +#: taiga/projects/notifications/choices.py:31 +msgid "All" +msgstr "所有" + +#: taiga/projects/notifications/choices.py:32 +msgid "None" +msgstr "无" + +#: taiga/projects/notifications/models.py:64 +msgid "created date time" +msgstr "创建日期时间" + +#: taiga/projects/notifications/models.py:68 +msgid "history entries" +msgstr "历史记录条目" + +#: taiga/projects/notifications/models.py:71 +msgid "notify users" +msgstr "通知用户" + +#: taiga/projects/notifications/models.py:93 +#: taiga/projects/notifications/models.py:94 +msgid "Watched" +msgstr "关注" + +#: taiga/projects/notifications/services.py:65 +#: taiga/projects/notifications/services.py:79 +msgid "Notify exists for specified user and project" +msgstr "通知已存在" + +#: taiga/projects/notifications/services.py:436 +msgid "Invalid value for notify level" +msgstr "此通知级别该值非法" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Epic updated

\n" +"

Hello %(user)s,
%(changer)s has updated a epic on %(project)s\n" +"

Epic #%(ref)s %(subject)s

\n" +" See epic\n" +" " +msgstr "" +"\n" +"

已更新的史诗

\n" +"

你好 %(user)s,
%(changer)s 已更新史诗在 %(project)s

\n" +"

史诗 #%(ref)s %(subject)s

\n" +"查看史诗" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Epic updated\n" +"Hello %(user)s, %(changer)s has updated a epic on %(project)s\n" +"See epic #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"史诗更新\n" +"您好,%(user)s, %(changer)s 更新了%(project)s项目的史诗\n" +"查看史诗#%(ref)s %(subject)s 从 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新史诗 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New epic created

\n" +"

Hello %(user)s,
%(changer)s has created a new epic on " +"%(project)s

\n" +"

Epic #%(ref)s %(subject)s

\n" +" See epic\n" +"

The Taiga Team

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

新史诗创建

\n" +"

Hello %(user)s,
%(changer)s 创建了一个史诗在 %(project)s

\n" +"

史诗 #%(ref)s %(subject)s

\n" +" 查看史诗\n" +"

Taiga团队

\n" +" " + +#: taiga/projects/notifications/templates/emails/epics/epic-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New epic created\n" +"Hello %(user)s, %(changer)s has created a new epic on %(project)s\n" +"See epic #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"新的史诗已创建\n" +"你好 %(user)s, %(changer)s 创建新的史诗在 %(project)s\n" +"查看史诗#%(ref)s %(subject)s 在 %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 创建史诗#%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Epic deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a epic on %(project)s\n" +"

Epic #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

史诗已删除

\n" +"

你好 %(user)s,
%(changer)s 已删除史诗在 %(project)s

\n" +"

史诗 #%(ref)s %(subject)s

\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Epic deleted\n" +"Hello %(user)s, %(changer)s has deleted a epic on %(project)s\n" +"Epic #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"史诗已删除\n" +"你好 %(user)s, %(changer)s 已删除的史诗在 %(project)s\n" +"史诗 #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/epics/epic-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the epic #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 删除史诗 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Issue updated

\n" +"

Hello %(user)s,
%(changer)s has updated an issue on %(project)s\n" +"

Issue #%(ref)s %(subject)s

\n" +" See issue\n" +" " +msgstr "" +"\n" +"

问题已更新

\n" +"

你好 %(user)s,
%(changer)s 已更新的问题在 %(project)s

\n" +"

问题 #%(ref)s %(subject)s

\n" +"查看问题" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Issue updated\n" +"Hello %(user)s, %(changer)s has updated an issue on %(project)s\n" +"See issue #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"已更新的问题\n" +"你好%(user)s, %(changer)s 已更新问题在 %(project)s\n" +"查看问题 #%(ref)s %(subject)s 在 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新问题 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New issue created

\n" +"

Hello %(user)s,
%(changer)s has created a new issue on " +"%(project)s

\n" +"

Issue #%(ref)s %(subject)s

\n" +" See issue\n" +"

The Taiga Team

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

新问题已创建

\n" +"

你好 %(user)s,
%(changer)s 已创建新问题在 %(project)s

\n" +"

问题 #%(ref)s %(subject)s

\n" +"" +"查看问题\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New issue created\n" +"Hello %(user)s, %(changer)s has created a new issue on %(project)s\n" +"See issue #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"新问题已创建\n" +"你好 %(user)s, %(changer)s 已创建新问题在 %(project)s\n" +"查看问题 #%(ref)s %(subject)s 在t %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 新增问题 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Issue deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted an issue on %(project)s\n" +"

Issue #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

问题已删除

\n" +"

你好 %(user)s,
%(changer)s已删除问题在 %(project)s

\n" +"

问题 #%(ref)s %(subject)s

\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Issue deleted\n" +"Hello %(user)s, %(changer)s has deleted an issue on %(project)s\n" +"Issue #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"问题已删除\n" +"你好 %(user)s, %(changer)s 已删除问题在 %(project)s\n" +"问题 #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/issues/issue-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the issue #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新问题 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Sprint updated

\n" +"

Hello %(user)s,
%(changer)s has updated an sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +" See sprint\n" +" " +msgstr "" +"\n" +"

冲刺已更新

\n" +"

你好 %(user)s,
%(changer)s 已更新冲刺在 %(project)s

\n" +"

冲刺 %(name)s

\n" +"查" +"看冲刺" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Sprint updated\n" +"Hello %(user)s, %(changer)s has updated a sprint on %(project)s\n" +"See sprint %(name)s at %(url)s\n" +msgstr "" +"\n" +"冲刺已更新\n" +"你好 %(user)s, %(changer)s 已更新冲刺在 %(project)s\n" +"查看冲刺 %(name)s 在 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the sprint \"%(milestone)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新冲刺任务 \"%(milestone)s\"\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New sprint created

\n" +"

Hello %(user)s,
%(changer)s has created a new sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +" See " +"sprint\n" +"

The Taiga Team

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

已创建新冲刺

\n" +"

你好 %(user)s,
%(changer)s 已创建新冲刺在 %(project)s

\n" +"

冲刺 %(name)s

\n" +"查看冲刺\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New sprint created\n" +"Hello %(user)s, %(changer)s has created a new sprint on %(project)s\n" +"See sprint %(name)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已创建新冲刺\n" +"你好 %(user)s, %(changer)s 已创建新冲刺在 %(project)s\n" +"查看冲刺 %(name)s 在 %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the sprint \"%(milestone)s\"\n" +msgstr "" +"\n" +"[%(project)s] 新增冲刺任务\"%(milestone)s\"\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Sprint deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted an sprint on " +"%(project)s

\n" +"

Sprint %(name)s

\n" +"

The Taiga Team

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

已删除冲刺

\n" +"

你好 %(user)s,
%(changer)s 已删除冲刺在 %(project)s

\n" +"

冲刺 %(name)s

\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Sprint deleted\n" +"Hello %(user)s, %(changer)s has deleted an sprint on %(project)s\n" +"Sprint %(name)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已删除冲刺\n" +"你好 %(user)s, %(changer)s 已删除冲刺在 %(project)s\n" +"冲刺 %(name)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/milestones/milestone-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the Sprint \"%(milestone)s\"\n" +msgstr "" +"\n" +"[%(project)s] 删除冲刺任务 \"%(milestone)s\"\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Task updated

\n" +"

Hello %(user)s,
%(changer)s has updated a task on %(project)s\n" +"

Task #%(ref)s %(subject)s

\n" +"
See task\n" +" " +msgstr "" +"\n" +"

已更新的任务

\n" +"

你好 %(user)s,
%(changer)s 已更新的任务在%(project)s

\n" +"

任务 #%(ref)s %(subject)s

\n" +"查看任务" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Task updated\n" +"Hello %(user)s, %(changer)s has updated a task on %(project)s\n" +"See task #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"已更新的任务\n" +"你好 %(user)s, %(changer)s 已更新的任务在 %(project)s\n" +"查看任务 #%(ref)s %(subject)s 在 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the task #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新任务 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New task created

\n" +"

Hello %(user)s,
%(changer)s has created a new task on " +"%(project)s

\n" +"

Task #%(ref)s %(subject)s

\n" +" See task\n" +"

The Taiga Team

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

已创建新任务

\n" +"

你好 %(user)s,
%(changer)s 已创建的新任务在 %(project)s

\n" +"

任务 #%(ref)s %(subject)s

\n" +"" +"查看任务\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New task created\n" +"Hello %(user)s, %(changer)s has created a new task on %(project)s\n" +"See task #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已创建新任务\n" +"你好 %(user)s, %(changer)s 已创建的新任务在%(project)s\n" +"查看任务 #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the task #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 新增任务 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Task deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a task on %(project)s\n" +"

Task #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

已删除的任务

\n" +"

你好 %(user)s,
%(changer)s 已删除的任务在 %(project)s

\n" +"

任务 #%(ref)s %(subject)s

\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Task deleted\n" +"Hello %(user)s, %(changer)s has deleted a task on %(project)s\n" +"Task #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已删除的任务\n" +"你好 %(user)s, %(changer)s 已删除的任务在 %(project)s\n" +"任务 #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/tasks/task-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the task #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 删除任务 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

User Story updated

\n" +"

Hello %(user)s,
%(changer)s has updated a user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +" See user story\n" +" " +msgstr "" +"\n" +"

已更新的用户故事

\n" +"

你好 %(user)s,
%(changer)s 已更新的用户故事在 %(project)s

\n" +"

用户故事 #%(ref)s %(subject)s

\n" +"查看用户故事" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"User story updated\n" +"Hello %(user)s, %(changer)s has updated a user story on %(project)s\n" +"See user story #%(ref)s %(subject)s at %(url)s\n" +msgstr "" +"\n" +"已更新的用户故事\n" +"你好 %(user)s, %(changer)s 已更新的用户故事在 %(project)s\n" +"查看用户故事 #%(ref)s %(subject)s 在 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the US #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新用户故事 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New user story created

\n" +"

Hello %(user)s,
%(changer)s has created a new user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +" See user story\n" +"

The Taiga Team

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

已创建新的用户故事

\n" +"

你好%(user)s,
%(changer)s 已创建新的用户故事在 %(project)s

\n" +"

用户故事 #%(ref)s %(subject)s

\n" +"查看用户故事\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New user story created\n" +"Hello %(user)s, %(changer)s has created a new user story on %(project)s\n" +"See user story #%(ref)s %(subject)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已创建新的用户故事\n" +"你好 %(user)s, %(changer)s 已创建新的用户故事在 %(project)s\n" +"查看用户故事 #%(ref)s %(subject)s 在 %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the US #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 新增用户故事 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

User Story deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a user story on " +"%(project)s

\n" +"

User Story #%(ref)s %(subject)s

\n" +"

The Taiga Team

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

已删除的用户故事

\n" +"

你好 %(user)s,
%(changer)s 已删除的用户故事在 %(project)s

\n" +"

用户故事 #%(ref)s %(subject)s

\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"User Story deleted\n" +"Hello %(user)s, %(changer)s has deleted a user story on %(project)s\n" +"User Story #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已删除的用户故事\n" +"你好 %(user)s, %(changer)s已删除的用户故事在 %(project)s\n" +"用户故事 #%(ref)s %(subject)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/userstories/userstory-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the US #%(ref)s \"%(subject)s\"\n" +msgstr "" +"\n" +"[%(project)s] 删除用户故事 #%(ref)s \"%(subject)s\"\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Wiki Page updated

\n" +"

Hello %(user)s,
%(changer)s has updated a wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +" See Wiki Page\n" +" " +msgstr "" +"\n" +"

Wiki 页面已更新

\n" +"

你好 %(user)s,
%(changer)s 已更新的 wiki 页面在 %(project)s

\n" +"

Wiki 页面 %(page)s

\n" +"查看Wiki" +"页面" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-body-text.jinja:3 +#, python-format +msgid "" +"\n" +"Wiki Page updated\n" +"\n" +"Hello %(user)s, %(changer)s has updated a wiki page on %(project)s\n" +"\n" +"See wiki page %(page)s at %(url)s\n" +msgstr "" +"\n" +"Wiki 页面已更新\n" +"\n" +"你好 %(user)s, %(changer)s 已更新的 wiki 页面在 %(project)s\n" +"\n" +"查看 wiki 页面 %(page)s 在 %(url)s\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-change-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Updated the Wiki Page \"%(page)s\"\n" +msgstr "" +"\n" +"[%(project)s] 更新维基页 \"%(page)s\"\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

New wiki page created

\n" +"

Hello %(user)s,
%(changer)s has created a new wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +" See " +"wiki page\n" +"

The Taiga Team

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

已创建新的 wiki 页面

\n" +"

你好 %(user)s,
%(changer)s 已创建新的 wiki 页面在 %(project)s

\n" +"

Wiki 页面 %(page)s

\n" +"查看 wiki " +"页面\n" +"

The Taiga Team

" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"New wiki page created\n" +"\n" +"Hello %(user)s, %(changer)s has created a new wiki page on %(project)s\n" +"\n" +"See wiki page %(page)s at %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"已创建新的 wiki 页面\n" +"\n" +"你好 %(user)s, %(changer)s 已创建新的 wiki 页面在 %(project)s\n" +"\n" +"查看 wiki 页面 %(page)s 在 %(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-create-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Created the Wiki Page \"%(page)s\"\n" +msgstr "" +"\n" +"[%(project)s] 创建维基页 \"%(page)s\"\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Wiki page deleted

\n" +"

Hello %(user)s,
%(changer)s has deleted a wiki page on " +"%(project)s

\n" +"

Wiki page %(page)s

\n" +"

The Taiga Team

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

维基页删除

\n" +"

您好,%(user)s,
%(changer)s 删除了%(project)s项目的维基页

\n" +"

维基页 %(page)s

\n" +"

Taiga团队

\n" +" " + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Wiki page deleted\n" +"\n" +"Hello %(user)s, %(changer)s has deleted a wiki page on %(project)s\n" +"\n" +"Wiki page %(page)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"维基页删除\n" +"\n" +"您好 %(user)s, %(changer)s 删除了%(project)s项目的维基页 \n" +"\n" +"维基页%(page)s\n" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/projects/notifications/templates/emails/wiki/wikipage-delete-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Deleted the Wiki Page \"%(page)s\"\n" +msgstr "" +"\n" +"[%(project)s] 删除维基页 \"%(page)s\"\n" + +#: taiga/projects/notifications/validators.py:48 +msgid "Watchers contains invalid users" +msgstr "观察者保护无效用户" + +#: taiga/projects/occ/mixins.py:37 +msgid "The version must be an integer" +msgstr "版本号必须是个整数值" + +#: taiga/projects/occ/mixins.py:60 +msgid "The version parameter is not valid" +msgstr "版本号参数无效" + +#: taiga/projects/occ/mixins.py:76 +msgid "The version doesn't match with the current one" +msgstr "版本号与当前版本不匹配" + +#: taiga/projects/occ/mixins.py:95 +msgid "version" +msgstr "版本" + +#: taiga/projects/permissions.py:44 +msgid "" +"You can't leave the project if you are the owner or there are no more admins" +msgstr "因没有其他项目管理者,项目所有者不能离开项目" + +#: taiga/projects/services/members.py:133 +msgid "Project without owner" +msgstr "项目没有所有者" + +#: taiga/projects/services/members.py:138 +msgid "You have reached your current limit of memberships for private projects" +msgstr "你已经达到了私有项目的最大成员数目" + +#: taiga/projects/services/members.py:142 +msgid "You have reached your current limit of memberships for public projects" +msgstr "你已经达到了公开项目的最大成员数目" + +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 +msgid "You can't have more private projects" +msgstr "你拥有的私有项目已到上限" + +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 +msgid "" +"This project reaches your current limit of memberships for private projects" +msgstr "该项目已达到你设置的私有项目最大成员数" + +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 +msgid "You can't have more public projects" +msgstr "你拥有的公开项目已到上限" + +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 +msgid "" +"This project reaches your current limit of memberships for public projects" +msgstr "该项目已达到你设置的公开项目最大成员数" + +#: taiga/projects/services/stats.py:197 +msgid "Future sprint" +msgstr "未来的冲刺任务" + +#: taiga/projects/services/stats.py:217 +msgid "Project End" +msgstr "项目结束" + +#: taiga/projects/services/transfer.py:62 +#: taiga/projects/services/transfer.py:69 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 +msgid "Token is invalid" +msgstr "令牌无效" + +#: taiga/projects/services/transfer.py:67 +msgid "Token has expired" +msgstr "令牌已经过期" + +#: taiga/projects/tagging/fields.py:52 +#, python-brace-format +msgid "Invalid tag '{value}'. The color is not a valid HEX color or null." +msgstr "无效标签 '{value}',该颜色不是一个合法的HEX颜色。" + +#: taiga/projects/tagging/fields.py:55 +#, python-brace-format +msgid "" +"Invalid tag '{value}'. it must be the name or a pair '[\"name\", \"hex color/" +"\" | null]'." +msgstr "" +"无效标签 '{value}'. 它必须是或部分是 '[\"name\", \"hex color/\" | null]'。" + +#: taiga/projects/tagging/fields.py:77 +#, python-brace-format +msgid "Invalid tag '{value}'. It must be the tag name." +msgstr "无效标签'{value}'. 它必须为标签名称。" + +#: taiga/projects/tagging/models.py:27 +msgid "tags" +msgstr "标签" + +#: taiga/projects/tagging/models.py:35 +msgid "tags colors" +msgstr "标签颜色" + +#: taiga/projects/tagging/validators.py:47 +#: taiga/projects/tagging/validators.py:74 +msgid "This tag already exists." +msgstr "该标签已存在" + +#: taiga/projects/tagging/validators.py:54 +#: taiga/projects/tagging/validators.py:81 +msgid "The color is not a valid HEX color." +msgstr "该颜色不是一个合法的HEX颜色" + +#: taiga/projects/tagging/validators.py:67 +#: taiga/projects/tagging/validators.py:101 +#: taiga/projects/tagging/validators.py:114 +#: taiga/projects/tagging/validators.py:121 +msgid "The tag doesn't exist." +msgstr "该标签不存在" + +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 +msgid "You don't have permissions to set this sprint to this task." +msgstr "你无权对这个任务设置该冲刺任务。" + +#: taiga/projects/tasks/api.py:101 +msgid "You don't have permissions to set this user story to this task." +msgstr "你无权对这个任务设置该用户故事。" + +#: taiga/projects/tasks/api.py:104 +msgid "You don't have permissions to set this status to this task." +msgstr "你无权对这个任务设置该状态。" + +#: taiga/projects/tasks/models.py:58 +msgid "us order" +msgstr "用户故事次序" + +#: taiga/projects/tasks/models.py:60 +msgid "taskboard order" +msgstr "任务板次序" + +#: taiga/projects/tasks/models.py:68 +msgid "is iocaine" +msgstr "负予全新任务" + +#: taiga/projects/tasks/validators.py:61 +msgid "Invalid milestone id." +msgstr "无效的里程碑ID" + +#: taiga/projects/tasks/validators.py:72 +msgid "Invalid task status id." +msgstr "无效的任务状态ID" + +#: taiga/projects/tasks/validators.py:85 +msgid "Invalid user story id." +msgstr "无效的用户故事ID" + +#: taiga/projects/tasks/validators.py:109 +msgid "Invalid task status id. The status must belong to the same project." +msgstr "任务状态ID无效,该状态必须属于同一项目。" + +#: taiga/projects/tasks/validators.py:123 +msgid "Invalid user story id. The user story must belong to the same project." +msgstr "用户故事ID无效,该用户故事必须属于同一项目。" + +#: taiga/projects/tasks/validators.py:135 +msgid "Invalid milestone id. The milestone must belong to the same project." +msgstr "里程碑ID无效,该里程碑必须属于同一项目" + +#: taiga/projects/tasks/validators.py:152 +msgid "" +"Invalid task ids. All tasks must belong to the same project and, if it " +"exists, to the same status, user story and/or milestone." +msgstr "任务ID无效,所有任务必须属于同一项目。" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:6 +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:4 +msgid "someone" +msgstr "某人" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:11 +#, python-format +msgid "" +"\n" +"

You have been invited to Taiga!

\n" +"

Hi! %(full_name)s has sent you an invitation to join project " +"%(project)s in Taiga.
Taiga is a Free, open Source Agile Project " +"Management Tool.

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

您被邀请加入Taiga

\n" +"

您好! %(full_name)s 邀请您加入%(project)s 项目。
Taiga is a " +"Free, open Source Agile Project Management Tool.

\n" +" " + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:17 +#, python-format +msgid "" +"\n" +"

And now a few words from the jolly good fellow or sistren
" +"who thought so kindly as to invite you

\n" +"

%(extra)s

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

And now a few words from the jolly good fellow or sistren
" +"who thought so kindly as to invite you

\n" +"

%(extra)s

\n" +" " + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:24 +msgid "Accept your invitation to Taiga" +msgstr "接受加入Taiga的邀请" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:24 +msgid "Accept your invitation" +msgstr "接受你的邀请" + +#: taiga/projects/templates/emails/membership_invitation-body-html.jinja:25 +msgid "The Taiga Team" +msgstr "Taiga团队" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:6 +#, python-format +msgid "" +"\n" +"You, or someone you know, has invited you to Taiga\n" +"\n" +"Hi! %(full_name)s has sent you an invitation to join a project called " +"%(project)s which is being managed on Taiga, a Free, open Source Agile " +"Project Management Tool.\n" +msgstr "" +"\n" +"You, or someone you know, has invited you to Taiga\n" +"\n" +"Hi! %(full_name)s has sent you an invitation to join a project called " +"%(project)s which is being managed on Taiga, a Free, open Source Agile " +"Project Management Tool.\n" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:12 +#, python-format +msgid "" +"\n" +"And now a few words from the jolly good fellow or sistren who thought so " +"kindly as to invite you:\n" +"\n" +"%(extra)s\n" +" " +msgstr "" +"\n" +"And now a few words from the jolly good fellow or sistren who thought so " +"kindly as to invite you:\n" +"\n" +"%(extra)s\n" +" " + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:18 +msgid "Accept your invitation to Taiga following this link:" +msgstr "接受加入Taiga邀请,从此链接:" + +#: taiga/projects/templates/emails/membership_invitation-body-text.jinja:20 +msgid "" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/projects/templates/emails/membership_invitation-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Invitation to join to the project '%(project)s'\n" +msgstr "" +"\n" +"[Taiga] 邀请您加入项目 '%(project)s'\n" + +#: taiga/projects/templates/emails/membership_notification-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

You have been added to a project

\n" +"

Hello %(full_name)s,
you have been added to the project " +"%(project)s

\n" +" Go to " +"project\n" +"

The Taiga Team

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

你已添加新的项目

\n" +"

你好 %(full_name)s,
你以添加新的项目 %(project)s

\n" +"跳转至项目\n" +"

The Taiga Team

" + +#: taiga/projects/templates/emails/membership_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"You have been added to a project\n" +"Hello %(full_name)s,you have been added to the project %(project)s\n" +"\n" +"See project at %(url)s\n" +msgstr "" +"\n" +"您已经加入项目\n" +"您好%(full_name)s,你已经加入%(project)s项目\n" +"\n" +"查看项目 %(url)s\n" + +#: taiga/projects/templates/emails/membership_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] Added to the project '%(project)s'\n" +msgstr "" +"\n" +"[Taiga] 新增项目'%(project)s'\n" + +#: taiga/projects/templates/emails/transfer_accept-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(old_owner_name)s,

\n" +"

%(new_owner_name)s has accepted your offer and will become the " +"new project owner for \"%(project_name)s\".

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

您好%(old_owner_name)s,

\n" +"

%(new_owner_name)s已经接受您的请求,将成为 \"%(project_name)s\"项" +"目的所有者。

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

%(new_owner_name)s says:

" +msgstr "

%(new_owner_name)s 说:

" + +#: taiga/projects/templates/emails/transfer_accept-body-html.jinja:14 +msgid "" +"\n" +"

From now on, your new status for this project will be \"admin\".\n" +" " +msgstr "" +"\n" +"

从现在开始, 你将成为该项目的\"admin\"。

\n" +" " + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(old_owner_name)s,\n" +"%(new_owner_name)s has accepted your offer and will become the new project " +"owner for \"%(project_name)s\".\n" +msgstr "" +"\n" +"您好 %(old_owner_name)s,\n" +"%(new_owner_name)s 已经接受您的转让请求,将会成为\"%(project_name)s\"项目的所" +"有者。\n" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:7 +#, python-format +msgid "%(new_owner_name)s says:" +msgstr "%(new_owner_name)s 说:" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:11 +msgid "" +"\n" +"From now on, your new status for this project will be \"admin\".\n" +msgstr "" +"\n" +"从现在开始, 你将成为该项目的\"admin\"。\n" + +#: taiga/projects/templates/emails/transfer_accept-body-text.jinja:16 +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:19 +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:13 +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:18 +msgid "" +"\n" +"The Taiga Team\n" +msgstr "" +"\n" +"Taiga团队\n" + +#: taiga/projects/templates/emails/transfer_accept-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer offer accepted!\n" +msgstr "" +"\n" +"[%(project)s] 项目所有权转让请求被接受!\n" + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(owner_name)s,

\n" +"

%(rejecter_name)s has declined your offer and will not become the " +"new project owner for \"%(project_name)s\".

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

您好,%(owner_name)s,

\n" +"

%(rejecter_name)s 已经拒绝了你的请求,不会接受\"%(project_name)s" +"\"项目的所有权。

\n" +" " + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:10 +#, python-format +msgid "" +"\n" +"

%(rejecter_name)s says:

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

%(rejecter_name)s 说:

\n" +" " + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:16 +msgid "" +"\n" +"

If you want, you can still try to transfer the project ownership to a " +"different person.

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

如果你想, 你可以继续尝试将所有权转让给其他人。

\n" +" " + +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:21 +#: taiga/projects/templates/emails/transfer_reject-body-html.jinja:22 +msgid "Request transfer to a different person" +msgstr "请求转让给其他人" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(owner_name)s,\n" +"%(rejecter_name)s has declined your offer and will not become the new " +"project owner for \"%(project_name)s\".\n" +msgstr "" +"\n" +"您好 %(owner_name)s,\n" +"%(rejecter_name)s 已经拒绝将\"%(project_name)s\"项目的所有权,转让给你。\n" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:7 +#, python-format +msgid "%(rejecter_name)s says:" +msgstr "%(rejecter_name)s 说:" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:11 +msgid "" +"\n" +"If you want, you can still try to transfer the project ownership to a " +"different person.\n" +msgstr "" +"\n" +"如果你想, 你可以继续尝试将所有权转让给其他人。\n" + +#: taiga/projects/templates/emails/transfer_reject-body-text.jinja:15 +msgid "Request transfer to a different person:" +msgstr "请求转让给其他人:" + +#: taiga/projects/templates/emails/transfer_reject-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer declined\n" +msgstr "" +"\n" +"[%(project)s] 拒绝项目所有权转让\n" + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(owner_name)s,

\n" +"

%(requester_name)s has requested to become the project owner for " +"\"%(project_name)s\".

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

您好%(owner_name)s,

\n" +"

%(requester_name)s 请求成为\"%(project_name)s\"项目的所有者。\n" +" " + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:9 +msgid "" +"\n" +"

Please, click on \"Continue\" if you would like to start the " +"project transfer from the administration panel.

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

如果你想转让项目所有权,请点击 \"Continue\" 继续。

\n" +" " + +#: taiga/projects/templates/emails/transfer_request-body-html.jinja:14 +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:22 +msgid "Continue" +msgstr "继续" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(owner_name)s,\n" +"%(requester_name)s has requested to become the project owner for " +"\"%(project_name)s\".\n" +msgstr "" +"\n" +"您好 %(owner_name)s,\n" +"%(requester_name)s 已经请求成为\"%(project_name)s\"项目的所有者.\n" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:6 +msgid "" +"\n" +"Please, go to your project settings if you would like to start the project " +"transfer from the administration panel.\n" +msgstr "" +"\n" +"如果想从控制面板转让项目所有权,请跳转到项目设置。\n" + +#: taiga/projects/templates/emails/transfer_request-body-text.jinja:10 +msgid "Go to your project settings:" +msgstr "跳转至项目设置:" + +#: taiga/projects/templates/emails/transfer_request-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer request\n" +msgstr "" +"\n" +"[%(project)s] 项目所有权转让请求\n" + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Hi %(receiver_name)s,

\n" +"

%(owner_name)s, the current project owner at \"%(project_name)s\" " +"would like you to become the new project owner.

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

您好 %(receiver_name)s,

\n" +"

%(owner_name)s, 拥有 \"%(project_name)s的所有权,\" 希望将该所有权" +"转让给您.

\n" +" " + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:10 +#, python-format +msgid "" +"\n" +"

%(owner_name)s says:

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

%(owner_name)s 说:

\n" +" " + +#: taiga/projects/templates/emails/transfer_start-body-html.jinja:17 +msgid "" +"\n" +"

Please, click on \"Continue\" to either accept or reject this " +"proposal.

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

请点击 \"Continue\" 继续,或其他拒绝.

\n" +" " + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hi %(receiver_name)s,\n" +"%(owner_name)s, the current project owner at \"%(project_name)s\" would like " +"you to become the new project owner.\n" +msgstr "" +"\n" +"您好 %(receiver_name)s,\n" +"%(owner_name)s, 项目\"%(project_name)s\" 的所有者想将项目所有权转让给您。\n" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:6 +#, python-format +msgid "%(owner_name)s says:" +msgstr "%(owner_name)s 说:" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:11 +msgid "" +"\n" +"Please, go to the following link to either accept or reject this proposal.\n" +msgstr "" +"\n" +"请访问下面的链接接受或拒绝该建议。

\n" + +#: taiga/projects/templates/emails/transfer_start-body-text.jinja:15 +msgid "Accept or reject the project ownership transfer:" +msgstr "接受或拒绝该项目所有权的转让:" + +#: taiga/projects/templates/emails/transfer_start-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[%(project)s] Project ownership transfer offer\n" +msgstr "" +"\n" +"[%(project)s] 项目所有权转让请求\n" + +#. Translators: Name of scrum project template. +#: taiga/projects/translations.py:30 +msgid "Scrum" +msgstr "冲刺" + +#. Translators: Description of scrum project template. +#: taiga/projects/translations.py:32 +msgid "" +"The agile product backlog in Scrum is a prioritized features list, " +"containing short descriptions of all functionality desired in the product. " +"When applying Scrum, it's not necessary to start a project with a lengthy, " +"upfront effort to document all requirements. The Scrum product backlog is " +"then allowed to grow and change as more is learned about the product and its " +"customers" +msgstr "" +"The agile product backlog in Scrum is a prioritized features list, " +"containing short descriptions of all functionality desired in the product. " +"When applying Scrum, it's not necessary to start a project with a lengthy, " +"upfront effort to document all requirements. The Scrum product backlog is " +"then allowed to grow and change as more is learned about the product and its " +"customers" + +#. Translators: Name of kanban project template. +#: taiga/projects/translations.py:35 +msgid "Kanban" +msgstr "看板" + +#. Translators: Description of kanban project template. +#: taiga/projects/translations.py:37 +msgid "" +"Kanban is a method for managing knowledge work with an emphasis on just-in-" +"time delivery while not overloading the team members. In this approach, the " +"process, from definition of a task to its delivery to the customer, is " +"displayed for participants to see and team members pull work from a queue." +msgstr "" +"Kanban is a method for managing knowledge work with an emphasis on just-in-" +"time delivery while not overloading the team members. In this approach, the " +"process, from definition of a task to its delivery to the customer, is " +"displayed for participants to see and team members pull work from a queue." + +#. Translators: User story point value (value = undefined) +#: taiga/projects/translations.py:45 +msgid "?" +msgstr "?" + +#. Translators: User story point value (value = 0) +#: taiga/projects/translations.py:47 +msgid "0" +msgstr "0" + +#. Translators: User story point value (value = 0.5) +#: taiga/projects/translations.py:49 +msgid "1/2" +msgstr "1/2" + +#. Translators: User story point value (value = 1) +#: taiga/projects/translations.py:51 +msgid "1" +msgstr "1" + +#. Translators: User story point value (value = 2) +#: taiga/projects/translations.py:53 +msgid "2" +msgstr "2" + +#. Translators: User story point value (value = 3) +#: taiga/projects/translations.py:55 +msgid "3" +msgstr "3" + +#. Translators: User story point value (value = 5) +#: taiga/projects/translations.py:57 +msgid "5" +msgstr "5" + +#. Translators: User story point value (value = 8) +#: taiga/projects/translations.py:59 +msgid "8" +msgstr "8" + +#. Translators: User story point value (value = 10) +#: taiga/projects/translations.py:61 +msgid "10" +msgstr "10" + +#. Translators: User story point value (value = 13) +#: taiga/projects/translations.py:63 +msgid "13" +msgstr "13" + +#. Translators: User story point value (value = 20) +#: taiga/projects/translations.py:65 +msgid "20" +msgstr "20" + +#. Translators: User story point value (value = 40) +#: taiga/projects/translations.py:67 +msgid "40" +msgstr "40" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:75 taiga/projects/translations.py:98 +#: taiga/projects/translations.py:114 +msgid "New" +msgstr "新建" + +#. Translators: User story status +#: taiga/projects/translations.py:78 +msgid "Ready" +msgstr "准备好" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:81 taiga/projects/translations.py:100 +#: taiga/projects/translations.py:116 +msgid "In progress" +msgstr "进行中" + +#. Translators: User story status +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:84 taiga/projects/translations.py:102 +#: taiga/projects/translations.py:118 +msgid "Ready for test" +msgstr "待测试" + +#. Translators: User story status +#: taiga/projects/translations.py:87 +msgid "Done" +msgstr "完成" + +#. Translators: User story status +#: taiga/projects/translations.py:90 +msgid "Archived" +msgstr "归档" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:104 taiga/projects/translations.py:120 +msgid "Closed" +msgstr "已关闭" + +#. Translators: Task status +#. Translators: Issue status +#: taiga/projects/translations.py:106 taiga/projects/translations.py:122 +msgid "Needs Info" +msgstr "需要更多信息" + +#. Translators: Issue status +#: taiga/projects/translations.py:124 +msgid "Postponed" +msgstr "推迟" + +#. Translators: Issue status +#: taiga/projects/translations.py:126 +msgid "Rejected" +msgstr "拒绝" + +#. Translators: Issue type +#: taiga/projects/translations.py:134 +msgid "Bug" +msgstr "缺陷" + +#. Translators: Issue type +#: taiga/projects/translations.py:136 +msgid "Question" +msgstr "疑问" + +#. Translators: Issue type +#: taiga/projects/translations.py:138 +msgid "Enhancement" +msgstr "增强" + +#. Translators: Issue priority +#: taiga/projects/translations.py:146 +msgid "Low" +msgstr "低" + +#. Translators: Issue priority +#. Translators: Issue severity +#: taiga/projects/translations.py:148 taiga/projects/translations.py:161 +msgid "Normal" +msgstr "中" + +#. Translators: Issue priority +#: taiga/projects/translations.py:150 +msgid "High" +msgstr "高" + +#. Translators: Issue severity +#: taiga/projects/translations.py:157 +msgid "Wishlist" +msgstr "收藏" + +#. Translators: Issue severity +#: taiga/projects/translations.py:159 +msgid "Minor" +msgstr "次要" + +#. Translators: Issue severity +#: taiga/projects/translations.py:163 +msgid "Important" +msgstr "重要" + +#. Translators: Issue severity +#: taiga/projects/translations.py:165 +msgid "Critical" +msgstr "关键" + +#. Translators: User role +#: taiga/projects/translations.py:172 +msgid "UX" +msgstr "用户体验" + +#. Translators: User role +#: taiga/projects/translations.py:174 +msgid "Design" +msgstr "设计" + +#. Translators: User role +#: taiga/projects/translations.py:176 +msgid "Front" +msgstr "字体" + +#. Translators: User role +#: taiga/projects/translations.py:178 +msgid "Back" +msgstr "后退" + +#. Translators: User role +#: taiga/projects/translations.py:180 +msgid "Product Owner" +msgstr "产品所有者" + +#. Translators: User role +#: taiga/projects/translations.py:182 +msgid "Stakeholder" +msgstr "相关人员" + +#: taiga/projects/userstories/api.py:128 +msgid "You don't have permissions to set this sprint to this user story." +msgstr "你无权对这个用户故事设置此冲刺任务。" + +#: taiga/projects/userstories/api.py:132 +msgid "You don't have permissions to set this status to this user story." +msgstr "你无权对这个用户故事设置此状态。" + +#: taiga/projects/userstories/api.py:222 +#, python-brace-format +msgid "Invalid role id '{role_id}'" +msgstr "无效的角色ID '{role_id}'" + +#: taiga/projects/userstories/api.py:229 +#, python-brace-format +msgid "Invalid points id '{points_id}'" +msgstr "无效的点数ID '{points_id}'" + +#: taiga/projects/userstories/api.py:244 +#, python-brace-format +msgid "Generating the user story #{ref} - {subject}" +msgstr "生成用户故事#{ref} - {subject}" + +#: taiga/projects/userstories/models.py:41 +msgid "role" +msgstr "角色" + +#: taiga/projects/userstories/models.py:80 +msgid "backlog order" +msgstr "待办任务先后次序" + +#: taiga/projects/userstories/models.py:82 +msgid "sprint order" +msgstr "冲刺任务次序" + +#: taiga/projects/userstories/models.py:84 +msgid "kanban order" +msgstr "看板次序" + +#: taiga/projects/userstories/models.py:92 +msgid "finish date" +msgstr "完成日期" + +#: taiga/projects/userstories/models.py:107 +msgid "generated from issue" +msgstr "从问题派生" + +#: taiga/projects/userstories/validators.py:43 +msgid "There's no user story with that id" +msgstr "该ID无用户故事" + +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 +msgid "" +"Invalid user story status id. The status must belong to the same project." +msgstr "用户故事ID无效,所有用户故事必须属于同一项目" + +#: taiga/projects/userstories/validators.py:121 +msgid "Invalid milestone id. The milistone must belong to the same project." +msgstr "里程碑ID无效,此里程碑必须属于同一项目" + +#: taiga/projects/userstories/validators.py:136 +msgid "" +"Invalid user story ids. All stories must belong to the same project and, if " +"it exists, to the same status and milestone." +msgstr "用户故事ID无效,所有用户故事必须属于同一项目" + +#: taiga/projects/userstories/validators.py:160 +msgid "The milestone isn't valid for the project" +msgstr "此里程碑在该项目无效" + +#: taiga/projects/userstories/validators.py:170 +msgid "All the user stories must be from the same project" +msgstr "所有用户故事必须从属于一个同一项目" + +#: taiga/projects/validators.py:63 +msgid "There's no project with that id" +msgstr "该ID无对应项目" + +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" + +#: taiga/projects/validators.py:155 +msgid "Invalid role for the project" +msgstr "此角色在该项目无效" + +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 +msgid "The project owner must be admin." +msgstr "项目所有者必须是管理者" + +#: taiga/projects/validators.py:195 +msgid "At least one user must be an active admin for this project." +msgstr "一个项目至少有一名活跃的管理者" + +#: taiga/projects/validators.py:240 +msgid "Invalid role ids. All roles must belong to the same project." +msgstr "角色ID无效,所有角色必须属于同一项目" + +#: taiga/projects/validators.py:264 +msgid "Default options" +msgstr "默认选项" + +#: taiga/projects/validators.py:265 +msgid "User story's statuses" +msgstr "用户故事状态" + +#: taiga/projects/validators.py:266 +msgid "Points" +msgstr "点数" + +#: taiga/projects/validators.py:267 +msgid "Task's statuses" +msgstr "任务状态" + +#: taiga/projects/validators.py:268 +msgid "Issue's statuses" +msgstr "问题状态" + +#: taiga/projects/validators.py:269 +msgid "Issue's types" +msgstr "问题类型" + +#: taiga/projects/validators.py:270 +msgid "Priorities" +msgstr "优先级" + +#: taiga/projects/validators.py:271 +msgid "Severities" +msgstr "严重程度" + +#: taiga/projects/validators.py:272 +msgid "Roles" +msgstr "角色" + +#: taiga/projects/votes/models.py:33 taiga/projects/votes/models.py:34 +#: taiga/projects/votes/models.py:58 +msgid "Votes" +msgstr "投票数" + +#: taiga/projects/votes/models.py:57 +msgid "Vote" +msgstr "投票" + +#: taiga/projects/wiki/api.py:77 +msgid "'content' parameter is mandatory" +msgstr "'content' 参数必需" + +#: taiga/projects/wiki/api.py:80 +msgid "'project_id' parameter is mandatory" +msgstr "'project_id' 参数必需" + +#: taiga/projects/wiki/models.py:42 +msgid "last modifier" +msgstr "最后修改者" + +#: taiga/projects/wiki/models.py:75 +msgid "href" +msgstr "超链接" + +#: taiga/timeline/signals.py:65 +msgid "Check the history API for the exact diff" +msgstr "检查确切的 diff 的历史记录 API" + +#: taiga/users/admin.py:39 +msgid "Project Member" +msgstr "项目成员" + +#: taiga/users/admin.py:40 +msgid "Project Members" +msgstr "项目成员" + +#: taiga/users/admin.py:50 +msgid "id" +msgstr "id" + +#: 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:122 +msgid "Permissions" +msgstr "权限" + +#: taiga/users/admin.py:123 +msgid "Restrictions" +msgstr "限制" + +#: taiga/users/admin.py:125 +msgid "Important dates" +msgstr "重要日期" + +#: taiga/users/api.py:132 +msgid "Duplicated email" +msgstr "重复的邮件地址" + +#: taiga/users/api.py:134 +msgid "Not valid email" +msgstr "无效的邮件地址" + +#: taiga/users/api.py:172 +msgid "Invalid username or email" +msgstr "用户名称或邮件地址无效" + +#: taiga/users/api.py:181 +msgid "Mail sended successful!" +msgstr "邮件发送成功!" + +#: taiga/users/api.py:219 +msgid "Current password parameter needed" +msgstr "输入当前密码" + +#: taiga/users/api.py:222 +msgid "New password parameter needed" +msgstr "输入新密码" + +#: taiga/users/api.py:225 +msgid "Invalid password length at least 6 charaters needed" +msgstr "无效密码,长度至少6位" + +#: taiga/users/api.py:228 +msgid "Invalid current password" +msgstr "当前密码无效" + +#: taiga/users/api.py:275 taiga/users/api.py:281 +msgid "" +"Invalid, are you sure the token is correct and you didn't use it before?" +msgstr "无效,请确定令牌正确,之前使用过?" + +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 +msgid "Invalid, are you sure the token is correct?" +msgstr "无效,请确定令牌正确?" + +#: taiga/users/models.py:98 +msgid "superuser status" +msgstr "超级用户状态" + +#: taiga/users/models.py:99 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "选定此用户拥有所有权限,而不用显式将他们分配。" + +#: taiga/users/models.py:129 +msgid "username" +msgstr "用户名" + +#: taiga/users/models.py:130 +msgid "" +"Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" +msgstr "必填,长度小于30的英文字母、数字、“.”、“-”、“_”" + +#: taiga/users/models.py:133 +msgid "Enter a valid username." +msgstr "输入一个合法的用户名" + +#: taiga/users/models.py:136 +msgid "active" +msgstr "活跃" + +#: taiga/users/models.py:137 +msgid "" +"Designates whether this user should be treated as active. Unselect this " +"instead of deleting accounts." +msgstr "指定是否此用户为活跃用户。取消选择这而不是删除帐户。" + +#: taiga/users/models.py:143 +msgid "biography" +msgstr "个人简介" + +#: taiga/users/models.py:146 +msgid "photo" +msgstr "照片" + +#: taiga/users/models.py:147 +msgid "date joined" +msgstr "加入日期" + +#: taiga/users/models.py:149 +msgid "default language" +msgstr "默认语言" + +#: taiga/users/models.py:151 +msgid "default theme" +msgstr "默认主题" + +#: taiga/users/models.py:153 +msgid "default timezone" +msgstr "默认时区" + +#: taiga/users/models.py:155 +msgid "colorize tags" +msgstr "彩色标签" + +#: taiga/users/models.py:160 +msgid "email token" +msgstr "电子邮件密码" + +#: taiga/users/models.py:162 +msgid "new email address" +msgstr "新邮件地址" + +#: taiga/users/models.py:169 +msgid "max number of owned private projects" +msgstr "最大私有项目数" + +#: taiga/users/models.py:172 +msgid "max number of owned public projects" +msgstr "最大公开项目数" + +#: taiga/users/models.py:175 +msgid "max number of memberships for each owned private project" +msgstr "私有项目最大成员数" + +#: taiga/users/models.py:179 +msgid "max number of memberships for each owned public project" +msgstr "公开项目最大成员数" + +#: taiga/users/models.py:307 +msgid "permissions" +msgstr "权限" + +#: taiga/users/services.py:51 taiga/users/services.py:68 +msgid "Username or password does not matches user." +msgstr "用户名或密码错误。" + +#: taiga/users/templates/emails/change_email-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Change your email

\n" +"

Hello %(full_name)s,
please confirm your email

\n" +"
Confirm " +"email\n" +"

You can ignore this message if you did not request.

\n" +"

The Taiga Team

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

邮件地址变更

\n" +"

您好 %(full_name)s,
请确认邮件地址

\n" +" 确认\n" +"

如果不是是提出的申请,可以忽略这封邮件。

\n" +"

Taiga团队

\n" +" " + +#: taiga/users/templates/emails/change_email-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(full_name)s, please confirm your email\n" +"\n" +"%(url)s\n" +"\n" +"You can ignore this message if you did not request.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"您好%(full_name)s, 请求确认您的邮件地址\n" +"\n" +"%(url)s\n" +"\n" +"如果不是你提出的申请,可以忽略这封邮件。\n" +"---\n" +"Taiga团队\n" + +#: taiga/users/templates/emails/change_email-subject.jinja:1 +msgid "[Taiga] Change email" +msgstr "[Taiga] 变更电邮地址" + +#: taiga/users/templates/emails/password_recovery-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Recover your password

\n" +"

Hello %(full_name)s,
you asked to recover your password

\n" +" Recover your password\n" +"

You can ignore this message if you did not request.

\n" +"

The Taiga Team

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

重置密码

\n" +"

您好 %(full_name)s,
您申请进行密码重置

\n" +" 重置" +"\n" +"

如果不是是提出的申请,可以忽略这封邮件。

\n" +"

Taiga团队

\n" +" " + +#: taiga/users/templates/emails/password_recovery-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(full_name)s, you asked to recover your password\n" +"\n" +"%(url)s\n" +"\n" +"You can ignore this message if you did not request.\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" +"\n" +"您好 %(full_name)s, 你申请进行重置密码,点击下面链接进行重置\n" +"\n" +"%(url)s\n" +"\n" +"如果提出的申请,可以忽略这封邮件。\n" +"\n" +"---\n" +"Taiga团队\n" + +#: taiga/users/templates/emails/password_recovery-subject.jinja:1 +msgid "[Taiga] Password recovery" +msgstr "[Taiga] 找回密码" + +#: taiga/users/templates/emails/registered_user-body-html.jinja:6 +msgid "" +"\n" +"
\n" +" " +msgstr "" +"\n" +" \n" +" " + +#: taiga/users/templates/emails/registered_user-body-html.jinja:23 +#, python-format +msgid "" +"\n" +" You may remove your account from this service clicking " +"here\n" +" " +msgstr "" +"\n" +" 你可以注销账户从下面的服务 点击\n" +" " + +#: taiga/users/templates/emails/registered_user-body-text.jinja:1 +msgid "" +"\n" +"Thank you for registering in Taiga\n" +"\n" +"We hope you enjoy it\n" +"\n" +"We built Taiga because we wanted the project management tool that sits open " +"on our computers all day long, to serve as a continued reminder of why we " +"love to collaborate, code and design.\n" +"\n" +"We built it to be beautiful, elegant, simple to use and fun - without " +"forsaking flexibility and power.\n" +"\n" +"--\n" +"The taiga Team\n" +msgstr "" +"\n" +"Thank you for registering in Taiga\n" +"\n" +"We hope you enjoy it\n" +"\n" +"We built Taiga because we wanted the project management tool that sits open " +"on our computers all day long, to serve as a continued reminder of why we " +"love to collaborate, code and design.\n" +"\n" +"We built it to be beautiful, elegant, simple to use and fun - without " +"forsaking flexibility and power.\n" +"\n" +"--\n" +"The taiga Team\n" + +#: taiga/users/templates/emails/registered_user-body-text.jinja:13 +#, python-format +msgid "" +"\n" +"You may remove your account from this service: %(url)s\n" +msgstr "" +"\n" +"您可以注销您的账户,通过: %(url)s\n" + +#: taiga/users/templates/emails/registered_user-subject.jinja:1 +msgid "You've been Taigatized!" +msgstr "您已成为Taiga的一员" + +#: taiga/users/validators.py:45 +msgid "invalid" +msgstr "无效的" + +#: taiga/users/validators.py:56 +msgid "Invalid username. Try with a different one." +msgstr "无效用户名,请尝试其他的。" + +#: taiga/userstorage/api.py:53 +msgid "" +"Duplicate key value violates unique constraint. Key '{}' already exists." +msgstr "重复的键值违反唯一约束。键 '{}' 已经存在。" + +#: taiga/userstorage/models.py:32 +msgid "key" +msgstr "键" + +#: taiga/webhooks/models.py:30 taiga/webhooks/models.py:40 +msgid "URL" +msgstr "网址" + +#: taiga/webhooks/models.py:31 +msgid "secret key" +msgstr "密钥" + +#: taiga/webhooks/models.py:41 +msgid "status code" +msgstr "状态码" + +#: taiga/webhooks/models.py:42 +msgid "request data" +msgstr "请求数据" + +#: taiga/webhooks/models.py:43 +msgid "request headers" +msgstr "请求Header头" + +#: taiga/webhooks/models.py:44 +msgid "response data" +msgstr "响应数据" + +#: taiga/webhooks/models.py:45 +msgid "response headers" +msgstr "响应Header头" + +#: taiga/webhooks/models.py:46 +msgid "duration" +msgstr "持续时间" diff --git a/taiga/locale/zh-Hant/LC_MESSAGES/django.po b/taiga/locale/zh-Hant/LC_MESSAGES/django.po index fcbeb9ea..55aa4ec4 100644 --- a/taiga/locale/zh-Hant/LC_MESSAGES/django.po +++ b/taiga/locale/zh-Hant/LC_MESSAGES/django.po @@ -1,5 +1,5 @@ # taiga-back.taiga. -# Copyright (C) 2014-2016 Taiga Dev Team +# Copyright (C) 2014-2017 Taiga Dev Team # This file is distributed under the same license as the taiga-back package. # # Translators: @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-28 10:29+0200\n" -"PO-Revision-Date: 2016-09-20 10:50+0000\n" +"POT-Creation-Date: 2017-03-08 16:30+0100\n" +"PO-Revision-Date: 2017-03-06 16:54+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" @@ -22,15 +22,15 @@ msgstr "" "Language: zh-Hant\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: taiga/auth/api.py:102 +#: taiga/auth/api.py:74 msgid "Public register is disabled." msgstr "註冊功能暫不開放" -#: taiga/auth/api.py:135 +#: taiga/auth/api.py:101 msgid "invalid register type" msgstr "無效的註冊類型" -#: taiga/auth/api.py:148 +#: taiga/auth/api.py:117 msgid "invalid login type" msgstr "無效的登入類型" @@ -50,17 +50,17 @@ msgstr "代碼與任何有效的邀請不相符" msgid "User is already registered." msgstr "使用者已被註冊。" -#: taiga/auth/services.py:147 +#: taiga/auth/services.py:140 msgid "This user is already a member of the project." msgstr "使用者已是專案成員" -#: taiga/auth/services.py:173 +#: taiga/auth/services.py:164 msgid "Error on creating new user." msgstr "無法創建新使用者" #: taiga/auth/tokens.py:49 taiga/auth/tokens.py:56 -#: taiga/external_apps/services.py:36 taiga/projects/api.py:364 -#: taiga/projects/api.py:385 +#: taiga/external_apps/services.py:34 taiga/projects/api.py:374 +#: taiga/projects/api.py:395 msgid "Invalid token" msgstr "無效的代碼 " @@ -81,130 +81,130 @@ msgstr "此欄位是必要的。" msgid "Invalid value." msgstr "無效的數值" -#: taiga/base/api/fields.py:479 +#: taiga/base/api/fields.py:484 #, python-format msgid "'%s' value must be either True or False." msgstr "'%s' 數值必須為「是」或「否」。" -#: taiga/base/api/fields.py:543 +#: taiga/base/api/fields.py:549 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "輸入有效的代稱,其包括字母,數字,底底線與連字符號" -#: taiga/base/api/fields.py:558 +#: taiga/base/api/fields.py:564 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "請做個有效的選擇。 %(value)s 並不是可以選的選項。" -#: taiga/base/api/fields.py:621 +#: taiga/base/api/fields.py:638 msgid "You email domain is not allowed" msgstr "" -#: taiga/base/api/fields.py:630 +#: taiga/base/api/fields.py:647 msgid "Enter a valid email address." msgstr "輸入無效之電子郵件地址" -#: taiga/base/api/fields.py:672 +#: taiga/base/api/fields.py:689 #, python-format msgid "Date has wrong format. Use one of these formats instead: %s" msgstr "資料格式錯誤,請改用這些格式取代:%s" -#: taiga/base/api/fields.py:736 +#: taiga/base/api/fields.py:753 #, python-format msgid "Datetime has wrong format. Use one of these formats instead: %s" msgstr "日期格式錯誤,請使用這些格式取代:%s" -#: taiga/base/api/fields.py:806 +#: taiga/base/api/fields.py:823 #, python-format msgid "Time has wrong format. Use one of these formats instead: %s" msgstr "時間格式錯誤,請使用這些格式取代:%s" -#: taiga/base/api/fields.py:863 +#: taiga/base/api/fields.py:880 msgid "Enter a whole number." msgstr "輸入一個整數" -#: taiga/base/api/fields.py:864 taiga/base/api/fields.py:917 +#: taiga/base/api/fields.py:881 taiga/base/api/fields.py:934 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "確認此值小於等於 %(limit_value)s." -#: taiga/base/api/fields.py:865 taiga/base/api/fields.py:918 +#: taiga/base/api/fields.py:882 taiga/base/api/fields.py:935 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "確認此值大於等於 %(limit_value)s." -#: taiga/base/api/fields.py:895 +#: taiga/base/api/fields.py:912 #, python-format msgid "\"%s\" value must be a float." msgstr "\"%s\" 數值必須為一個浮點數" -#: taiga/base/api/fields.py:916 +#: taiga/base/api/fields.py:933 msgid "Enter a number." msgstr "輸入一組號碼" -#: taiga/base/api/fields.py:919 +#: taiga/base/api/fields.py:936 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "確認全部沒有多於 %s位數 " -#: taiga/base/api/fields.py:920 +#: taiga/base/api/fields.py:937 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "確認沒有多於 %s十進位數 " -#: taiga/base/api/fields.py:921 +#: taiga/base/api/fields.py:938 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "確認在小數點前沒有多於 %s位數 " -#: taiga/base/api/fields.py:988 +#: taiga/base/api/fields.py:1005 msgid "No file was submitted. Check the encoding type on the form." msgstr "無檔案送出,請 確認表格中的編碼 格式" -#: taiga/base/api/fields.py:989 +#: taiga/base/api/fields.py:1006 msgid "No file was submitted." msgstr "無檔案送出" -#: taiga/base/api/fields.py:990 +#: taiga/base/api/fields.py:1007 msgid "The submitted file is empty." msgstr "送出的檔案無內容" -#: taiga/base/api/fields.py:991 +#: taiga/base/api/fields.py:1008 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "確認檔案名稱最多有 %(max)d 字元 (它有 %(length)d)." -#: taiga/base/api/fields.py:992 +#: taiga/base/api/fields.py:1009 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "請上傳擋案或是勾選清除方格中二選一" -#: taiga/base/api/fields.py:1032 +#: taiga/base/api/fields.py:1049 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "上傳有效圖片,你所上傳的檔案非圖檔或已損壞" #: taiga/base/api/mixins.py:284 taiga/base/exceptions.py:211 -#: taiga/hooks/api.py:69 taiga/projects/api.py:396 taiga/projects/api.py:671 -#: taiga/projects/epics/api.py:213 taiga/projects/epics/api.py:292 -#: taiga/projects/issues/api.py:238 taiga/projects/mixins/ordering.py:59 -#: taiga/projects/tasks/api.py:261 taiga/projects/tasks/api.py:287 -#: taiga/projects/userstories/api.py:340 taiga/projects/userstories/api.py:392 -#: taiga/webhooks/api.py:71 +#: taiga/hooks/api.py:69 taiga/projects/api.py:409 taiga/projects/api.py:442 +#: taiga/projects/api.py:754 taiga/projects/epics/api.py:200 +#: taiga/projects/epics/api.py:284 taiga/projects/issues/api.py:224 +#: taiga/projects/mixins/ordering.py:59 taiga/projects/tasks/api.py:247 +#: taiga/projects/tasks/api.py:272 taiga/projects/userstories/api.py:323 +#: taiga/projects/userstories/api.py:375 taiga/webhooks/api.py:71 msgid "Blocked element" msgstr "" -#: taiga/base/api/pagination.py:214 +#: taiga/base/api/pagination.py:228 msgid "Page is not 'last', nor can it be converted to an int." msgstr "頁數不是最後,或者它無法轉成整數 " -#: taiga/base/api/pagination.py:218 +#: taiga/base/api/pagination.py:232 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "無效頁面I (%(page_number)s): %(message)s" -#: taiga/base/api/permissions.py:66 +#: taiga/base/api/permissions.py:65 msgid "Invalid permission definition." msgstr "無效的權限定義 " @@ -268,7 +268,7 @@ msgstr "找不到" msgid "Permission denied" msgstr "許可遭拒絕 " -#: taiga/base/api/views.py:477 +#: taiga/base/api/views.py:492 msgid "Server application error" msgstr "伺服器應用出錯" @@ -347,11 +347,11 @@ msgstr "前提出錯" msgid "No room left for more projects." msgstr "" -#: taiga/base/filters.py:81 taiga/base/filters.py:462 +#: taiga/base/filters.py:81 taiga/base/filters.py:463 msgid "Error in filter params types." msgstr "過濾參數類型出錯" -#: taiga/base/filters.py:135 taiga/base/filters.py:242 +#: taiga/base/filters.py:136 taiga/base/filters.py:243 #: taiga/projects/filters.py:64 msgid "'project' must be an integer value." msgstr "專案須為整數值" @@ -360,43 +360,43 @@ msgstr "專案須為整數值" msgid "Taiga" msgstr "Taiga" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Follow us on Twitter" msgstr "透過推特追踪" -#: taiga/base/templates/emails/base-body-html.jinja:406 +#: taiga/base/templates/emails/base-body-html.jinja:421 #: taiga/base/templates/emails/hero-body-html.jinja:380 #: taiga/base/templates/emails/updates-body-html.jinja:442 msgid "Twitter" msgstr "推特" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "Get the code on GitHub" msgstr "從GitHub取得原始碼" -#: taiga/base/templates/emails/base-body-html.jinja:407 +#: taiga/base/templates/emails/base-body-html.jinja:422 #: taiga/base/templates/emails/hero-body-html.jinja:381 #: taiga/base/templates/emails/updates-body-html.jinja:443 msgid "GitHub" msgstr "GitHub" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Visit our website" msgstr "造訪我們的網站" -#: taiga/base/templates/emails/base-body-html.jinja:408 +#: taiga/base/templates/emails/base-body-html.jinja:423 #: taiga/base/templates/emails/hero-body-html.jinja:382 #: taiga/base/templates/emails/updates-body-html.jinja:444 msgid "Taiga.io" msgstr "Taiga.io" -#: taiga/base/templates/emails/base-body-html.jinja:423 +#: taiga/base/templates/emails/base-body-html.jinja:438 #: taiga/base/templates/emails/hero-body-html.jinja:397 #: taiga/base/templates/emails/updates-body-html.jinja:459 #, python-format @@ -707,6 +707,7 @@ msgid "[%(project)s] %(error_subject)s" msgstr "[%(project)s] %(error_subject)s" #: taiga/export_import/templates/emails/import_error-body-html.jinja:4 +#: taiga/importers/templates/emails/importer_import_error-body-html.jinja:4 #, python-format msgid "" "\n" @@ -730,6 +731,7 @@ msgstr "" "

Taiga 團隊

" #: taiga/export_import/templates/emails/import_error-body-text.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-body-text.jinja:1 #, python-format msgid "" "\n" @@ -761,6 +763,7 @@ msgstr "" "Taiga團隊\n" #: taiga/export_import/templates/emails/import_error-subject.jinja:1 +#: taiga/importers/templates/emails/importer_import_error-subject.jinja:1 #, python-format msgid "[Taiga] %(error_subject)s" msgstr "[Taiga] %(error_subject)s" @@ -832,7 +835,7 @@ msgid "It contain invalid custom fields." msgstr "包括無效慣例欄位" #: taiga/export_import/validators/validators.py:245 -#: taiga/projects/validators.py:52 +#: taiga/projects/validators.py:54 msgid "Name duplicated for the project" msgstr "專案的名稱被複製了" @@ -843,13 +846,13 @@ msgstr "要求取得授權" #: taiga/external_apps/models.py:35 #: taiga/projects/custom_attributes/models.py:36 -#: taiga/projects/milestones/models.py:38 taiga/projects/models.py:145 -#: taiga/projects/models.py:512 taiga/projects/models.py:545 -#: taiga/projects/models.py:581 taiga/projects/models.py:603 -#: taiga/projects/models.py:637 taiga/projects/models.py:657 -#: taiga/projects/models.py:677 taiga/projects/models.py:709 -#: taiga/projects/models.py:729 taiga/users/admin.py:54 -#: taiga/users/models.py:292 taiga/webhooks/models.py:29 +#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:148 +#: taiga/projects/models.py:518 taiga/projects/models.py:551 +#: taiga/projects/models.py:587 taiga/projects/models.py:609 +#: taiga/projects/models.py:643 taiga/projects/models.py:663 +#: taiga/projects/models.py:683 taiga/projects/models.py:715 +#: taiga/projects/models.py:735 taiga/users/admin.py:54 +#: taiga/users/models.py:303 taiga/webhooks/models.py:29 msgid "name" msgstr "姓名" @@ -861,12 +864,12 @@ msgstr "網址圖標" msgid "web" msgstr "網頁" -#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:61 +#: taiga/external_apps/models.py:39 taiga/projects/attachments/models.py:62 #: taiga/projects/custom_attributes/models.py:37 -#: taiga/projects/epics/models.py:55 +#: taiga/projects/epics/models.py:56 #: taiga/projects/history/templatetags/functions.py:25 -#: taiga/projects/issues/models.py:60 taiga/projects/models.py:149 -#: taiga/projects/models.py:733 taiga/projects/tasks/models.py:62 +#: taiga/projects/issues/models.py:60 taiga/projects/models.py:152 +#: taiga/projects/models.py:739 taiga/projects/tasks/models.py:62 #: taiga/projects/userstories/models.py:95 msgid "description" msgstr "描述" @@ -875,36 +878,34 @@ msgstr "描述" msgid "Next url" msgstr "下一個網址" -#: taiga/external_apps/models.py:43 -msgid "secret key for ciphering the application tokens" -msgstr "應用程式的密碼字符數列" - -#: taiga/external_apps/models.py:57 taiga/projects/likes/models.py:31 -#: taiga/projects/notifications/models.py:87 taiga/projects/votes/models.py:52 +#: taiga/external_apps/models.py:55 taiga/projects/contact/models.py:26 +#: taiga/projects/likes/models.py:31 taiga/projects/notifications/models.py:87 +#: taiga/projects/votes/models.py:52 msgid "user" msgstr "使用者" -#: taiga/external_apps/models.py:61 +#: taiga/external_apps/models.py:59 msgid "application" msgstr "應用程式" -#: taiga/feedback/models.py:25 taiga/users/models.py:137 +#: taiga/feedback/models.py:25 taiga/users/models.py:140 msgid "full name" msgstr "全名" -#: taiga/feedback/models.py:27 taiga/users/models.py:132 +#: taiga/feedback/models.py:27 taiga/users/models.py:135 msgid "email address" msgstr "電子郵件" -#: taiga/feedback/models.py:29 +#: taiga/feedback/models.py:29 taiga/projects/contact/models.py:31 msgid "comment" msgstr "評論" #: taiga/feedback/models.py:31 taiga/projects/attachments/models.py:48 +#: taiga/projects/contact/models.py:34 #: taiga/projects/custom_attributes/models.py:46 -#: taiga/projects/epics/models.py:48 taiga/projects/issues/models.py:52 -#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:49 -#: taiga/projects/models.py:156 taiga/projects/models.py:737 +#: taiga/projects/epics/models.py:49 taiga/projects/issues/models.py:52 +#: taiga/projects/likes/models.py:33 taiga/projects/milestones/models.py:48 +#: taiga/projects/models.py:159 taiga/projects/models.py:743 #: taiga/projects/notifications/models.py:89 taiga/projects/tasks/models.py:48 #: taiga/projects/userstories/models.py:87 taiga/projects/votes/models.py:54 #: taiga/projects/wiki/models.py:44 taiga/userstorage/models.py:29 @@ -973,9 +974,9 @@ msgstr "" msgid "The payload is not a valid json" msgstr "載荷為無效json" -#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:152 -#: taiga/projects/issues/api.py:138 taiga/projects/tasks/api.py:200 -#: taiga/projects/userstories/api.py:273 +#: taiga/hooks/api.py:63 taiga/projects/epics/api.py:154 +#: taiga/projects/issues/api.py:139 taiga/projects/tasks/api.py:201 +#: taiga/projects/userstories/api.py:277 msgid "The project doesn't exist" msgstr "專案不存在" @@ -1063,6 +1064,221 @@ msgstr "參考元素不存在" msgid "The status doesn't exist" msgstr "狀態不存在" +#: taiga/importers/asana/api.py:43 taiga/importers/asana/api.py:85 +#: taiga/importers/github/api.py:44 taiga/importers/github/api.py:74 +#: taiga/importers/jira/api.py:57 taiga/importers/jira/api.py:103 +#: taiga/importers/pivotal/api.py:43 taiga/importers/pivotal/api.py:80 +#: taiga/importers/trello/api.py:46 taiga/importers/trello/api.py:83 +msgid "The project param is needed" +msgstr "" + +#: taiga/importers/asana/api.py:50 taiga/importers/asana/api.py:73 +#: taiga/importers/asana/api.py:139 +msgid "Invalid Asana API request" +msgstr "" + +#: taiga/importers/asana/api.py:52 taiga/importers/asana/api.py:75 +#: taiga/importers/asana/api.py:141 +msgid "Failed to make the request to Asana API" +msgstr "" + +#: taiga/importers/asana/api.py:129 taiga/importers/github/api.py:123 +msgid "Code param needed" +msgstr "" + +#: taiga/importers/asana/tasks.py:42 taiga/importers/asana/tasks.py:43 +msgid "Error importing Asana project" +msgstr "" + +#: taiga/importers/github/api.py:135 +msgid "Invalid auth data" +msgstr "" + +#: taiga/importers/github/api.py:137 +msgid "Third party service failing" +msgstr "" + +#: taiga/importers/github/tasks.py:42 taiga/importers/github/tasks.py:43 +msgid "Error importing GitHub project" +msgstr "" + +#: taiga/importers/jira/api.py:59 taiga/importers/jira/api.py:86 +#: taiga/importers/jira/api.py:106 taiga/importers/jira/api.py:179 +msgid "The url param is needed" +msgstr "" + +#: taiga/importers/jira/api.py:155 +msgid "Invalid project_type {}" +msgstr "" + +#: taiga/importers/jira/api.py:225 taiga/importers/pivotal/api.py:138 +#: taiga/importers/trello/api.py:143 +msgid "Invalid or expired auth token" +msgstr "" + +#: taiga/importers/jira/tasks.py:48 taiga/importers/jira/tasks.py:49 +msgid "Error importing Jira project" +msgstr "" + +#: taiga/importers/pivotal/tasks.py:42 taiga/importers/pivotal/tasks.py:43 +msgid "Error importing PivotalTracker project" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Asana Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Asana project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Asana project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/asana_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Asana project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

GitHub Project imported

\n" +"

Hello %(user)s,

\n" +"

Your GitHub project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your GitHub project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/github_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your GitHub project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Jira Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Jira project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Jira project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/jira_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Jira project has been imported" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-html.jinja:4 +#, python-format +msgid "" +"\n" +"

Trello Project imported

\n" +"

Hello %(user)s,

\n" +"

Your Trello project has been correctly imported.

\n" +" Go to %(project)s\n" +"

The Taiga Team

\n" +" " +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"Hello %(user)s,\n" +"\n" +"Your Trello project has been correctly imported.\n" +"\n" +"You can see the project %(project)s here:\n" +"\n" +"%(url)s\n" +"\n" +"---\n" +"The Taiga Team\n" +msgstr "" + +#: taiga/importers/templates/emails/trello_import_success-subject.jinja:1 +#, python-format +msgid "[%(project)s] Your Trello project has been imported" +msgstr "" + +#: taiga/importers/trello/importer.py:78 +#, python-format +msgid "Invalid Request: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:80 taiga/importers/trello/importer.py:82 +#, python-format +msgid "Unauthorized: %s at %s" +msgstr "" + +#: taiga/importers/trello/importer.py:84 taiga/importers/trello/importer.py:86 +#, python-format +msgid "Resource Unavailable: %s at %s" +msgstr "" + +#: taiga/importers/trello/tasks.py:42 taiga/importers/trello/tasks.py:43 +msgid "Error importing Trello project" +msgstr "" + #: taiga/permissions/choices.py:23 taiga/permissions/choices.py:34 msgid "View project" msgstr "檢視專案" @@ -1231,89 +1447,89 @@ msgstr "管理員角色" msgid "Privacity" msgstr "" -#: taiga/projects/admin.py:112 +#: taiga/projects/admin.py:111 msgid "Modules" msgstr "" -#: taiga/projects/admin.py:120 +#: taiga/projects/admin.py:119 msgid "Default values" msgstr "" -#: taiga/projects/admin.py:126 +#: taiga/projects/admin.py:125 msgid "Activity" msgstr "" -#: taiga/projects/admin.py:131 +#: taiga/projects/admin.py:130 msgid "Fans" msgstr "" -#: taiga/projects/admin.py:145 taiga/projects/attachments/models.py:39 -#: taiga/projects/epics/models.py:39 taiga/projects/issues/models.py:37 -#: taiga/projects/milestones/models.py:43 taiga/projects/models.py:161 +#: taiga/projects/admin.py:144 taiga/projects/attachments/models.py:39 +#: taiga/projects/epics/models.py:40 taiga/projects/issues/models.py:37 +#: taiga/projects/milestones/models.py:42 taiga/projects/models.py:164 #: taiga/projects/notifications/models.py:62 taiga/projects/tasks/models.py:39 #: taiga/projects/userstories/models.py:69 taiga/projects/wiki/models.py:40 #: taiga/users/admin.py:69 taiga/userstorage/models.py:27 msgid "owner" msgstr "所有者" -#: taiga/projects/admin.py:200 +#: taiga/projects/admin.py:192 #, python-brace-format msgid "{count} successfully made public." msgstr "" -#: taiga/projects/admin.py:201 +#: taiga/projects/admin.py:193 msgid "Make public" msgstr "" -#: taiga/projects/admin.py:215 +#: taiga/projects/admin.py:207 #, python-brace-format msgid "{count} successfully made private." msgstr "" -#: taiga/projects/admin.py:216 +#: taiga/projects/admin.py:208 msgid "Make private" msgstr "" -#: taiga/projects/admin.py:246 +#: taiga/projects/admin.py:238 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" -#: taiga/projects/api.py:150 taiga/users/api.py:237 +#: taiga/projects/api.py:160 taiga/users/api.py:244 msgid "Incomplete arguments" msgstr "不完整參數" -#: taiga/projects/api.py:154 taiga/users/api.py:242 +#: taiga/projects/api.py:164 taiga/users/api.py:249 msgid "Invalid image format" msgstr "無效的圖片檔案" -#: taiga/projects/api.py:215 +#: taiga/projects/api.py:225 msgid "Not valid template name" msgstr "非有效樣板名稱 " -#: taiga/projects/api.py:218 +#: taiga/projects/api.py:228 msgid "Not valid template description" msgstr "無效樣板描述" -#: taiga/projects/api.py:344 +#: taiga/projects/api.py:354 msgid "Invalid user id" msgstr "" -#: taiga/projects/api.py:350 +#: taiga/projects/api.py:360 msgid "The user doesn't exist" msgstr "" -#: taiga/projects/api.py:354 +#: taiga/projects/api.py:364 msgid "The user must be already a project member" msgstr "" -#: taiga/projects/api.py:701 +#: taiga/projects/api.py:785 msgid "" "The project must have an owner and at least one of the users must be an " "active admin" msgstr "" -#: taiga/projects/api.py:735 +#: taiga/projects/api.py:819 msgid "You don't have permisions to see that." msgstr "您無觀看權限" @@ -1329,18 +1545,18 @@ msgstr "" msgid "Project ID not matches between object and project" msgstr "專案ID不符合物件與專案" -#: taiga/projects/attachments/models.py:41 +#: taiga/projects/attachments/models.py:41 taiga/projects/contact/models.py:29 #: taiga/projects/custom_attributes/models.py:43 -#: taiga/projects/epics/models.py:37 taiga/projects/issues/models.py:50 -#: taiga/projects/milestones/models.py:45 taiga/projects/models.py:500 -#: taiga/projects/models.py:522 taiga/projects/models.py:559 -#: taiga/projects/models.py:587 taiga/projects/models.py:613 -#: taiga/projects/models.py:643 taiga/projects/models.py:663 -#: taiga/projects/models.py:687 taiga/projects/models.py:715 +#: taiga/projects/epics/models.py:38 taiga/projects/issues/models.py:50 +#: taiga/projects/milestones/models.py:44 taiga/projects/models.py:506 +#: taiga/projects/models.py:528 taiga/projects/models.py:565 +#: taiga/projects/models.py:593 taiga/projects/models.py:619 +#: taiga/projects/models.py:649 taiga/projects/models.py:669 +#: taiga/projects/models.py:693 taiga/projects/models.py:721 #: taiga/projects/notifications/models.py:74 #: taiga/projects/notifications/models.py:91 taiga/projects/tasks/models.py:43 #: taiga/projects/userstories/models.py:67 taiga/projects/wiki/models.py:34 -#: taiga/projects/wiki/models.py:72 taiga/users/models.py:303 +#: taiga/projects/wiki/models.py:72 taiga/users/models.py:314 msgid "project" msgstr "專案" @@ -1354,9 +1570,9 @@ msgstr "物件ID" #: taiga/projects/attachments/models.py:51 #: taiga/projects/custom_attributes/models.py:48 -#: taiga/projects/epics/models.py:51 taiga/projects/issues/models.py:55 -#: taiga/projects/milestones/models.py:52 taiga/projects/models.py:159 -#: taiga/projects/models.py:740 taiga/projects/tasks/models.py:51 +#: taiga/projects/epics/models.py:52 taiga/projects/issues/models.py:55 +#: taiga/projects/milestones/models.py:51 taiga/projects/models.py:162 +#: taiga/projects/models.py:746 taiga/projects/tasks/models.py:51 #: taiga/projects/userstories/models.py:90 taiga/projects/wiki/models.py:47 #: taiga/userstorage/models.py:31 msgid "modified date" @@ -1374,14 +1590,18 @@ msgstr "sha1" msgid "is deprecated" msgstr "棄用" -#: taiga/projects/attachments/models.py:62 +#: taiga/projects/attachments/models.py:61 +msgid "from comment" +msgstr "" + +#: taiga/projects/attachments/models.py:63 #: taiga/projects/custom_attributes/models.py:41 -#: taiga/projects/epics/models.py:101 taiga/projects/milestones/models.py:58 -#: taiga/projects/models.py:516 taiga/projects/models.py:549 -#: taiga/projects/models.py:583 taiga/projects/models.py:607 -#: taiga/projects/models.py:639 taiga/projects/models.py:659 -#: taiga/projects/models.py:681 taiga/projects/models.py:711 -#: taiga/projects/wiki/models.py:77 taiga/users/models.py:298 +#: taiga/projects/epics/models.py:104 taiga/projects/milestones/models.py:57 +#: taiga/projects/models.py:522 taiga/projects/models.py:555 +#: taiga/projects/models.py:589 taiga/projects/models.py:613 +#: taiga/projects/models.py:645 taiga/projects/models.py:665 +#: taiga/projects/models.py:687 taiga/projects/models.py:717 +#: taiga/projects/wiki/models.py:77 taiga/users/models.py:309 msgid "order" msgstr "次序" @@ -1417,19 +1637,71 @@ msgstr "" msgid "This project is blocked while it's deleted" msgstr "" -#: taiga/projects/custom_attributes/choices.py:28 +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:10 +#, python-format +msgid "" +"\n" +" %(full_name)s has " +"written to %(project_name)s\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-html.jinja:20 +#, python-format +msgid "" +"\n" +" You are receiving this message because you are listed as " +"administrator of the project titled %(project_name)s. If you don't want " +"members of the Taiga community contacting your project, please update your project settings to prevent " +"such contacts. Regular communications amongst members of the project will " +"not be affected.\n" +" " +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:1 +#, python-format +msgid "" +"\n" +"%(full_name)s has written to %(project_name)s\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-body-text.jinja:7 +#, python-format +msgid "" +"\n" +"You are receiving this message because you are listed as administrator of " +"the project titled %(project_name)s. If you don't want members of the Taiga " +"community contacting your project, please update your project settings in " +"%(project_settings_url)s to prevent such contacts. Regular communications " +"amongst members of the project will not be affected.\n" +msgstr "" + +#: taiga/projects/contact/templates/emails/contact_notification-subject.jinja:1 +#, python-format +msgid "" +"\n" +"[Taiga] %(full_name)s has sent a message to the project %(project_name)s\n" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:29 msgid "Text" msgstr "單行文字" -#: taiga/projects/custom_attributes/choices.py:29 +#: taiga/projects/custom_attributes/choices.py:30 msgid "Multi-Line Text" msgstr "多行列文字" -#: taiga/projects/custom_attributes/choices.py:30 +#: taiga/projects/custom_attributes/choices.py:31 +msgid "Rich text" +msgstr "" + +#: taiga/projects/custom_attributes/choices.py:32 msgid "Date" msgstr "日期" -#: taiga/projects/custom_attributes/choices.py:31 +#: taiga/projects/custom_attributes/choices.py:33 msgid "Url" msgstr "" @@ -1463,54 +1735,59 @@ msgstr "問題 " msgid "Already exists one with the same name." msgstr "已存在相同姓名" -#: taiga/projects/epics/api.py:92 +#: taiga/projects/epics/api.py:94 msgid "You don't have permissions to set this status to this epic." msgstr "" -#: taiga/projects/epics/models.py:35 taiga/projects/issues/models.py:35 +#: taiga/projects/epics/models.py:36 taiga/projects/issues/models.py:35 #: taiga/projects/tasks/models.py:37 taiga/projects/userstories/models.py:62 msgid "ref" msgstr "ref" -#: taiga/projects/epics/models.py:42 taiga/projects/issues/models.py:39 +#: taiga/projects/epics/models.py:43 taiga/projects/issues/models.py:39 #: taiga/projects/tasks/models.py:41 taiga/projects/userstories/models.py:72 msgid "status" msgstr "狀態" -#: taiga/projects/epics/models.py:45 +#: taiga/projects/epics/models.py:46 msgid "epics order" msgstr "" -#: taiga/projects/epics/models.py:54 taiga/projects/issues/models.py:59 +#: taiga/projects/epics/models.py:55 taiga/projects/issues/models.py:59 #: taiga/projects/tasks/models.py:55 taiga/projects/userstories/models.py:94 msgid "subject" msgstr "主旨" -#: taiga/projects/epics/models.py:58 taiga/projects/models.py:520 -#: taiga/projects/models.py:555 taiga/projects/models.py:611 -#: taiga/projects/models.py:641 taiga/projects/models.py:661 -#: taiga/projects/models.py:685 taiga/projects/models.py:713 -#: taiga/users/models.py:139 +#: taiga/projects/epics/models.py:59 taiga/projects/models.py:526 +#: taiga/projects/models.py:561 taiga/projects/models.py:617 +#: taiga/projects/models.py:647 taiga/projects/models.py:667 +#: taiga/projects/models.py:691 taiga/projects/models.py:719 +#: taiga/users/models.py:142 msgid "color" msgstr "顏色" -#: taiga/projects/epics/models.py:61 taiga/projects/issues/models.py:63 +#: taiga/projects/epics/models.py:62 taiga/projects/issues/models.py:63 #: taiga/projects/tasks/models.py:65 taiga/projects/userstories/models.py:98 msgid "assigned to" msgstr "指派給" -#: taiga/projects/epics/models.py:63 taiga/projects/userstories/models.py:100 +#: taiga/projects/epics/models.py:64 taiga/projects/userstories/models.py:100 msgid "is client requirement" msgstr "客戶要求" -#: taiga/projects/epics/models.py:65 taiga/projects/userstories/models.py:102 +#: taiga/projects/epics/models.py:66 taiga/projects/userstories/models.py:102 msgid "is team requirement" msgstr "團隊要求" -#: taiga/projects/epics/models.py:69 +#: taiga/projects/epics/models.py:70 msgid "user stories" msgstr "" +#: taiga/projects/epics/models.py:72 taiga/projects/issues/models.py:66 +#: taiga/projects/tasks/models.py:70 taiga/projects/userstories/models.py:109 +msgid "external reference" +msgstr "外部參考" + #: taiga/projects/epics/validators.py:37 msgid "There's no epic with that id" msgstr "" @@ -1543,102 +1820,102 @@ msgstr "創建" msgid "Delete" msgstr "刪除 " -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:23 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:25 #, python-format msgid "%(role)s role points" msgstr "%(role)s 角色點數" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:26 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:131 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:134 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:157 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:194 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:28 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:133 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:136 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:159 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:213 msgid "from" msgstr "來自" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:32 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:142 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:145 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:163 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:180 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:200 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:144 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:147 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:165 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:190 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:219 msgid "to" msgstr "給" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:44 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:46 msgid "Added new attachment" msgstr "新增附件" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:62 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:64 msgid "Updated attachment" msgstr "更新附件" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:68 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 msgid "deprecated" msgstr "棄用" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:70 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:72 msgid "not deprecated" msgstr "不棄用" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:86 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:88 msgid "Deleted attachment" msgstr "刪除附件" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:105 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:107 msgid "added" msgstr "新增" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:110 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:112 msgid "removed" msgstr "移除 " -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:135 -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:146 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:137 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:148 #: taiga/projects/services/stats.py:55 taiga/projects/services/stats.py:56 msgid "Unassigned" msgstr "無指定" -#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:212 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:87 +#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:232 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:89 msgid "-deleted-" msgstr "-刪除-" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "to:" msgstr "給:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:21 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:23 msgid "from:" msgstr "from:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:27 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:29 msgid "Added" msgstr "新增的" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:34 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:36 msgid "Changed" msgstr "改變的" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:41 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:43 msgid "Deleted" msgstr "刪除的:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:55 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:57 msgid "added:" msgstr "新增的:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:58 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:60 msgid "removed:" msgstr "移除的:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:63 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:80 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:65 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:82 msgid "From:" msgstr "來自:" -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:64 -#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:81 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:66 +#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:83 msgid "To:" msgstr "給:" @@ -1656,23 +1933,23 @@ msgstr "封鎖筆記" msgid "sprint" msgstr "衝刺任務" -#: taiga/projects/issues/api.py:156 +#: taiga/projects/issues/api.py:157 msgid "You don't have permissions to set this sprint to this issue." msgstr "您無權限設定此問題的衝刺任務" -#: taiga/projects/issues/api.py:160 +#: taiga/projects/issues/api.py:161 msgid "You don't have permissions to set this status to this issue." msgstr "您無權限設定此問題的狀態" -#: taiga/projects/issues/api.py:164 +#: taiga/projects/issues/api.py:165 msgid "You don't have permissions to set this severity to this issue." msgstr "您無權限設定此問題的嚴重性" -#: taiga/projects/issues/api.py:168 +#: taiga/projects/issues/api.py:169 msgid "You don't have permissions to set this priority to this issue." msgstr "您無權限設定此問題的優先性" -#: taiga/projects/issues/api.py:172 +#: taiga/projects/issues/api.py:173 msgid "You don't have permissions to set this type to this issue." msgstr "您無權限設定此問題的類型" @@ -1693,11 +1970,6 @@ msgstr "里程碑" msgid "finished date" msgstr "完成日期" -#: taiga/projects/issues/models.py:66 taiga/projects/tasks/models.py:70 -#: taiga/projects/userstories/models.py:109 -msgid "external reference" -msgstr "外部參考" - #: taiga/projects/likes/models.py:36 msgid "Like" msgstr "喜歡" @@ -1706,33 +1978,33 @@ msgstr "喜歡" msgid "Likes" msgstr "喜歡" -#: taiga/projects/milestones/models.py:41 taiga/projects/models.py:147 -#: taiga/projects/models.py:514 taiga/projects/models.py:547 -#: taiga/projects/models.py:605 taiga/projects/models.py:679 -#: taiga/projects/models.py:731 taiga/projects/wiki/models.py:36 -#: taiga/users/admin.py:58 taiga/users/models.py:294 +#: taiga/projects/milestones/models.py:40 taiga/projects/models.py:150 +#: taiga/projects/models.py:520 taiga/projects/models.py:553 +#: taiga/projects/models.py:611 taiga/projects/models.py:685 +#: taiga/projects/models.py:737 taiga/projects/wiki/models.py:36 +#: taiga/users/admin.py:58 taiga/users/models.py:305 msgid "slug" msgstr "代稱" -#: taiga/projects/milestones/models.py:46 +#: taiga/projects/milestones/models.py:45 msgid "estimated start date" msgstr "预計開始日期" -#: taiga/projects/milestones/models.py:47 +#: taiga/projects/milestones/models.py:46 msgid "estimated finish date" msgstr "預計完成日期" -#: taiga/projects/milestones/models.py:54 taiga/projects/models.py:518 -#: taiga/projects/models.py:551 taiga/projects/models.py:609 -#: taiga/projects/models.py:683 +#: taiga/projects/milestones/models.py:53 taiga/projects/models.py:524 +#: taiga/projects/models.py:557 taiga/projects/models.py:615 +#: taiga/projects/models.py:689 msgid "is closed" msgstr "被關閉" -#: taiga/projects/milestones/models.py:56 +#: taiga/projects/milestones/models.py:55 msgid "disponibility" msgstr "disponibility" -#: taiga/projects/milestones/models.py:80 +#: taiga/projects/milestones/models.py:79 msgid "The estimated start must be previous to the estimated finish." msgstr "預估開始必須在預估結束之前" @@ -1744,6 +2016,14 @@ msgstr "" msgid "is blocked" msgstr "已封鎖" +#: taiga/projects/mixins/by_ref.py:32 +msgid "ref param is needed" +msgstr "" + +#: taiga/projects/mixins/by_ref.py:35 +msgid "project or project__slug param is needed" +msgstr "" + #: taiga/projects/mixins/ordering.py:49 #, python-brace-format msgid "'{param}' parameter is mandatory" @@ -1753,236 +2033,260 @@ msgstr "'{param}' 參數為必要" msgid "'project' parameter is mandatory" msgstr "'project'參數為必要" -#: taiga/projects/models.py:76 +#: taiga/projects/mixins/validators.py:19 +msgid "The user must be a project member." +msgstr "" + +#: taiga/projects/models.py:79 msgid "email" msgstr "電子郵件" -#: taiga/projects/models.py:78 +#: taiga/projects/models.py:81 msgid "create at" msgstr "創建於" -#: taiga/projects/models.py:80 taiga/users/models.py:154 +#: taiga/projects/models.py:83 taiga/users/models.py:157 msgid "token" msgstr "代號" -#: taiga/projects/models.py:86 +#: taiga/projects/models.py:89 msgid "invitation extra text" msgstr "額外文案邀請" -#: taiga/projects/models.py:89 taiga/projects/models.py:735 +#: taiga/projects/models.py:92 taiga/projects/models.py:741 msgid "user order" msgstr "使用者次序" -#: taiga/projects/models.py:105 +#: taiga/projects/models.py:108 msgid "The user is already member of the project" msgstr "使用者已是專案成員" -#: taiga/projects/models.py:112 +#: taiga/projects/models.py:115 msgid "default epic status" msgstr "" -#: taiga/projects/models.py:116 +#: taiga/projects/models.py:119 msgid "default US status" msgstr "預設使用者故事狀態" -#: taiga/projects/models.py:119 +#: taiga/projects/models.py:122 msgid "default points" msgstr "預設點數" -#: taiga/projects/models.py:123 +#: taiga/projects/models.py:126 msgid "default task status" msgstr "預設任務狀態" -#: taiga/projects/models.py:126 +#: taiga/projects/models.py:129 msgid "default priority" msgstr "預設優先性" -#: taiga/projects/models.py:129 +#: taiga/projects/models.py:132 msgid "default severity" msgstr "預設嚴重性" -#: taiga/projects/models.py:133 +#: taiga/projects/models.py:136 msgid "default issue status" msgstr "預設問題狀態" -#: taiga/projects/models.py:137 +#: taiga/projects/models.py:140 msgid "default issue type" msgstr "預設議題類型" -#: taiga/projects/models.py:153 +#: taiga/projects/models.py:156 msgid "logo" msgstr "圖標" -#: taiga/projects/models.py:163 +#: taiga/projects/models.py:166 msgid "members" msgstr "成員" -#: taiga/projects/models.py:166 +#: taiga/projects/models.py:169 msgid "total of milestones" msgstr "全部里程碑" -#: taiga/projects/models.py:167 +#: taiga/projects/models.py:170 msgid "total story points" msgstr "全部故事點數" -#: taiga/projects/models.py:170 taiga/projects/models.py:746 +#: taiga/projects/models.py:172 taiga/projects/models.py:751 +msgid "active contact" +msgstr "" + +#: taiga/projects/models.py:174 taiga/projects/models.py:753 msgid "active epics panel" msgstr "" -#: taiga/projects/models.py:172 taiga/projects/models.py:748 +#: taiga/projects/models.py:176 taiga/projects/models.py:755 msgid "active backlog panel" msgstr "活躍的待辦任務優先表面板" -#: taiga/projects/models.py:174 taiga/projects/models.py:750 +#: taiga/projects/models.py:178 taiga/projects/models.py:757 msgid "active kanban panel" msgstr "活躍的看板式面板" -#: taiga/projects/models.py:176 taiga/projects/models.py:752 +#: taiga/projects/models.py:180 taiga/projects/models.py:759 msgid "active wiki panel" msgstr "活躍的維基面板" -#: taiga/projects/models.py:178 taiga/projects/models.py:754 +#: taiga/projects/models.py:182 taiga/projects/models.py:761 msgid "active issues panel" msgstr "活躍的問題面板" -#: taiga/projects/models.py:181 taiga/projects/models.py:757 +#: taiga/projects/models.py:185 taiga/projects/models.py:768 msgid "videoconference system" msgstr "視訊會議系統" -#: taiga/projects/models.py:183 taiga/projects/models.py:759 +#: taiga/projects/models.py:187 taiga/projects/models.py:770 msgid "videoconference extra data" msgstr "視訊會議額外資料" -#: taiga/projects/models.py:189 +#: taiga/projects/models.py:193 msgid "creation template" msgstr "創建模版" -#: taiga/projects/models.py:192 taiga/users/admin.py:62 +#: taiga/projects/models.py:196 taiga/users/admin.py:62 msgid "is private" msgstr "私密" -#: taiga/projects/models.py:194 +#: taiga/projects/models.py:198 msgid "anonymous permissions" msgstr "匿名權限" -#: taiga/projects/models.py:196 +#: taiga/projects/models.py:200 msgid "user permissions" msgstr "使用者權限" -#: taiga/projects/models.py:199 +#: taiga/projects/models.py:203 msgid "is featured" msgstr " 受矚目的" -#: taiga/projects/models.py:202 +#: taiga/projects/models.py:206 taiga/projects/models.py:763 msgid "is looking for people" msgstr "正在找人" -#: taiga/projects/models.py:204 -msgid "loking for people note" -msgstr "" - -#: taiga/projects/models.py:218 -msgid "project transfer token" +#: taiga/projects/models.py:208 taiga/projects/models.py:765 +msgid "looking for people note" msgstr "" #: taiga/projects/models.py:222 +msgid "project transfer token" +msgstr "" + +#: taiga/projects/models.py:226 msgid "blocked code" msgstr "" -#: taiga/projects/models.py:226 taiga/projects/notifications/models.py:66 +#: taiga/projects/models.py:229 taiga/projects/notifications/models.py:66 msgid "updated date time" msgstr "更新日期時間" -#: taiga/projects/models.py:229 taiga/projects/models.py:241 +#: taiga/projects/models.py:232 taiga/projects/models.py:244 #: taiga/projects/votes/models.py:30 msgid "count" msgstr "數量" -#: taiga/projects/models.py:232 +#: taiga/projects/models.py:235 msgid "fans last week" msgstr "上週粉絲" -#: taiga/projects/models.py:235 +#: taiga/projects/models.py:238 msgid "fans last month" msgstr "上個月粉絲" -#: taiga/projects/models.py:238 +#: taiga/projects/models.py:241 msgid "fans last year" msgstr "去年粉絲" -#: taiga/projects/models.py:244 +#: taiga/projects/models.py:248 msgid "activity last week" msgstr "上週活躍成員" -#: taiga/projects/models.py:247 +#: taiga/projects/models.py:252 msgid "activity last month" msgstr "上月活躍成員" -#: taiga/projects/models.py:250 +#: taiga/projects/models.py:256 msgid "activity last year" msgstr "去年活躍成員" -#: taiga/projects/models.py:501 +#: taiga/projects/models.py:507 msgid "modules config" msgstr "模組設定" -#: taiga/projects/models.py:553 +#: taiga/projects/models.py:559 msgid "is archived" msgstr "已歸檔" -#: taiga/projects/models.py:557 +#: taiga/projects/models.py:563 msgid "work in progress limit" msgstr "工作進度限制" -#: taiga/projects/models.py:585 taiga/userstorage/models.py:33 +#: taiga/projects/models.py:591 taiga/userstorage/models.py:33 msgid "value" msgstr "價值" -#: taiga/projects/models.py:743 +#: taiga/projects/models.py:749 msgid "default owner's role" msgstr "預設所有者角色" -#: taiga/projects/models.py:761 +#: taiga/projects/models.py:772 msgid "default options" msgstr "預設選項" -#: taiga/projects/models.py:762 +#: taiga/projects/models.py:773 msgid "epic statuses" msgstr "" -#: taiga/projects/models.py:763 +#: taiga/projects/models.py:774 msgid "us statuses" msgstr "我們狀況" -#: taiga/projects/models.py:764 taiga/projects/userstories/models.py:44 +#: taiga/projects/models.py:775 taiga/projects/userstories/models.py:44 #: taiga/projects/userstories/models.py:77 msgid "points" msgstr "點數" -#: taiga/projects/models.py:765 +#: taiga/projects/models.py:776 msgid "task statuses" msgstr "任務狀況" -#: taiga/projects/models.py:766 +#: taiga/projects/models.py:777 msgid "issue statuses" msgstr "問題狀況" -#: taiga/projects/models.py:767 +#: taiga/projects/models.py:778 msgid "issue types" msgstr "問題類型" -#: taiga/projects/models.py:768 +#: taiga/projects/models.py:779 msgid "priorities" msgstr "優先性" -#: taiga/projects/models.py:769 +#: taiga/projects/models.py:780 msgid "severities" msgstr "嚴重性" -#: taiga/projects/models.py:770 +#: taiga/projects/models.py:781 msgid "roles" msgstr "角色" +#: taiga/projects/models.py:782 +msgid "epic custom attributes" +msgstr "" + +#: taiga/projects/models.py:783 +msgid "us custom attributes" +msgstr "" + +#: taiga/projects/models.py:784 +msgid "task custom attributes" +msgstr "" + +#: taiga/projects/models.py:785 +msgid "issue custom attributes" +msgstr "" + #: taiga/projects/notifications/choices.py:30 msgid "Involved" msgstr "相關涉入者" @@ -2017,7 +2321,7 @@ msgstr "已觀注" msgid "Notify exists for specified user and project" msgstr "通知特定使用者與專案退出" -#: taiga/projects/notifications/services.py:426 +#: taiga/projects/notifications/services.py:436 msgid "Invalid value for notify level" msgstr "通知水平的無效值" @@ -2860,36 +3164,40 @@ msgid "" "You can't leave the project if you are the owner or there are no more admins" msgstr "" -#: taiga/projects/services/members.py:118 +#: taiga/projects/services/members.py:133 msgid "Project without owner" msgstr "" -#: taiga/projects/services/members.py:123 +#: taiga/projects/services/members.py:138 msgid "You have reached your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/members.py:127 +#: taiga/projects/services/members.py:142 msgid "You have reached your current limit of memberships for public projects" msgstr "" -#: taiga/projects/services/projects.py:94 -#: taiga/projects/services/projects.py:134 taiga/users/services.py:589 +#: taiga/projects/services/members.py:148 +msgid "You have reached the current limit of pending memberships" +msgstr "" + +#: taiga/projects/services/projects.py:101 +#: taiga/projects/services/projects.py:141 taiga/users/services.py:589 msgid "You can't have more private projects" msgstr "" -#: taiga/projects/services/projects.py:98 -#: taiga/projects/services/projects.py:138 taiga/users/services.py:592 +#: taiga/projects/services/projects.py:105 +#: taiga/projects/services/projects.py:145 taiga/users/services.py:592 msgid "" "This project reaches your current limit of memberships for private projects" msgstr "" -#: taiga/projects/services/projects.py:102 -#: taiga/projects/services/projects.py:142 taiga/users/services.py:596 +#: taiga/projects/services/projects.py:109 +#: taiga/projects/services/projects.py:149 taiga/users/services.py:596 msgid "You can't have more public projects" msgstr "" -#: taiga/projects/services/projects.py:106 -#: taiga/projects/services/projects.py:146 taiga/users/services.py:599 +#: taiga/projects/services/projects.py:113 +#: taiga/projects/services/projects.py:153 taiga/users/services.py:599 msgid "" "This project reaches your current limit of memberships for public projects" msgstr "" @@ -2904,8 +3212,8 @@ msgstr "專案結束" #: taiga/projects/services/transfer.py:62 #: taiga/projects/services/transfer.py:69 -#: taiga/projects/services/transfer.py:72 taiga/users/api.py:186 -#: taiga/users/api.py:191 +#: taiga/projects/services/transfer.py:72 taiga/users/api.py:193 +#: taiga/users/api.py:198 msgid "Token is invalid" msgstr "代號無效" @@ -2955,15 +3263,15 @@ msgstr "" msgid "The tag doesn't exist." msgstr "" -#: taiga/projects/tasks/api.py:97 taiga/projects/tasks/api.py:106 +#: taiga/projects/tasks/api.py:98 taiga/projects/tasks/api.py:107 msgid "You don't have permissions to set this sprint to this task." msgstr "無權限更動此任務下的衝刺任務" -#: taiga/projects/tasks/api.py:100 +#: taiga/projects/tasks/api.py:101 msgid "You don't have permissions to set this user story to this task." msgstr "無權限更動此務下的使用者故事" -#: taiga/projects/tasks/api.py:103 +#: taiga/projects/tasks/api.py:104 msgid "You don't have permissions to set this status to this task." msgstr "無權限更動此任務下的狀態" @@ -2979,31 +3287,31 @@ msgstr "任務板次序" msgid "is iocaine" msgstr "挑戰全新任務" -#: taiga/projects/tasks/validators.py:59 +#: taiga/projects/tasks/validators.py:61 msgid "Invalid milestone id." msgstr "" -#: taiga/projects/tasks/validators.py:70 +#: taiga/projects/tasks/validators.py:72 msgid "Invalid task status id." msgstr "" -#: taiga/projects/tasks/validators.py:83 +#: taiga/projects/tasks/validators.py:85 msgid "Invalid user story id." msgstr "" -#: taiga/projects/tasks/validators.py:107 +#: taiga/projects/tasks/validators.py:109 msgid "Invalid task status id. The status must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:121 +#: taiga/projects/tasks/validators.py:123 msgid "Invalid user story id. The user story must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:133 +#: taiga/projects/tasks/validators.py:135 msgid "Invalid milestone id. The milestone must belong to the same project." msgstr "" -#: taiga/projects/tasks/validators.py:150 +#: taiga/projects/tasks/validators.py:152 msgid "" "Invalid task ids. All tasks must belong to the same project and, if it " "exists, to the same status, user story and/or milestone." @@ -3621,37 +3929,29 @@ msgstr "產品所有人" msgid "Stakeholder" msgstr "利害關係人" -#: taiga/projects/userstories/api.py:124 +#: taiga/projects/userstories/api.py:128 msgid "You don't have permissions to set this sprint to this user story." msgstr "無權限更動使用者故事的衝刺任務" -#: taiga/projects/userstories/api.py:128 +#: taiga/projects/userstories/api.py:132 msgid "You don't have permissions to set this status to this user story." msgstr "無權限更動此使用者故事的狀態" -#: taiga/projects/userstories/api.py:218 +#: taiga/projects/userstories/api.py:222 #, python-brace-format msgid "Invalid role id '{role_id}'" msgstr "" -#: taiga/projects/userstories/api.py:225 +#: taiga/projects/userstories/api.py:229 #, python-brace-format msgid "Invalid points id '{points_id}'" msgstr "" -#: taiga/projects/userstories/api.py:240 +#: taiga/projects/userstories/api.py:244 #, python-brace-format msgid "Generating the user story #{ref} - {subject}" msgstr "産生使用者故事 #{ref} - {subject}" -#: taiga/projects/userstories/api.py:301 -msgid "ref param is needed" -msgstr "" - -#: taiga/projects/userstories/api.py:304 -msgid "project or project_slug param is needed" -msgstr "" - #: taiga/projects/userstories/models.py:41 msgid "role" msgstr "角色" @@ -3680,87 +3980,91 @@ msgstr "産生自問題 " msgid "There's no user story with that id" msgstr "該ID無相關使用者故事" -#: taiga/projects/userstories/validators.py:82 -#: taiga/projects/userstories/validators.py:108 +#: taiga/projects/userstories/validators.py:83 +#: taiga/projects/userstories/validators.py:109 msgid "" "Invalid user story status id. The status must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:120 +#: taiga/projects/userstories/validators.py:121 msgid "Invalid milestone id. The milistone must belong to the same project." msgstr "" -#: taiga/projects/userstories/validators.py:135 +#: taiga/projects/userstories/validators.py:136 msgid "" "Invalid user story ids. All stories must belong to the same project and, if " "it exists, to the same status and milestone." msgstr "" -#: taiga/projects/userstories/validators.py:159 +#: taiga/projects/userstories/validators.py:160 msgid "The milestone isn't valid for the project" msgstr "" -#: taiga/projects/userstories/validators.py:169 +#: taiga/projects/userstories/validators.py:170 msgid "All the user stories must be from the same project" msgstr "" -#: taiga/projects/validators.py:61 +#: taiga/projects/validators.py:63 msgid "There's no project with that id" msgstr "該ID無相關專案" -#: taiga/projects/validators.py:142 -msgid "Email address is already taken" -msgstr "電子郵件已使用" +#: taiga/projects/validators.py:145 +msgid "The user yet exists in the project" +msgstr "" -#: taiga/projects/validators.py:154 +#: taiga/projects/validators.py:155 msgid "Invalid role for the project" msgstr "專案無效的角色" -#: taiga/projects/validators.py:165 +#: taiga/projects/validators.py:170 taiga/projects/validators.py:225 +msgid "The user must be a valid contact" +msgstr "" + +#: taiga/projects/validators.py:191 msgid "The project owner must be admin." msgstr "" -#: taiga/projects/validators.py:169 +#: taiga/projects/validators.py:195 msgid "At least one user must be an active admin for this project." msgstr "" -#: taiga/projects/validators.py:201 +#: taiga/projects/validators.py:240 msgid "Invalid role ids. All roles must belong to the same project." msgstr "" -#: taiga/projects/validators.py:225 +#: taiga/projects/validators.py:264 msgid "Default options" msgstr "預設選項" -#: taiga/projects/validators.py:226 +#: taiga/projects/validators.py:265 msgid "User story's statuses" msgstr "使用者故事狀態" -#: taiga/projects/validators.py:227 +#: taiga/projects/validators.py:266 msgid "Points" msgstr "點數" -#: taiga/projects/validators.py:228 +#: taiga/projects/validators.py:267 msgid "Task's statuses" msgstr "任務狀態" -#: taiga/projects/validators.py:229 +#: taiga/projects/validators.py:268 msgid "Issue's statuses" msgstr "問題狀態" -#: taiga/projects/validators.py:230 +#: taiga/projects/validators.py:269 msgid "Issue's types" msgstr "問題類型" -#: taiga/projects/validators.py:231 +#: taiga/projects/validators.py:270 msgid "Priorities" msgstr "優先性" -#: taiga/projects/validators.py:232 +#: taiga/projects/validators.py:271 msgid "Severities" msgstr "嚴重性" -#: taiga/projects/validators.py:233 +#: taiga/projects/validators.py:272 msgid "Roles" msgstr "角色" @@ -3789,7 +4093,7 @@ msgstr "上次更改" msgid "href" msgstr "href" -#: taiga/timeline/signals.py:63 +#: taiga/timeline/signals.py:65 msgid "Check the history API for the exact diff" msgstr "檢查API過去資料以找出差異" @@ -3829,133 +4133,133 @@ msgstr "" msgid "Important dates" msgstr "重要日期" -#: taiga/users/api.py:123 +#: taiga/users/api.py:132 msgid "Duplicated email" msgstr "複製電子郵件" -#: taiga/users/api.py:125 +#: taiga/users/api.py:134 msgid "Not valid email" msgstr "非有效電子郵性" -#: taiga/users/api.py:165 +#: taiga/users/api.py:172 msgid "Invalid username or email" msgstr "無效使用者或郵件" -#: taiga/users/api.py:174 +#: taiga/users/api.py:181 msgid "Mail sended successful!" msgstr "成功送出郵件" -#: taiga/users/api.py:212 +#: taiga/users/api.py:219 msgid "Current password parameter needed" msgstr "需要目前密碼之參數" -#: taiga/users/api.py:215 +#: taiga/users/api.py:222 msgid "New password parameter needed" msgstr "需要新密碼參數" -#: taiga/users/api.py:218 +#: taiga/users/api.py:225 msgid "Invalid password length at least 6 charaters needed" msgstr "無效密碼長度,至少需6個字元" -#: taiga/users/api.py:221 +#: taiga/users/api.py:228 msgid "Invalid current password" msgstr "無效密碼" -#: taiga/users/api.py:268 taiga/users/api.py:274 +#: taiga/users/api.py:275 taiga/users/api.py:281 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "無效,請確認代號正確,之前是否曾使用過?" -#: taiga/users/api.py:301 taiga/users/api.py:309 taiga/users/api.py:312 +#: taiga/users/api.py:317 taiga/users/api.py:325 taiga/users/api.py:328 msgid "Invalid, are you sure the token is correct?" msgstr "無效,請確認代號是否正確?" -#: taiga/users/models.py:95 +#: taiga/users/models.py:98 msgid "superuser status" msgstr "超級使用者狀態 " -#: taiga/users/models.py:96 +#: taiga/users/models.py:99 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "無經明確分派,即賦予該使用者所有權限," -#: taiga/users/models.py:126 +#: taiga/users/models.py:129 msgid "username" msgstr "使用者名稱" -#: taiga/users/models.py:127 +#: taiga/users/models.py:130 msgid "" "Required. 30 characters or fewer. Letters, numbers and /./-/_ characters" msgstr "必填。最多30字元(可為數字,字母,符號....)" -#: taiga/users/models.py:130 +#: taiga/users/models.py:133 msgid "Enter a valid username." msgstr "輸入有效的使用者名稱 " -#: taiga/users/models.py:133 +#: taiga/users/models.py:136 msgid "active" msgstr "活躍" -#: taiga/users/models.py:134 +#: taiga/users/models.py:137 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "賦予該使用者活躍角色,以不選擇取代刪除帳戶功能。" -#: taiga/users/models.py:140 +#: taiga/users/models.py:143 msgid "biography" msgstr "自傳" -#: taiga/users/models.py:143 +#: taiga/users/models.py:146 msgid "photo" msgstr "照片" -#: taiga/users/models.py:144 +#: taiga/users/models.py:147 msgid "date joined" msgstr "加入日期" -#: taiga/users/models.py:146 +#: taiga/users/models.py:149 msgid "default language" msgstr "預設語言 " -#: taiga/users/models.py:148 +#: taiga/users/models.py:151 msgid "default theme" msgstr "預設主題" -#: taiga/users/models.py:150 +#: taiga/users/models.py:153 msgid "default timezone" msgstr "預設時區" -#: taiga/users/models.py:152 +#: taiga/users/models.py:155 msgid "colorize tags" msgstr "顏色標籤" -#: taiga/users/models.py:157 +#: taiga/users/models.py:160 msgid "email token" msgstr "電子郵件符號 " -#: taiga/users/models.py:159 +#: taiga/users/models.py:162 msgid "new email address" msgstr "新電子郵件地址" -#: taiga/users/models.py:166 +#: taiga/users/models.py:169 msgid "max number of owned private projects" msgstr "" -#: taiga/users/models.py:169 +#: taiga/users/models.py:172 msgid "max number of owned public projects" msgstr "" -#: taiga/users/models.py:172 +#: taiga/users/models.py:175 msgid "max number of memberships for each owned private project" msgstr "" -#: taiga/users/models.py:176 +#: taiga/users/models.py:179 msgid "max number of memberships for each owned public project" msgstr "" -#: taiga/users/models.py:296 +#: taiga/users/models.py:307 msgid "permissions" msgstr "許可" diff --git a/taiga/mdrender/extensions/target_link.py b/taiga/mdrender/extensions/target_link.py index b9cff1c3..1f12b66d 100644 --- a/taiga/mdrender/extensions/target_link.py +++ b/taiga/mdrender/extensions/target_link.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 Alejandro Alonso +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/mdrender/extensions/wikilinks.py b/taiga/mdrender/extensions/wikilinks.py index 52363bc1..890ce3ca 100644 --- a/taiga/mdrender/extensions/wikilinks.py +++ b/taiga/mdrender/extensions/wikilinks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/mdrender/service.py b/taiga/mdrender/service.py index 701ed0d4..3a6a5908 100644 --- a/taiga/mdrender/service.py +++ b/taiga/mdrender/service.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -167,7 +167,7 @@ class DiffMatchPatch(diff_match_patch.diff_match_patch): def get_diff_of_htmls(html1, html2): diffutil = DiffMatchPatch() - diffs = diffutil.diff_main(html1, html2) + diffs = diffutil.diff_main(html1 or "", html2 or "") diffutil.diff_cleanupSemantic(diffs) return diffutil.diff_pretty_html(diffs) diff --git a/taiga/mdrender/templatetags/functions.py b/taiga/mdrender/templatetags/functions.py index 3bb52ebc..dab2f04d 100644 --- a/taiga/mdrender/templatetags/functions.py +++ b/taiga/mdrender/templatetags/functions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/permissions/choices.py b/taiga/permissions/choices.py index 594d48ee..0a86df50 100644 --- a/taiga/permissions/choices.py +++ b/taiga/permissions/choices.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/permissions/permissions.py b/taiga/permissions/permissions.py index 95a2620b..366c8d78 100644 --- a/taiga/permissions/permissions.py +++ b/taiga/permissions/permissions.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/permissions/services.py b/taiga/permissions/services.py index 50d8d72d..ba9b8137 100644 --- a/taiga/permissions/services.py +++ b/taiga/permissions/services.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/__init__.py b/taiga/projects/__init__.py index a4281e96..ca7b3169 100644 --- a/taiga/projects/__init__.py +++ b/taiga/projects/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/admin.py b/taiga/projects/admin.py index 9f7f5431..c7d0bc31 100644 --- a/taiga/projects/admin.py +++ b/taiga/projects/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -44,11 +44,11 @@ class MembershipAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name in ["user", "invited_by"] and getattr(self, 'obj', None): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) elif db_field.name in ["role"] and getattr(self, 'obj', None): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) @@ -59,17 +59,17 @@ class MembershipInline(admin.TabularInline): extra = 0 def get_formset(self, request, obj=None, **kwargs): - # Hack! Hook parent obj just in time to use in formfield_for_manytomany + # Hack! Hook parent obj just in time to use in formfield_for_foreignkey self.parent_obj = obj return super(MembershipInline, self).get_formset(request, obj, **kwargs) def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["user", "invited_by"]): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.parent_obj) elif (db_field.name in ["role"]): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( project=self.parent_obj) return super().formfield_for_foreignkey(db_field, request, **kwargs) @@ -152,25 +152,18 @@ class ProjectAdmin(admin.ModelAdmin): "default_priority", "default_severity", "default_issue_status", "default_issue_type"]): if getattr(self, 'obj', None): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( project=self.obj) else: - kwargs["queryset"] = db_field.related.model.objects.none() + kwargs["queryset"] = db_field.related_model.objects.none() elif (db_field.name in ["owner"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) - def formfield_for_manytomany(self, db_field, request, **kwargs): - if (db_field.name in ["watchers"] - and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.parent_model.objects.filter( - memberships__project=self.obj) - return super().formfield_for_manytomany(db_field, request, **kwargs) - def delete_model(self, request, obj): obj.delete_related_content() super().delete_model(request, obj) diff --git a/taiga/projects/api.py b/taiga/projects/api.py index 22c485a8..83b75487 100644 --- a/taiga/projects/api.py +++ b/taiga/projects/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -52,6 +52,7 @@ from taiga.projects.mixins.ordering import BulkUpdateOrderMixin from taiga.projects.tasks.models import Task from taiga.projects.tagging.api import TagsColorsResourceMixin from taiga.projects.userstories.models import UserStory, RolePoints +from taiga.users import services as users_services from . import filters as project_filters from . import models @@ -226,7 +227,7 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, if not template_description: raise response.BadRequest(_("Not valid template description")) - with advisory_lock("create-project-template") as acquired_key_lock: + with advisory_lock("create-project-template"): template_slug = slugify_uniquely(template_name, models.ProjectTemplate) project = self.get_object() @@ -400,6 +401,42 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, services.reject_project_transfer(project, request.user, token, reason) return response.Ok() + @detail_route(methods=["POST"]) + def duplicate(self, request, pk=None): + project = self.get_object() + self.check_permissions(request, "duplicate", project) + if project.blocked_code is not None: + raise exc.Blocked(_("Blocked element")) + + validator = validators.DuplicateProjectValidator(data=request.DATA) + if not validator.is_valid(): + return response.BadRequest(validator.errors) + + data = validator.data + + # Validate if the project can be imported + is_private = data.get('is_private', False) + total_memberships = len(data.get("users", [])) + 1 + (enough_slots, error_message) = users_services.has_available_slot_for_new_project( + self.request.user, + is_private, + total_memberships + ) + if not enough_slots: + raise exc.NotEnoughSlotsForProject(is_private, total_memberships, error_message) + + new_project = services.duplicate_project( + project=project, + owner=request.user, + name=data["name"], + description=data["description"], + is_private=data["is_private"], + users=data["users"] + ) + new_project = get_object_or_404(self.get_queryset(), id=new_project.id) + serializer = self.get_serializer(new_project) + return response.Created(serializer.data) + def _raise_if_blocked(self, project): if self.is_blocked(project): raise exc.Blocked(_("Blocked element")) @@ -640,7 +677,6 @@ class IssueStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin, return super().create(request, *args, **kwargs) - ###################################################### ## Project Template ###################################################### @@ -662,7 +698,6 @@ class ProjectTemplateViewSet(ModelCrudViewSet): class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet): model = models.Membership admin_serializer_class = serializers.MembershipAdminSerializer - admin_validator_class = validators.MembershipAdminValidator serializer_class = serializers.MembershipSerializer validator_class = validators.MembershipValidator permission_classes = (permissions.MembershipPermission,) @@ -690,12 +725,6 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet): else: return self.serializer_class - def get_validator_class(self): - if self.action == "create": - return self.admin_validator_class - - return self.validator_class - def _check_if_project_can_have_more_memberships(self, project, total_new_memberships): (can_add_memberships, error_type) = services.check_if_project_can_have_more_memberships( project, @@ -710,7 +739,10 @@ class MembershipViewSet(BlockedByProjectMixin, ModelCrudViewSet): @list_route(methods=["POST"]) def bulk_create(self, request, **kwargs): - validator = validators.MembersBulkValidator(data=request.DATA) + context = { + "request": request + } + validator = validators.MembersBulkValidator(data=request.DATA, context=context) if not validator.is_valid(): return response.BadRequest(validator.errors) diff --git a/taiga/projects/apps.py b/taiga/projects/apps.py index 634d56ce..89977ccf 100644 --- a/taiga/projects/apps.py +++ b/taiga/projects/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -58,6 +58,7 @@ def connect_memberships_signals(): sender=apps.get_model("projects", "Membership"), dispatch_uid='create-notify-policy') + def disconnect_memberships_signals(): signals.pre_delete.disconnect(sender=apps.get_model("projects", "Membership"), dispatch_uid='membership_pre_delete') @@ -79,7 +80,6 @@ def disconnect_us_status_signals(): dispatch_uid="try_to_close_or_open_user_stories_when_edit_us_status") - ## Tasks Statuses Signals def connect_task_status_signals(): @@ -94,7 +94,6 @@ def disconnect_task_status_signals(): dispatch_uid="try_to_close_or_open_user_stories_when_edit_task_status") - class ProjectsAppConfig(AppConfig): name = "taiga.projects" verbose_name = "Projects" diff --git a/taiga/projects/attachments/__init__.py b/taiga/projects/attachments/__init__.py index bdc2814a..3ddc3fe0 100644 --- a/taiga/projects/attachments/__init__.py +++ b/taiga/projects/attachments/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/attachments/admin.py b/taiga/projects/attachments/admin.py index befbef4f..9ac79288 100644 --- a/taiga/projects/attachments/admin.py +++ b/taiga/projects/attachments/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -34,7 +34,7 @@ class AttachmentAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["owner"]and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/attachments/api.py b/taiga/projects/attachments/api.py index 3bcbf6cf..d0fa92bb 100644 --- a/taiga/projects/attachments/api.py +++ b/taiga/projects/attachments/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/attachments/apps.py b/taiga/projects/attachments/apps.py index 2f9edae2..1b77c3b4 100644 --- a/taiga/projects/attachments/apps.py +++ b/taiga/projects/attachments/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/attachments/management/commands/generate_sha1.py b/taiga/projects/attachments/management/commands/generate_sha1.py index 567788d1..e89ee4cf 100644 --- a/taiga/projects/attachments/management/commands/generate_sha1.py +++ b/taiga/projects/attachments/management/commands/generate_sha1.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/attachments/migrations/0007_attachment_from_comment.py b/taiga/projects/attachments/migrations/0007_attachment_from_comment.py new file mode 100644 index 00000000..00482ec4 --- /dev/null +++ b/taiga/projects/attachments/migrations/0007_attachment_from_comment.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-17 07:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('attachments', '0006_auto_20160617_1233'), + ] + + operations = [ + migrations.AddField( + model_name='attachment', + name='from_comment', + field=models.BooleanField(default=False, verbose_name='from_comment'), + ), + ] diff --git a/taiga/projects/attachments/migrations/0008_auto_20170201_1053.py b/taiga/projects/attachments/migrations/0008_auto_20170201_1053.py new file mode 100644 index 00000000..72b91c3b --- /dev/null +++ b/taiga/projects/attachments/migrations/0008_auto_20170201_1053.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-02-01 10:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('attachments', '0007_attachment_from_comment'), + ] + + operations = [ + migrations.AlterField( + model_name='attachment', + name='from_comment', + field=models.BooleanField(default=False, verbose_name='from comment'), + ), + ] diff --git a/taiga/projects/attachments/models.py b/taiga/projects/attachments/models.py index a5110a4b..f988e5d9 100644 --- a/taiga/projects/attachments/models.py +++ b/taiga/projects/attachments/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -58,6 +58,7 @@ class Attachment(models.Model): sha1 = models.CharField(default="", max_length=40, verbose_name=_("sha1"), blank=True) is_deprecated = models.BooleanField(default=False, verbose_name=_("is deprecated")) + from_comment = models.BooleanField(default=False, verbose_name=_("from comment")) description = models.TextField(null=False, blank=True, verbose_name=_("description")) order = models.IntegerField(default=0, null=False, blank=False, verbose_name=_("order")) diff --git a/taiga/projects/attachments/permissions.py b/taiga/projects/attachments/permissions.py index 4c7a7915..7653e7f3 100644 --- a/taiga/projects/attachments/permissions.py +++ b/taiga/projects/attachments/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -27,10 +27,16 @@ class IsAttachmentOwnerPerm(PermissionComponent): return request.user == obj.owner return False +class CommentAttachmentPerm(PermissionComponent): + def check_permissions(self, request, view, obj=None): + if obj.from_comment: + return True + return False + class EpicAttachmentPermission(TaigaResourcePermission): retrieve_perms = HasProjectPerm('view_epics') | IsAttachmentOwnerPerm() - create_perms = HasProjectPerm('modify_epic') + create_perms = HasProjectPerm('modify_epic') | (CommentAttachmentPerm() & HasProjectPerm('comment_epic')) update_perms = HasProjectPerm('modify_epic') | IsAttachmentOwnerPerm() partial_update_perms = HasProjectPerm('modify_epic') | IsAttachmentOwnerPerm() destroy_perms = HasProjectPerm('modify_epic') | IsAttachmentOwnerPerm() @@ -39,7 +45,7 @@ class EpicAttachmentPermission(TaigaResourcePermission): class UserStoryAttachmentPermission(TaigaResourcePermission): retrieve_perms = HasProjectPerm('view_us') | IsAttachmentOwnerPerm() - create_perms = HasProjectPerm('modify_us') + create_perms = HasProjectPerm('modify_us') | (CommentAttachmentPerm() & HasProjectPerm('comment_us')) update_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm() partial_update_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm() destroy_perms = HasProjectPerm('modify_us') | IsAttachmentOwnerPerm() @@ -48,7 +54,7 @@ class UserStoryAttachmentPermission(TaigaResourcePermission): class TaskAttachmentPermission(TaigaResourcePermission): retrieve_perms = HasProjectPerm('view_tasks') | IsAttachmentOwnerPerm() - create_perms = HasProjectPerm('modify_task') + create_perms = HasProjectPerm('modify_task') | (CommentAttachmentPerm() & HasProjectPerm('comment_task')) update_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm() partial_update_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm() destroy_perms = HasProjectPerm('modify_task') | IsAttachmentOwnerPerm() @@ -57,7 +63,7 @@ class TaskAttachmentPermission(TaigaResourcePermission): class IssueAttachmentPermission(TaigaResourcePermission): retrieve_perms = HasProjectPerm('view_issues') | IsAttachmentOwnerPerm() - create_perms = HasProjectPerm('modify_issue') + create_perms = HasProjectPerm('modify_issue') | (CommentAttachmentPerm() & HasProjectPerm('comment_issue')) update_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm() partial_update_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm() destroy_perms = HasProjectPerm('modify_issue') | IsAttachmentOwnerPerm() @@ -66,7 +72,7 @@ class IssueAttachmentPermission(TaigaResourcePermission): class WikiAttachmentPermission(TaigaResourcePermission): retrieve_perms = HasProjectPerm('view_wiki_pages') | IsAttachmentOwnerPerm() - create_perms = HasProjectPerm('modify_wiki_page') + create_perms = HasProjectPerm('modify_wiki_page') | (CommentAttachmentPerm() & HasProjectPerm('comment_wiki_page')) update_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm() partial_update_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm() destroy_perms = HasProjectPerm('modify_wiki_page') | IsAttachmentOwnerPerm() diff --git a/taiga/projects/attachments/serializers.py b/taiga/projects/attachments/serializers.py index ce8893b7..e7fbc297 100644 --- a/taiga/projects/attachments/serializers.py +++ b/taiga/projects/attachments/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -35,6 +35,7 @@ class AttachmentSerializer(serializers.LightSerializer): url = Field() description = Field() is_deprecated = Field() + from_comment = Field() created_date = Field() modified_date = Field() object_id = Field() @@ -42,6 +43,7 @@ class AttachmentSerializer(serializers.LightSerializer): sha1 = Field() url = MethodField("get_url") thumbnail_card_url = MethodField("get_thumbnail_card_url") + preview_url = MethodField("get_preview_url") def get_url(self, obj): return obj.attached_file.url @@ -49,6 +51,11 @@ class AttachmentSerializer(serializers.LightSerializer): def get_thumbnail_card_url(self, obj): return services.get_card_image_thumbnail_url(obj) + def get_preview_url(self, obj): + if obj.name.endswith(".psd"): + return services.get_attachment_image_preview_url(obj) + return self.get_url(obj) + class BasicAttachmentsInfoSerializerMixin(serializers.LightSerializer): """ diff --git a/taiga/projects/attachments/services.py b/taiga/projects/attachments/services.py index b3305552..0c5ac4a0 100644 --- a/taiga/projects/attachments/services.py +++ b/taiga/projects/attachments/services.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 @@ -28,3 +28,8 @@ def get_card_image_thumbnail_url(attachment): if attachment.attached_file: return get_thumbnail_url(attachment.attached_file, settings.THN_ATTACHMENT_CARD) return None + +def get_attachment_image_preview_url(attachment): + if attachment.attached_file: + return get_thumbnail_url(attachment.attached_file, settings.THN_ATTACHMENT_PREVIEW) + return None diff --git a/taiga/projects/attachments/utils.py b/taiga/projects/attachments/utils.py index b1f6b08e..3c1c42ee 100644 --- a/taiga/projects/attachments/utils.py +++ b/taiga/projects/attachments/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/attachments/validators.py b/taiga/projects/attachments/validators.py index 72355ce4..1a10dab4 100644 --- a/taiga/projects/attachments/validators.py +++ b/taiga/projects/attachments/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -29,5 +29,5 @@ class AttachmentValidator(validators.ModelValidator): model = models.Attachment fields = ("id", "project", "owner", "name", "attached_file", "size", "description", "is_deprecated", "created_date", - "modified_date", "object_id", "order", "sha1") + "modified_date", "object_id", "order", "sha1", "from_comment") read_only_fields = ("owner", "created_date", "modified_date", "sha1") diff --git a/taiga/projects/choices.py b/taiga/projects/choices.py index d1458c44..550807d3 100644 --- a/taiga/projects/choices.py +++ b/taiga/projects/choices.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/contact/__init__.py b/taiga/projects/contact/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/projects/contact/admin.py b/taiga/projects/contact/admin.py new file mode 100644 index 00000000..99545dfb --- /dev/null +++ b/taiga/projects/contact/admin.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.contrib import admin + +from . import models + + +class ContactEntryAdmin(admin.ModelAdmin): + list_display = ["created_date", "project", "user"] + list_display_links = list_display + list_filter = ["created_date", ] + date_hierarchy = "created_date" + ordering = ("-created_date", "id") + search_fields = ("project__name", "project__slug", "user__username", "user__email", "user__full_name") + +admin.site.register(models.ContactEntry, ContactEntryAdmin) diff --git a/taiga/projects/contact/api.py b/taiga/projects/contact/api.py new file mode 100644 index 00000000..7d398144 --- /dev/null +++ b/taiga/projects/contact/api.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base import status +from taiga.base.api.mixins import CreateModelMixin, BlockedByProjectMixin +from taiga.base.api.viewsets import GenericViewSet + +from . import models +from . import permissions +from . import services +from . import validators + +from django.conf import settings + + +class ContactViewSet(BlockedByProjectMixin, CreateModelMixin, GenericViewSet): + permission_classes = (permissions.ContactPermission,) + validator_class = validators.ContactEntryValidator + model = models.ContactEntry + + def create(self, *args, **kwargs): + response = super().create(*args, **kwargs) + + if response.status_code == status.HTTP_201_CREATED: + if settings.CELERY_ENABLED: + services.send_contact_email.delay(self.object.id) + else: + services.send_contact_email(self.object.id) + + return response + + def pre_save(self, obj): + obj.user = self.request.user + super().pre_save(obj) diff --git a/taiga/projects/contact/migrations/0001_initial.py b/taiga/projects/contact/migrations/0001_initial.py new file mode 100644 index 00000000..f06eb96b --- /dev/null +++ b/taiga/projects/contact/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-11-10 15:18 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('projects', '0056_auto_20161110_1518'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ContactEntry', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('comment', models.TextField(verbose_name='comment')), + ('created_date', models.DateTimeField(auto_now_add=True, verbose_name='created date')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contact_entries', to='projects.Project', verbose_name='project')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contact_entries', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ], + options={ + 'verbose_name': 'contact entry', + 'ordering': ['-created_date', 'id'], + 'verbose_name_plural': 'contact entries', + }, + ), + ] diff --git a/taiga/projects/contact/migrations/__init__.py b/taiga/projects/contact/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/projects/contact/models.py b/taiga/projects/contact/models.py new file mode 100644 index 00000000..ce2c47a1 --- /dev/null +++ b/taiga/projects/contact/models.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class ContactEntry(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="contact_entries", + verbose_name=_("user")) + + project = models.ForeignKey("projects.Project", null=False, blank=False, + related_name="contact_entries", verbose_name=_("project")) + + comment = models.TextField(null=False, blank=False, verbose_name=_("comment")) + + created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True, + verbose_name=_("created date")) + + class Meta: + verbose_name = "contact entry" + verbose_name_plural = "contact entries" + ordering = ["-created_date", "id"] diff --git a/taiga/projects/contact/permissions.py b/taiga/projects/contact/permissions.py new file mode 100644 index 00000000..678aede9 --- /dev/null +++ b/taiga/projects/contact/permissions.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base.api.permissions import PermissionComponent +from taiga.base.api.permissions import TaigaResourcePermission + + +class IsContactActivated(PermissionComponent): + def check_permissions(self, request, view, obj=None): + return request.user.is_authenticated() and obj.project.is_contact_activated + + +class ContactPermission(TaigaResourcePermission): + create_perms = IsContactActivated() diff --git a/taiga/projects/contact/services.py b/taiga/projects/contact/services.py new file mode 100644 index 00000000..d623c78b --- /dev/null +++ b/taiga/projects/contact/services.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base.mails import mail_builder +from taiga.celery import app +from taiga.front.templatetags.functions import resolve as resolve_front_url +from taiga.users.services import get_user_photo_url + +from . import models + + +@app.task +def send_contact_email(contact_entry_id): + contact_entry = models.ContactEntry.objects.filter(id=contact_entry_id).first() + if contact_entry is None: + return + + ctx = { + "comment": contact_entry.comment, + "full_name": contact_entry.user.get_full_name(), + "project_name": contact_entry.project.name, + "photo_url": get_user_photo_url(contact_entry.user), + "user_profile_url": resolve_front_url("user", contact_entry.user.username), + "project_settings_url": resolve_front_url("project-admin", contact_entry.project.slug), + } + users = contact_entry.project.get_users().exclude(id=contact_entry.user_id) + addresses = ", ".join([u.email for u in users]) + email = mail_builder.contact_notification(addresses, ctx) + email.extra_headers["Reply-To"] = ", ".join([contact_entry.user.email]) + email.send() diff --git a/taiga/projects/contact/templates/emails/contact_notification-body-html.jinja b/taiga/projects/contact/templates/emails/contact_notification-body-html.jinja new file mode 100644 index 00000000..257a5905 --- /dev/null +++ b/taiga/projects/contact/templates/emails/contact_notification-body-html.jinja @@ -0,0 +1,24 @@ +{% extends "emails/base-body-html.jinja" %} + +{% block body %} +

+ {% if photo_url %} + + {{ full_name }} + + {% endif %} + {% trans full_name=full_name, comment=comment, project_name=project_name %} + {{ full_name }} has written to {{ project_name }} + {% endtrans %} +

+ +

{{ comment }}

+ +
+ +

+ {% trans project_name=project_name %} + You are receiving this message because you are listed as administrator of the project titled {{ project_name }}. If you don't want members of the Taiga community contacting your project, please update your project settings to prevent such contacts. Regular communications amongst members of the project will not be affected. + {% endtrans %} +

+{% endblock %} diff --git a/taiga/projects/contact/templates/emails/contact_notification-body-text.jinja b/taiga/projects/contact/templates/emails/contact_notification-body-text.jinja new file mode 100644 index 00000000..c08f68db --- /dev/null +++ b/taiga/projects/contact/templates/emails/contact_notification-body-text.jinja @@ -0,0 +1,9 @@ +{% trans full_name=full_name, comment=comment, project_name=project_name %} +{{ full_name }} has written to {{ project_name }} +{% endtrans %} +--------- +{{ comment }} +--------- +{% trans project_name=project_name %} +You are receiving this message because you are listed as administrator of the project titled {{ project_name }}. If you don't want members of the Taiga community contacting your project, please update your project settings in {{ project_settings_url }} to prevent such contacts. Regular communications amongst members of the project will not be affected. +{% endtrans %} diff --git a/taiga/projects/contact/templates/emails/contact_notification-subject.jinja b/taiga/projects/contact/templates/emails/contact_notification-subject.jinja new file mode 100644 index 00000000..481e442e --- /dev/null +++ b/taiga/projects/contact/templates/emails/contact_notification-subject.jinja @@ -0,0 +1,3 @@ +{% trans full_name=full_name|safe, project_name=project_name|safe %} +[Taiga] {{ full_name }} has sent a message to the project {{ project_name }} +{% endtrans %} diff --git a/taiga/projects/contact/validators.py b/taiga/projects/contact/validators.py new file mode 100644 index 00000000..5ee8ac88 --- /dev/null +++ b/taiga/projects/contact/validators.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base.api import validators + +from . import models + + +class ContactEntryValidator(validators.ModelValidator): + + class Meta: + model = models.ContactEntry + read_only_fields = ("user", "created_date", ) diff --git a/taiga/projects/custom_attributes/admin.py b/taiga/projects/custom_attributes/admin.py index ffa676d5..664524f9 100644 --- a/taiga/projects/custom_attributes/admin.py +++ b/taiga/projects/custom_attributes/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/custom_attributes/api.py b/taiga/projects/custom_attributes/api.py index f8e74b00..daf4e24c 100644 --- a/taiga/projects/custom_attributes/api.py +++ b/taiga/projects/custom_attributes/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -16,14 +16,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from django.utils.translation import ugettext_lazy as _ - from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelUpdateRetrieveViewSet from taiga.base.api.mixins import BlockedByProjectMixin -from taiga.base import exceptions as exc from taiga.base import filters -from taiga.base import response from taiga.projects.mixins.ordering import BulkUpdateOrderMixin from taiga.projects.history.mixins import HistoryResourceMixin diff --git a/taiga/projects/custom_attributes/choices.py b/taiga/projects/custom_attributes/choices.py index d03ce070..a4cedf34 100644 --- a/taiga/projects/custom_attributes/choices.py +++ b/taiga/projects/custom_attributes/choices.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -21,12 +21,14 @@ from django.utils.translation import ugettext_lazy as _ TEXT_TYPE = "text" MULTILINE_TYPE = "multiline" +RICHTEXT_TYPE = "richtext" DATE_TYPE = "date" URL_TYPE = "url" TYPES_CHOICES = ( (TEXT_TYPE, _("Text")), (MULTILINE_TYPE, _("Multi-Line Text")), + (RICHTEXT_TYPE, _("Rich text")), (DATE_TYPE, _("Date")), (URL_TYPE, _("Url")) ) diff --git a/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py b/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py index 8c1848db..a6c5084e 100644 --- a/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py +++ b/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -20,7 +20,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), ('version', models.IntegerField(default=1, verbose_name='version')), - ('attributes_values', django_pgjson.fields.JsonField(default={}, verbose_name='attributes_values')), + ('attributes_values', taiga.base.db.models.fields.JSONField(default={}, verbose_name='attributes_values')), ('issue', models.OneToOneField(verbose_name='issue', to='issues.Issue', related_name='custom_attributes_values')), ], options={ @@ -36,7 +36,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), ('version', models.IntegerField(default=1, verbose_name='version')), - ('attributes_values', django_pgjson.fields.JsonField(default={}, verbose_name='attributes_values')), + ('attributes_values', taiga.base.db.models.fields.JSONField(default={}, verbose_name='attributes_values')), ('task', models.OneToOneField(verbose_name='task', to='tasks.Task', related_name='custom_attributes_values')), ], options={ @@ -52,7 +52,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), ('version', models.IntegerField(default=1, verbose_name='version')), - ('attributes_values', django_pgjson.fields.JsonField(default={}, verbose_name='attributes_values')), + ('attributes_values', taiga.base.db.models.fields.JSONField(default={}, verbose_name='attributes_values')), ('user_story', models.OneToOneField(verbose_name='user story', to='userstories.UserStory', related_name='custom_attributes_values')), ], options={ diff --git a/taiga/projects/custom_attributes/migrations/0005_auto_20150505_1639.py b/taiga/projects/custom_attributes/migrations/0005_auto_20150505_1639.py index 4fba06c5..4c6503e0 100644 --- a/taiga/projects/custom_attributes/migrations/0005_auto_20150505_1639.py +++ b/taiga/projects/custom_attributes/migrations/0005_auto_20150505_1639.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -15,19 +15,19 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='issuecustomattributesvalues', name='attributes_values', - field=django_pgjson.fields.JsonField(verbose_name='values', default={}), + field=taiga.base.db.models.fields.JSONField(verbose_name='values', default={}), preserve_default=True, ), migrations.AlterField( model_name='taskcustomattributesvalues', name='attributes_values', - field=django_pgjson.fields.JsonField(verbose_name='values', default={}), + field=taiga.base.db.models.fields.JSONField(verbose_name='values', default={}), preserve_default=True, ), migrations.AlterField( model_name='userstorycustomattributesvalues', name='attributes_values', - field=django_pgjson.fields.JsonField(verbose_name='values', default={}), + field=taiga.base.db.models.fields.JSONField(verbose_name='values', default={}), preserve_default=True, ), ] diff --git a/taiga/projects/custom_attributes/migrations/0009_auto_20160728_1002.py b/taiga/projects/custom_attributes/migrations/0009_auto_20160728_1002.py index 313e22fd..a5e84f6d 100644 --- a/taiga/projects/custom_attributes/migrations/0009_auto_20160728_1002.py +++ b/taiga/projects/custom_attributes/migrations/0009_auto_20160728_1002.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals from django.db import migrations, models import django.db.models.deletion import django.utils.timezone -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -55,7 +55,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('version', models.IntegerField(default=1, verbose_name='version')), - ('attributes_values', django_pgjson.fields.JsonField(default={}, verbose_name='values')), + ('attributes_values', taiga.base.db.models.fields.JSONField(default={}, verbose_name='values')), ('epic', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_attributes_values', to='epics.Epic', verbose_name='epic')), ], options={ diff --git a/taiga/projects/custom_attributes/migrations/0011_json_to_jsonb.py b/taiga/projects/custom_attributes/migrations/0011_json_to_jsonb.py new file mode 100644 index 00000000..bbe14196 --- /dev/null +++ b/taiga/projects/custom_attributes/migrations/0011_json_to_jsonb.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:34 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + +class Migration(migrations.Migration): + + dependencies = [ + ('custom_attributes', '0010_auto_20160928_0540'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="custom_attributes_epiccustomattributesvalues", + column_name="attributes_values", + ), + reverse_sql=migrations.RunSQL.noop + ), + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="custom_attributes_userstorycustomattributesvalues", + column_name="attributes_values", + ), + reverse_sql=migrations.RunSQL.noop + ), + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="custom_attributes_taskcustomattributesvalues", + column_name="attributes_values", + ), + reverse_sql=migrations.RunSQL.noop + ), + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="custom_attributes_issuecustomattributesvalues", + column_name="attributes_values", + ), + reverse_sql=migrations.RunSQL.noop + ), + + # Function: Remove a key in a json field + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION "json_object_delete_keys"("json" jsonb, VARIADIC "keys_to_delete" text[]) + RETURNS jsonb + LANGUAGE sql + IMMUTABLE + STRICT + AS $function$ + SELECT COALESCE ((SELECT ('{' || string_agg(to_json("key") || ':' || "value", ',') || '}') + FROM jsonb_each("json") + WHERE "key" <> ALL ("keys_to_delete")), + '{}')::text::jsonb $function$; + """, + reverse_sql=""" + CREATE OR REPLACE FUNCTION "json_object_delete_keys"("json" json, VARIADIC "keys_to_delete" text[]) + RETURNS json + LANGUAGE sql + IMMUTABLE + STRICT + AS $function$ + SELECT COALESCE ((SELECT ('{' || string_agg(to_json("key") || ':' || "value", ',') || '}') + FROM json_each("json") + WHERE "key" <> ALL ("keys_to_delete")), + '{}')::json $function$;""" + ), + ] diff --git a/taiga/projects/custom_attributes/migrations/0012_auto_20161201_1628.py b/taiga/projects/custom_attributes/migrations/0012_auto_20161201_1628.py new file mode 100644 index 00000000..cab03195 --- /dev/null +++ b/taiga/projects/custom_attributes/migrations/0012_auto_20161201_1628.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-12-01 16:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('custom_attributes', '0011_json_to_jsonb'), + ] + + operations = [ + migrations.AlterField( + model_name='epiccustomattribute', + name='type', + field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url')], default='text', max_length=16, verbose_name='type'), + ), + migrations.AlterField( + model_name='issuecustomattribute', + name='type', + field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url')], default='text', max_length=16, verbose_name='type'), + ), + migrations.AlterField( + model_name='taskcustomattribute', + name='type', + field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url')], default='text', max_length=16, verbose_name='type'), + ), + migrations.AlterField( + model_name='userstorycustomattribute', + name='type', + field=models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('richtext', 'Rich text'), ('date', 'Date'), ('url', 'Url')], default='text', max_length=16, verbose_name='type'), + ), + ] diff --git a/taiga/projects/custom_attributes/models.py b/taiga/projects/custom_attributes/models.py index 6467f97e..d239e70d 100644 --- a/taiga/projects/custom_attributes/models.py +++ b/taiga/projects/custom_attributes/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,7 +20,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from taiga.base.utils.time import timestamp_ms from taiga.projects.occ.mixins import OCCModelMixin @@ -92,7 +92,7 @@ class IssueCustomAttribute(AbstractCustomAttribute): ####################################################### class AbstractCustomAttributesValues(OCCModelMixin, models.Model): - attributes_values = JsonField(null=False, blank=False, default={}, verbose_name=_("values")) + attributes_values = JSONField(null=False, blank=False, default={}, verbose_name=_("values")) class Meta: abstract = True diff --git a/taiga/projects/custom_attributes/permissions.py b/taiga/projects/custom_attributes/permissions.py index ffc6a04c..b93ce5b7 100644 --- a/taiga/projects/custom_attributes/permissions.py +++ b/taiga/projects/custom_attributes/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/custom_attributes/serializers.py b/taiga/projects/custom_attributes/serializers.py index 10e9c756..8c6d3f06 100644 --- a/taiga/projects/custom_attributes/serializers.py +++ b/taiga/projects/custom_attributes/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,7 +17,7 @@ # along with this program. If not, see . -from taiga.base.fields import JsonField, Field +from taiga.base.fields import JSONField, Field from taiga.base.api import serializers diff --git a/taiga/projects/custom_attributes/services.py b/taiga/projects/custom_attributes/services.py index 4a30305e..65542ec7 100644 --- a/taiga/projects/custom_attributes/services.py +++ b/taiga/projects/custom_attributes/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/custom_attributes/signals.py b/taiga/projects/custom_attributes/signals.py index 96e74e9e..606ed1c9 100644 --- a/taiga/projects/custom_attributes/signals.py +++ b/taiga/projects/custom_attributes/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/custom_attributes/validators.py b/taiga/projects/custom_attributes/validators.py index 4169eee6..5f09e1d1 100644 --- a/taiga/projects/custom_attributes/validators.py +++ b/taiga/projects/custom_attributes/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,7 +19,7 @@ from django.utils.translation import ugettext_lazy as _ -from taiga.base.fields import JsonField +from taiga.base.fields import JSONField from taiga.base.exceptions import ValidationError from taiga.base.api.validators import ModelValidator @@ -92,7 +92,7 @@ class IssueCustomAttributeValidator(BaseCustomAttributeValidator): class BaseCustomAttributesValuesValidator(ModelValidator): - attributes_values = JsonField(source="attributes_values", label="attributes values") + attributes_values = JSONField(source="attributes_values", label="attributes values") _custom_attribute_model = None _container_field = None diff --git a/taiga/projects/epics/__init__.py b/taiga/projects/epics/__init__.py index cc0dd3b9..dcbc3cba 100644 --- a/taiga/projects/epics/__init__.py +++ b/taiga/projects/epics/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/epics/admin.py b/taiga/projects/epics/admin.py index 69aea806..3a17258a 100644 --- a/taiga/projects/epics/admin.py +++ b/taiga/projects/epics/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -44,10 +44,10 @@ class EpicAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["status"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter(project=self.obj.project) + kwargs["queryset"] = db_field.related_model.objects.filter(project=self.obj.project) elif (db_field.name in ["owner", "assigned_to"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter(memberships__project=self.obj.project) + kwargs["queryset"] = db_field.related_model.objects.filter(memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/epics/api.py b/taiga/projects/epics/api.py index b2e53052..eb5e1f5b 100644 --- a/taiga/projects/epics/api.py +++ b/taiga/projects/epics/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/epics/apps.py b/taiga/projects/epics/apps.py index bf489ea0..235ba32b 100644 --- a/taiga/projects/epics/apps.py +++ b/taiga/projects/epics/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/epics/migrations/0005_epic_external_reference.py b/taiga/projects/epics/migrations/0005_epic_external_reference.py new file mode 100644 index 00000000..a1d10880 --- /dev/null +++ b/taiga/projects/epics/migrations/0005_epic_external_reference.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-11-08 11:19 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('epics', '0004_auto_20160928_0540'), + ] + + operations = [ + migrations.AddField( + model_name='epic', + name='external_reference', + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=None, null=True, size=None, verbose_name='external reference'), + ), + ] diff --git a/taiga/projects/epics/models.py b/taiga/projects/epics/models.py index da0e4a3e..26957df0 100644 --- a/taiga/projects/epics/models.py +++ b/taiga/projects/epics/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -18,6 +18,7 @@ from django.db import models from django.contrib.contenttypes.fields import GenericRelation +from django.contrib.postgres.fields import ArrayField from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -67,6 +68,8 @@ class Epic(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.M user_stories = models.ManyToManyField("userstories.UserStory", related_name="epics", through='RelatedUserStory', verbose_name=_("user stories")) + external_reference = ArrayField(models.TextField(null=False, blank=False), + null=True, blank=True, default=None, verbose_name=_("external reference")) attachments = GenericRelation("attachments.Attachment") diff --git a/taiga/projects/epics/permissions.py b/taiga/projects/epics/permissions.py index fd473e18..904c417a 100644 --- a/taiga/projects/epics/permissions.py +++ b/taiga/projects/epics/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/epics/serializers.py b/taiga/projects/epics/serializers.py index 339272de..48dbc22d 100644 --- a/taiga/projects/epics/serializers.py +++ b/taiga/projects/epics/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -23,6 +23,7 @@ from taiga.base.neighbors import NeighborsSerializerMixin from taiga.mdrender.service import render as mdrender from taiga.projects.attachments.serializers import BasicAttachmentsInfoSerializerMixin from taiga.projects.mixins.serializers import OwnerExtraInfoSerializerMixin +from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin from taiga.projects.mixins.serializers import AssignedToExtraInfoSerializerMixin from taiga.projects.mixins.serializers import StatusExtraInfoSerializerMixin from taiga.projects.notifications.mixins import WatchedResourceSerializer @@ -32,7 +33,8 @@ from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin class EpicListSerializer(VoteResourceSerializerMixin, WatchedResourceSerializer, OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin, - StatusExtraInfoSerializerMixin, BasicAttachmentsInfoSerializerMixin, + StatusExtraInfoSerializerMixin, ProjectExtraInfoSerializerMixin, + BasicAttachmentsInfoSerializerMixin, TaggedInProjectResourceSerializer, serializers.LightSerializer): id = Field() diff --git a/taiga/projects/epics/services.py b/taiga/projects/epics/services.py index e2e712d4..b43424cf 100644 --- a/taiga/projects/epics/services.py +++ b/taiga/projects/epics/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/epics/utils.py b/taiga/projects/epics/utils.py index 49e394d8..1637aa0b 100644 --- a/taiga/projects/epics/utils.py +++ b/taiga/projects/epics/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/epics/validators.py b/taiga/projects/epics/validators.py index f5d71ab2..a82883ce 100644 --- a/taiga/projects/epics/validators.py +++ b/taiga/projects/epics/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/filters.py b/taiga/projects/filters.py index fe720f97..4dacd29f 100644 --- a/taiga/projects/filters.py +++ b/taiga/projects/filters.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -94,14 +94,14 @@ class QFilterBackend(FilterBackend): # NOTE: See migtration 0033_text_search_indexes q = request.QUERY_PARAMS.get('q', None) if q: - tsquery = "to_tsquery('english_nostop', %s)" + tsquery = "to_tsquery('simple', %s)" tsquery_params = [to_tsquery(q)] tsvector = """ - setweight(to_tsvector('english_nostop', + setweight(to_tsvector('simple', coalesce(projects_project.name, '')), 'A') || - setweight(to_tsvector('english_nostop', + setweight(to_tsvector('simple', coalesce(inmutable_array_to_string(projects_project.tags), '')), 'B') || - setweight(to_tsvector('english_nostop', + setweight(to_tsvector('simple', coalesce(projects_project.description, '')), 'C') """ diff --git a/taiga/projects/fixtures/initial_project_templates.json b/taiga/projects/fixtures/initial_project_templates.json index 6137792f..7d84b49f 100644 --- a/taiga/projects/fixtures/initial_project_templates.json +++ b/taiga/projects/fixtures/initial_project_templates.json @@ -17,16 +17,558 @@ "is_issues_activated": true, "videoconferences": null, "videoconferences_extra_data": "", - "default_options": "{\"epic_status\": \"New\", \"issue_status\": \"New\", \"task_status\": \"New\", \"points\": \"?\", \"issue_type\": \"Bug\", \"severity\": \"Normal\", \"priority\": \"Normal\", \"us_status\": \"New\"}", - "epic_statuses": "[{\"is_closed\": false, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"order\": 1}, {\"is_closed\": false, \"name\": \"Ready\", \"color\": \"#ff8a84\", \"slug\": \"ready\", \"order\": 2}, {\"is_closed\": false, \"name\": \"In progress\", \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"order\": 3}, {\"is_closed\": false, \"name\": \"Ready for test\", \"color\": \"#fcc000\", \"slug\": \"ready-for-test\", \"order\": 4}, {\"is_closed\": true, \"name\": \"Done\", \"color\": \"#669900\", \"slug\": \"done\", \"order\": 5}]", - "us_statuses": "[{\"is_archived\": false, \"name\": \"New\", \"slug\": \"new\", \"order\": 1, \"color\": \"#999999\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"Ready\", \"slug\": \"ready\", \"order\": 2, \"color\": \"#ff8a84\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"In progress\", \"slug\": \"in-progress\", \"order\": 3, \"color\": \"#ff9900\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"Ready for test\", \"slug\": \"ready-for-test\", \"order\": 4, \"color\": \"#fcc000\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"Done\", \"slug\": \"done\", \"order\": 5, \"color\": \"#669900\", \"wip_limit\": null, \"is_closed\": true}, {\"is_archived\": true, \"name\": \"Archived\", \"slug\": \"archived\", \"order\": 6, \"color\": \"#5c3566\", \"wip_limit\": null, \"is_closed\": true}]", - "points": "[{\"value\": null, \"name\": \"?\", \"order\": 1}, {\"value\": 0.0, \"name\": \"0\", \"order\": 2}, {\"value\": 0.5, \"name\": \"1/2\", \"order\": 3}, {\"value\": 1.0, \"name\": \"1\", \"order\": 4}, {\"value\": 2.0, \"name\": \"2\", \"order\": 5}, {\"value\": 3.0, \"name\": \"3\", \"order\": 6}, {\"value\": 5.0, \"name\": \"5\", \"order\": 7}, {\"value\": 8.0, \"name\": \"8\", \"order\": 8}, {\"value\": 10.0, \"name\": \"10\", \"order\": 9}, {\"value\": 13.0, \"name\": \"13\", \"order\": 10}, {\"value\": 20.0, \"name\": \"20\", \"order\": 11}, {\"value\": 40.0, \"name\": \"40\", \"order\": 12}]", - "task_statuses": "[{\"is_closed\": false, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"order\": 1}, {\"is_closed\": false, \"name\": \"In progress\", \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"order\": 2}, {\"is_closed\": true, \"name\": \"Ready for test\", \"color\": \"#ffcc00\", \"slug\": \"ready-for-test\", \"order\": 3}, {\"is_closed\": true, \"name\": \"Closed\", \"color\": \"#669900\", \"slug\": \"closed\", \"order\": 4}, {\"is_closed\": false, \"name\": \"Needs Info\", \"color\": \"#999999\", \"slug\": \"needs-info\", \"order\": 5}]", - "issue_statuses": "[{\"is_closed\": false, \"name\": \"New\", \"color\": \"#8C2318\", \"slug\": \"new\", \"order\": 1}, {\"is_closed\": false, \"name\": \"In progress\", \"color\": \"#5E8C6A\", \"slug\": \"in-progress\", \"order\": 2}, {\"is_closed\": true, \"name\": \"Ready for test\", \"color\": \"#88A65E\", \"slug\": \"ready-for-test\", \"order\": 3}, {\"is_closed\": true, \"name\": \"Closed\", \"color\": \"#BFB35A\", \"slug\": \"closed\", \"order\": 4}, {\"is_closed\": false, \"name\": \"Needs Info\", \"color\": \"#89BAB4\", \"slug\": \"needs-info\", \"order\": 5}, {\"is_closed\": true, \"name\": \"Rejected\", \"color\": \"#CC0000\", \"slug\": \"rejected\", \"order\": 6}, {\"is_closed\": false, \"name\": \"Postponed\", \"color\": \"#666666\", \"slug\": \"posponed\", \"order\": 7}]", - "issue_types": "[{\"name\": \"Bug\", \"color\": \"#89BAB4\", \"order\": 1}, {\"name\": \"Question\", \"color\": \"#ba89a8\", \"order\": 2}, {\"name\": \"Enhancement\", \"color\": \"#89a8ba\", \"order\": 3}]", - "priorities": "[{\"name\": \"Low\", \"color\": \"#666666\", \"order\": 1}, {\"name\": \"Normal\", \"color\": \"#669933\", \"order\": 3}, {\"name\": \"High\", \"color\": \"#CC0000\", \"order\": 5}]", - "severities": "[{\"name\": \"Wishlist\", \"color\": \"#666666\", \"order\": 1}, {\"name\": \"Minor\", \"color\": \"#669933\", \"order\": 2}, {\"name\": \"Normal\", \"color\": \"#0000FF\", \"order\": 3}, {\"name\": \"Important\", \"color\": \"#FFA500\", \"order\": 4}, {\"name\": \"Critical\", \"color\": \"#CC0000\", \"order\": 5}]", - "roles": "[{\"name\": \"UX\", \"computable\": true, \"slug\": \"ux\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 10}, {\"name\": \"Design\", \"computable\": true, \"slug\": \"design\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 20}, {\"name\": \"Front\", \"computable\": true, \"slug\": \"front\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 30}, {\"name\": \"Back\", \"computable\": true, \"slug\": \"back\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 40}, {\"name\": \"Product Owner\", \"computable\": false, \"slug\": \"product-owner\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 50}, {\"name\": \"Stakeholder\", \"computable\": false, \"slug\": \"stakeholder\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"view_milestones\", \"view_project\", \"view_tasks\", \"view_us\", \"modify_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 60}]" + "default_options": { + "points": "?", + "priority": "Normal", + "us_status": "New", + "issue_type": "Bug", + "epic_status": "New", + "severity": "Normal", + "task_status": "New", + "issue_status": "New" + }, + "epic_statuses": [ + { + "slug": "new", + "color": "#999999", + "name": "New", + "order": 1, + "is_closed": false + }, + { + "slug": "ready", + "color": "#ff8a84", + "name": "Ready", + "order": 2, + "is_closed": false + }, + { + "slug": "in-progress", + "color": "#ff9900", + "name": "In progress", + "order": 3, + "is_closed": false + }, + { + "slug": "ready-for-test", + "color": "#fcc000", + "name": "Ready for test", + "order": 4, + "is_closed": false + }, + { + "slug": "done", + "color": "#669900", + "name": "Done", + "order": 5, + "is_closed": true + } + ], + "us_statuses": [ + { + "slug": "new", + "name": "New", + "order": 1, + "is_archived": false, + "color": "#999999", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "ready", + "name": "Ready", + "order": 2, + "is_archived": false, + "color": "#ff8a84", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "in-progress", + "name": "In progress", + "order": 3, + "is_archived": false, + "color": "#ff9900", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "ready-for-test", + "name": "Ready for test", + "order": 4, + "is_archived": false, + "color": "#fcc000", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "done", + "name": "Done", + "order": 5, + "is_archived": false, + "color": "#669900", + "is_closed": true, + "wip_limit": null + }, + { + "slug": "archived", + "name": "Archived", + "order": 6, + "is_archived": true, + "color": "#5c3566", + "is_closed": true, + "wip_limit": null + } + ], + "points": [ + { + "name": "?", + "order": 1, + "value": null + }, + { + "name": "0", + "order": 2, + "value": 0.0 + }, + { + "name": "1/2", + "order": 3, + "value": 0.5 + }, + { + "name": "1", + "order": 4, + "value": 1.0 + }, + { + "name": "2", + "order": 5, + "value": 2.0 + }, + { + "name": "3", + "order": 6, + "value": 3.0 + }, + { + "name": "5", + "order": 7, + "value": 5.0 + }, + { + "name": "8", + "order": 8, + "value": 8.0 + }, + { + "name": "10", + "order": 9, + "value": 10.0 + }, + { + "name": "13", + "order": 10, + "value": 13.0 + }, + { + "name": "20", + "order": 11, + "value": 20.0 + }, + { + "name": "40", + "order": 12, + "value": 40.0 + } + ], + "task_statuses": [ + { + "slug": "new", + "color": "#999999", + "name": "New", + "order": 1, + "is_closed": false + }, + { + "slug": "in-progress", + "color": "#ff9900", + "name": "In progress", + "order": 2, + "is_closed": false + }, + { + "slug": "ready-for-test", + "color": "#ffcc00", + "name": "Ready for test", + "order": 3, + "is_closed": true + }, + { + "slug": "closed", + "color": "#669900", + "name": "Closed", + "order": 4, + "is_closed": true + }, + { + "slug": "needs-info", + "color": "#999999", + "name": "Needs Info", + "order": 5, + "is_closed": false + } + ], + "issue_statuses": [ + { + "slug": "new", + "color": "#8C2318", + "name": "New", + "order": 1, + "is_closed": false + }, + { + "slug": "in-progress", + "color": "#5E8C6A", + "name": "In progress", + "order": 2, + "is_closed": false + }, + { + "slug": "ready-for-test", + "color": "#88A65E", + "name": "Ready for test", + "order": 3, + "is_closed": true + }, + { + "slug": "closed", + "color": "#BFB35A", + "name": "Closed", + "order": 4, + "is_closed": true + }, + { + "slug": "needs-info", + "color": "#89BAB4", + "name": "Needs Info", + "order": 5, + "is_closed": false + }, + { + "slug": "rejected", + "color": "#CC0000", + "name": "Rejected", + "order": 6, + "is_closed": true + }, + { + "slug": "posponed", + "color": "#666666", + "name": "Postponed", + "order": 7, + "is_closed": false + } + ], + "issue_types": [ + { + "color": "#89BAB4", + "name": "Bug", + "order": 1 + }, + { + "color": "#ba89a8", + "name": "Question", + "order": 2 + }, + { + "color": "#89a8ba", + "name": "Enhancement", + "order": 3 + } + ], + "priorities": [ + { + "color": "#666666", + "name": "Low", + "order": 1 + }, + { + "color": "#669933", + "name": "Normal", + "order": 3 + }, + { + "color": "#CC0000", + "name": "High", + "order": 5 + } + ], + "severities": [ + { + "color": "#666666", + "name": "Wishlist", + "order": 1 + }, + { + "color": "#669933", + "name": "Minor", + "order": 2 + }, + { + "color": "#0000FF", + "name": "Normal", + "order": 3 + }, + { + "color": "#FFA500", + "name": "Important", + "order": 4 + }, + { + "color": "#CC0000", + "name": "Critical", + "order": 5 + } + ], + "roles": [ + { + "slug": "ux", + "computable": true, + "name": "UX", + "order": 10, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "design", + "computable": true, + "name": "Design", + "order": 20, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "front", + "computable": true, + "name": "Front", + "order": 30, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "back", + "computable": true, + "name": "Back", + "order": 40, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "product-owner", + "computable": false, + "name": "Product Owner", + "order": 50, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "stakeholder", + "computable": false, + "name": "Stakeholder", + "order": 60, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "view_milestones", + "view_project", + "view_tasks", + "view_us", + "modify_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + } + ], + "epic_custom_attributes": [], + "us_custom_attributes": [], + "task_custom_attributes": [], + "issue_custom_attributes": [] } }, { @@ -47,16 +589,558 @@ "is_issues_activated": false, "videoconferences": null, "videoconferences_extra_data": "", - "default_options": "{\"epic_status\": \"New\", \"issue_status\": \"New\", \"task_status\": \"New\", \"points\": \"?\", \"issue_type\": \"Bug\", \"severity\": \"Normal\", \"priority\": \"Normal\", \"us_status\": \"New\"}", - "epic_statuses": "[{\"is_closed\": false, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"order\": 1}, {\"is_closed\": false, \"name\": \"Ready\", \"color\": \"#ff8a84\", \"slug\": \"ready\", \"order\": 2}, {\"is_closed\": false, \"name\": \"In progress\", \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"order\": 3}, {\"is_closed\": false, \"name\": \"Ready for test\", \"color\": \"#fcc000\", \"slug\": \"ready-for-test\", \"order\": 4}, {\"is_closed\": true, \"name\": \"Done\", \"color\": \"#669900\", \"slug\": \"done\", \"order\": 5}]", - "us_statuses": "[{\"is_archived\": false, \"name\": \"New\", \"slug\": \"new\", \"order\": 1, \"color\": \"#999999\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"Ready\", \"slug\": \"ready\", \"order\": 2, \"color\": \"#f57900\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"In progress\", \"slug\": \"in-progress\", \"order\": 3, \"color\": \"#729fcf\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"Ready for test\", \"slug\": \"ready-for-test\", \"order\": 4, \"color\": \"#4e9a06\", \"wip_limit\": null, \"is_closed\": false}, {\"is_archived\": false, \"name\": \"Done\", \"slug\": \"done\", \"order\": 5, \"color\": \"#cc0000\", \"wip_limit\": null, \"is_closed\": true}, {\"is_archived\": true, \"name\": \"Archived\", \"slug\": \"archived\", \"order\": 6, \"color\": \"#5c3566\", \"wip_limit\": null, \"is_closed\": true}]", - "points": "[{\"value\": null, \"name\": \"?\", \"order\": 1}, {\"value\": 0.0, \"name\": \"0\", \"order\": 2}, {\"value\": 0.5, \"name\": \"1/2\", \"order\": 3}, {\"value\": 1.0, \"name\": \"1\", \"order\": 4}, {\"value\": 2.0, \"name\": \"2\", \"order\": 5}, {\"value\": 3.0, \"name\": \"3\", \"order\": 6}, {\"value\": 5.0, \"name\": \"5\", \"order\": 7}, {\"value\": 8.0, \"name\": \"8\", \"order\": 8}, {\"value\": 10.0, \"name\": \"10\", \"order\": 9}, {\"value\": 13.0, \"name\": \"13\", \"order\": 10}, {\"value\": 20.0, \"name\": \"20\", \"order\": 11}, {\"value\": 40.0, \"name\": \"40\", \"order\": 12}]", - "task_statuses": "[{\"is_closed\": false, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"order\": 1}, {\"is_closed\": false, \"name\": \"In progress\", \"color\": \"#729fcf\", \"slug\": \"in-progress\", \"order\": 2}, {\"is_closed\": true, \"name\": \"Ready for test\", \"color\": \"#f57900\", \"slug\": \"ready-for-test\", \"order\": 3}, {\"is_closed\": true, \"name\": \"Closed\", \"color\": \"#4e9a06\", \"slug\": \"closed\", \"order\": 4}, {\"is_closed\": false, \"name\": \"Needs Info\", \"color\": \"#cc0000\", \"slug\": \"needs-info\", \"order\": 5}]", - "issue_statuses": "[{\"is_closed\": false, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"order\": 1}, {\"is_closed\": false, \"name\": \"In progress\", \"color\": \"#729fcf\", \"slug\": \"in-progress\", \"order\": 2}, {\"is_closed\": true, \"name\": \"Ready for test\", \"color\": \"#f57900\", \"slug\": \"ready-for-test\", \"order\": 3}, {\"is_closed\": true, \"name\": \"Closed\", \"color\": \"#4e9a06\", \"slug\": \"closed\", \"order\": 4}, {\"is_closed\": false, \"name\": \"Needs Info\", \"color\": \"#cc0000\", \"slug\": \"needs-info\", \"order\": 5}, {\"is_closed\": true, \"name\": \"Rejected\", \"color\": \"#d3d7cf\", \"slug\": \"rejected\", \"order\": 6}, {\"is_closed\": false, \"name\": \"Postponed\", \"color\": \"#75507b\", \"slug\": \"posponed\", \"order\": 7}]", - "issue_types": "[{\"name\": \"Bug\", \"color\": \"#cc0000\", \"order\": 1}, {\"name\": \"Question\", \"color\": \"#729fcf\", \"order\": 2}, {\"name\": \"Enhancement\", \"color\": \"#4e9a06\", \"order\": 3}]", - "priorities": "[{\"name\": \"Low\", \"color\": \"#999999\", \"order\": 1}, {\"name\": \"Normal\", \"color\": \"#4e9a06\", \"order\": 3}, {\"name\": \"High\", \"color\": \"#CC0000\", \"order\": 5}]", - "severities": "[{\"name\": \"Wishlist\", \"color\": \"#999999\", \"order\": 1}, {\"name\": \"Minor\", \"color\": \"#729fcf\", \"order\": 2}, {\"name\": \"Normal\", \"color\": \"#4e9a06\", \"order\": 3}, {\"name\": \"Important\", \"color\": \"#f57900\", \"order\": 4}, {\"name\": \"Critical\", \"color\": \"#CC0000\", \"order\": 5}]", - "roles": "[{\"name\": \"UX\", \"computable\": true, \"slug\": \"ux\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 10}, {\"name\": \"Design\", \"computable\": true, \"slug\": \"design\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 20}, {\"name\": \"Front\", \"computable\": true, \"slug\": \"front\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 30}, {\"name\": \"Back\", \"computable\": true, \"slug\": \"back\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 40}, {\"name\": \"Product Owner\", \"computable\": false, \"slug\": \"product-owner\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"add_epic\", \"modify_epic\", \"delete_epic\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 50}, {\"name\": \"Stakeholder\", \"computable\": false, \"slug\": \"stakeholder\", \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"view_milestones\", \"view_project\", \"view_tasks\", \"view_us\", \"modify_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\", \"view_epics\", \"comment_epic\", \"comment_us\", \"comment_task\", \"comment_issue\", \"comment_wiki_page\"], \"order\": 60}]" + "default_options": { + "points": "?", + "priority": "Normal", + "us_status": "New", + "issue_type": "Bug", + "epic_status": "New", + "severity": "Normal", + "task_status": "New", + "issue_status": "New" + }, + "epic_statuses": [ + { + "slug": "new", + "color": "#999999", + "name": "New", + "order": 1, + "is_closed": false + }, + { + "slug": "ready", + "color": "#ff8a84", + "name": "Ready", + "order": 2, + "is_closed": false + }, + { + "slug": "in-progress", + "color": "#ff9900", + "name": "In progress", + "order": 3, + "is_closed": false + }, + { + "slug": "ready-for-test", + "color": "#fcc000", + "name": "Ready for test", + "order": 4, + "is_closed": false + }, + { + "slug": "done", + "color": "#669900", + "name": "Done", + "order": 5, + "is_closed": true + } + ], + "us_statuses": [ + { + "slug": "new", + "name": "New", + "order": 1, + "is_archived": false, + "color": "#999999", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "ready", + "name": "Ready", + "order": 2, + "is_archived": false, + "color": "#f57900", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "in-progress", + "name": "In progress", + "order": 3, + "is_archived": false, + "color": "#729fcf", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "ready-for-test", + "name": "Ready for test", + "order": 4, + "is_archived": false, + "color": "#4e9a06", + "is_closed": false, + "wip_limit": null + }, + { + "slug": "done", + "name": "Done", + "order": 5, + "is_archived": false, + "color": "#cc0000", + "is_closed": true, + "wip_limit": null + }, + { + "slug": "archived", + "name": "Archived", + "order": 6, + "is_archived": true, + "color": "#5c3566", + "is_closed": true, + "wip_limit": null + } + ], + "points": [ + { + "name": "?", + "order": 1, + "value": null + }, + { + "name": "0", + "order": 2, + "value": 0.0 + }, + { + "name": "1/2", + "order": 3, + "value": 0.5 + }, + { + "name": "1", + "order": 4, + "value": 1.0 + }, + { + "name": "2", + "order": 5, + "value": 2.0 + }, + { + "name": "3", + "order": 6, + "value": 3.0 + }, + { + "name": "5", + "order": 7, + "value": 5.0 + }, + { + "name": "8", + "order": 8, + "value": 8.0 + }, + { + "name": "10", + "order": 9, + "value": 10.0 + }, + { + "name": "13", + "order": 10, + "value": 13.0 + }, + { + "name": "20", + "order": 11, + "value": 20.0 + }, + { + "name": "40", + "order": 12, + "value": 40.0 + } + ], + "task_statuses": [ + { + "slug": "new", + "color": "#999999", + "name": "New", + "order": 1, + "is_closed": false + }, + { + "slug": "in-progress", + "color": "#729fcf", + "name": "In progress", + "order": 2, + "is_closed": false + }, + { + "slug": "ready-for-test", + "color": "#f57900", + "name": "Ready for test", + "order": 3, + "is_closed": true + }, + { + "slug": "closed", + "color": "#4e9a06", + "name": "Closed", + "order": 4, + "is_closed": true + }, + { + "slug": "needs-info", + "color": "#cc0000", + "name": "Needs Info", + "order": 5, + "is_closed": false + } + ], + "issue_statuses": [ + { + "slug": "new", + "color": "#999999", + "name": "New", + "order": 1, + "is_closed": false + }, + { + "slug": "in-progress", + "color": "#729fcf", + "name": "In progress", + "order": 2, + "is_closed": false + }, + { + "slug": "ready-for-test", + "color": "#f57900", + "name": "Ready for test", + "order": 3, + "is_closed": true + }, + { + "slug": "closed", + "color": "#4e9a06", + "name": "Closed", + "order": 4, + "is_closed": true + }, + { + "slug": "needs-info", + "color": "#cc0000", + "name": "Needs Info", + "order": 5, + "is_closed": false + }, + { + "slug": "rejected", + "color": "#d3d7cf", + "name": "Rejected", + "order": 6, + "is_closed": true + }, + { + "slug": "posponed", + "color": "#75507b", + "name": "Postponed", + "order": 7, + "is_closed": false + } + ], + "issue_types": [ + { + "color": "#cc0000", + "name": "Bug", + "order": 1 + }, + { + "color": "#729fcf", + "name": "Question", + "order": 2 + }, + { + "color": "#4e9a06", + "name": "Enhancement", + "order": 3 + } + ], + "priorities": [ + { + "color": "#999999", + "name": "Low", + "order": 1 + }, + { + "color": "#4e9a06", + "name": "Normal", + "order": 3 + }, + { + "color": "#CC0000", + "name": "High", + "order": 5 + } + ], + "severities": [ + { + "color": "#999999", + "name": "Wishlist", + "order": 1 + }, + { + "color": "#729fcf", + "name": "Minor", + "order": 2 + }, + { + "color": "#4e9a06", + "name": "Normal", + "order": 3 + }, + { + "color": "#f57900", + "name": "Important", + "order": 4 + }, + { + "color": "#CC0000", + "name": "Critical", + "order": 5 + } + ], + "roles": [ + { + "slug": "ux", + "computable": true, + "name": "UX", + "order": 10, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "design", + "computable": true, + "name": "Design", + "order": 20, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "front", + "computable": true, + "name": "Front", + "order": 30, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "back", + "computable": true, + "name": "Back", + "order": 40, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "product-owner", + "computable": false, + "name": "Product Owner", + "order": 50, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "add_milestone", + "modify_milestone", + "delete_milestone", + "view_milestones", + "view_project", + "add_task", + "modify_task", + "delete_task", + "view_tasks", + "add_us", + "modify_us", + "delete_us", + "view_us", + "add_wiki_page", + "modify_wiki_page", + "delete_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "add_epic", + "modify_epic", + "delete_epic", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + }, + { + "slug": "stakeholder", + "computable": false, + "name": "Stakeholder", + "order": 60, + "permissions": [ + "add_issue", + "modify_issue", + "delete_issue", + "view_issues", + "view_milestones", + "view_project", + "view_tasks", + "view_us", + "modify_wiki_page", + "view_wiki_pages", + "add_wiki_link", + "delete_wiki_link", + "view_wiki_links", + "view_epics", + "comment_epic", + "comment_us", + "comment_task", + "comment_issue", + "comment_wiki_page" + ] + } + ], + "epic_custom_attributes": [], + "us_custom_attributes": [], + "task_custom_attributes": [], + "issue_custom_attributes": [] } } ] diff --git a/taiga/projects/history/api.py b/taiga/projects/history/api.py index 17c2fa83..52dabcb0 100644 --- a/taiga/projects/history/api.py +++ b/taiga/projects/history/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/history/choices.py b/taiga/projects/history/choices.py index 411e0f55..0a85b9d4 100644 --- a/taiga/projects/history/choices.py +++ b/taiga/projects/history/choices.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/history/freeze_impl.py b/taiga/projects/history/freeze_impl.py index 027691b6..c3394baa 100644 --- a/taiga/projects/history/freeze_impl.py +++ b/taiga/projects/history/freeze_impl.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -208,13 +208,14 @@ def extract_attachments(obj) -> list: @as_tuple def extract_epic_custom_attributes(obj) -> list: with suppress(ObjectDoesNotExist): - custom_attributes_values = obj.custom_attributes_values.attributes_values + custom_attributes_values = obj.custom_attributes_values.attributes_values for attr in obj.project.epiccustomattributes.all(): with suppress(KeyError): value = custom_attributes_values[str(attr.id)] yield {"id": attr.id, "name": attr.name, - "value": value} + "value": value, + "type": attr.type} @as_tuple @@ -226,7 +227,8 @@ def extract_user_story_custom_attributes(obj) -> list: value = custom_attributes_values[str(attr.id)] yield {"id": attr.id, "name": attr.name, - "value": value} + "value": value, + "type": attr.type} @as_tuple @@ -238,7 +240,8 @@ def extract_task_custom_attributes(obj) -> list: value = custom_attributes_values[str(attr.id)] yield {"id": attr.id, "name": attr.name, - "value": value} + "value": value, + "type": attr.type} @as_tuple @@ -250,7 +253,8 @@ def extract_issue_custom_attributes(obj) -> list: value = custom_attributes_values[str(attr.id)] yield {"id": attr.id, "name": attr.name, - "value": value} + "value": value, + "type": attr.type} def project_freezer(project) -> dict: diff --git a/taiga/projects/history/migrations/0001_initial.py b/taiga/projects/history/migrations/0001_initial.py index 053149d7..5113e110 100644 --- a/taiga/projects/history/migrations/0001_initial.py +++ b/taiga/projects/history/migrations/0001_initial.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import models, migrations import taiga.projects.history.models import django.utils.timezone -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -17,14 +17,14 @@ class Migration(migrations.Migration): name='HistoryEntry', fields=[ ('id', models.CharField(primary_key=True, unique=True, max_length=255, serialize=False, default=taiga.projects.history.models._generate_uuid, editable=False)), - ('user', django_pgjson.fields.JsonField(default=None, blank=True, null=True)), + ('user', taiga.base.db.models.fields.JSONField(default=None, blank=True, null=True)), ('created_at', models.DateTimeField(default=django.utils.timezone.now)), ('type', models.SmallIntegerField(choices=[(1, 'Change'), (2, 'Create'), (3, 'Delete')])), ('is_snapshot', models.BooleanField(default=False)), ('key', models.CharField(max_length=255, default=None, blank=True, null=True)), - ('diff', django_pgjson.fields.JsonField(default=None, null=True)), - ('snapshot', django_pgjson.fields.JsonField(default=None, null=True)), - ('values', django_pgjson.fields.JsonField(default=None, null=True)), + ('diff', taiga.base.db.models.fields.JSONField(default=None, null=True)), + ('snapshot', taiga.base.db.models.fields.JSONField(default=None, null=True)), + ('values', taiga.base.db.models.fields.JSONField(default=None, null=True)), ('comment', models.TextField(blank=True)), ('comment_html', models.TextField(blank=True)), ], diff --git a/taiga/projects/history/migrations/0003_auto_20140917_1405.py b/taiga/projects/history/migrations/0003_auto_20140917_1405.py index bb4378fa..13d22518 100644 --- a/taiga/projects/history/migrations/0003_auto_20140917_1405.py +++ b/taiga/projects/history/migrations/0003_auto_20140917_1405.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings -import django_pgjson.fields +import taiga.base.db.models.fields def change_fk_with_tuple_pk_and_name(apps, schema_editor): HistoryEntry = apps.get_model("history", "HistoryEntry") @@ -31,7 +31,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='historyentry', name='delete_comment_user', - field=django_pgjson.fields.JsonField(null=True, blank=True, default=None), + field=taiga.base.db.models.fields.JSONField(null=True, blank=True, default=None), preserve_default=True, ), diff --git a/taiga/projects/history/migrations/0006_fix_json_field_not_null.py b/taiga/projects/history/migrations/0006_fix_json_field_not_null.py index 8c8ae187..a72d1cce 100644 --- a/taiga/projects/history/migrations/0006_fix_json_field_not_null.py +++ b/taiga/projects/history/migrations/0006_fix_json_field_not_null.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from django.db import models, migrations diff --git a/taiga/projects/history/migrations/0008_auto_20150508_1028.py b/taiga/projects/history/migrations/0008_auto_20150508_1028.py index bf82d6b3..5b4b07fa 100644 --- a/taiga/projects/history/migrations/0008_auto_20150508_1028.py +++ b/taiga/projects/history/migrations/0008_auto_20150508_1028.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -15,19 +15,19 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='historyentry', name='diff', - field=django_pgjson.fields.JsonField(null=True, default=None, blank=True), + field=taiga.base.db.models.fields.JSONField(null=True, default=None, blank=True), preserve_default=True, ), migrations.AlterField( model_name='historyentry', name='snapshot', - field=django_pgjson.fields.JsonField(null=True, default=None, blank=True), + field=taiga.base.db.models.fields.JSONField(null=True, default=None, blank=True), preserve_default=True, ), migrations.AlterField( model_name='historyentry', name='values', - field=django_pgjson.fields.JsonField(null=True, default=None, blank=True), + field=taiga.base.db.models.fields.JSONField(null=True, default=None, blank=True), preserve_default=True, ), ] diff --git a/taiga/projects/history/migrations/0009_auto_20160512_1110.py b/taiga/projects/history/migrations/0009_auto_20160512_1110.py index 0cf39023..0e293806 100644 --- a/taiga/projects/history/migrations/0009_auto_20160512_1110.py +++ b/taiga/projects/history/migrations/0009_auto_20160512_1110.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import migrations, models -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -16,7 +16,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='historyentry', name='comment_versions', - field=django_pgjson.fields.JsonField(blank=True, default=None, null=True), + field=taiga.base.db.models.fields.JSONField(blank=True, default=None, null=True), ), migrations.AddField( model_name='historyentry', diff --git a/taiga/projects/history/migrations/0013_historyentry_values_diff_cache.py b/taiga/projects/history/migrations/0013_historyentry_values_diff_cache.py index 1fe73a96..625d2343 100644 --- a/taiga/projects/history/migrations/0013_historyentry_values_diff_cache.py +++ b/taiga/projects/history/migrations/0013_historyentry_values_diff_cache.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='historyentry', name='values_diff_cache', - field=django_pgjson.fields.JsonField(blank=True, default=None, null=True), + field=taiga.base.db.models.fields.JSONField(blank=True, default=None, null=True), ), ] diff --git a/taiga/projects/history/migrations/0014_json_to_jsonb.py b/taiga/projects/history/migrations/0014_json_to_jsonb.py new file mode 100644 index 00000000..940217d9 --- /dev/null +++ b/taiga/projects/history/migrations/0014_json_to_jsonb.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:34 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + + +class Migration(migrations.Migration): + + dependencies = [ + ('history', '0013_historyentry_values_diff_cache'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "history_historyentry" + ALTER COLUMN "delete_comment_user" + TYPE jsonb + USING regexp_replace("delete_comment_user"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "comment_versions" + TYPE jsonb + USING regexp_replace("comment_versions"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "values_diff_cache" + TYPE jsonb + USING regexp_replace("values_diff_cache"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "user" + TYPE jsonb + USING regexp_replace("user"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "diff" + TYPE jsonb + USING regexp_replace("diff"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "snapshot" + TYPE jsonb + USING regexp_replace("snapshot"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "values" + TYPE jsonb + USING regexp_replace("values"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """, + reverse_sql=migrations.RunSQL.noop + ), + ] diff --git a/taiga/projects/history/mixins.py b/taiga/projects/history/mixins.py index c8f67305..9f4cdf8c 100644 --- a/taiga/projects/history/mixins.py +++ b/taiga/projects/history/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,6 +20,9 @@ import warnings from .services import take_snapshot from taiga.projects.notifications import services as notifications_services +from taiga.base.api import serializers +from taiga.base.fields import MethodField + class HistoryResourceMixin(object): """ @@ -79,3 +82,11 @@ class HistoryResourceMixin(object): def pre_delete(self, obj): self.persist_history_snapshot(obj, delete=True) super().pre_delete(obj) + + +class TotalCommentsSerializerMixin(serializers.LightSerializer): + total_comments = MethodField() + + def get_total_comments(self, obj): + # The "total_comments" attribute is attached in the get_queryset of the viewset. + return getattr(obj, "total_comments", 0) or 0 diff --git a/taiga/projects/history/models.py b/taiga/projects/history/models.py index c4155eee..88fd3c57 100644 --- a/taiga/projects/history/models.py +++ b/taiga/projects/history/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -22,7 +22,7 @@ from django.utils import timezone from django.db import models from django.contrib.auth import get_user_model from django.utils.functional import cached_property -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from taiga.mdrender.service import get_diff_of_htmls @@ -30,6 +30,7 @@ from .choices import HistoryType from .choices import HISTORY_TYPE_CHOICES from taiga.base.utils.diff import make_diff as make_diff_from_dicts +from taiga.projects.custom_attributes.choices import TEXT_TYPE # This keys has been removed from freeze_impl so we can have objects where the # previous diff has value for the attribute and we want to prevent their propagation @@ -52,32 +53,32 @@ class HistoryEntry(models.Model): editable=False, default=_generate_uuid) project = models.ForeignKey("projects.Project") - user = JsonField(null=True, blank=True, default=None) + user = JSONField(null=True, blank=True, default=None) created_at = models.DateTimeField(default=timezone.now) type = models.SmallIntegerField(choices=HISTORY_TYPE_CHOICES) key = models.CharField(max_length=255, null=True, default=None, blank=True, db_index=True) # Stores the last diff - diff = JsonField(null=True, blank=True, default=None) + diff = JSONField(null=True, blank=True, default=None) # Stores the values_diff cache - values_diff_cache = JsonField(null=True, blank=True, default=None) + values_diff_cache = JSONField(null=True, blank=True, default=None) # Stores the last complete frozen object snapshot - snapshot = JsonField(null=True, blank=True, default=None) + snapshot = JSONField(null=True, blank=True, default=None) # Stores a values of all identifiers used in - values = JsonField(null=True, blank=True, default=None) + values = JSONField(null=True, blank=True, default=None) # Stores a comment comment = models.TextField(blank=True) comment_html = models.TextField(blank=True) delete_comment_date = models.DateTimeField(null=True, blank=True, default=None) - delete_comment_user = JsonField(null=True, blank=True, default=None) + delete_comment_user = JSONField(null=True, blank=True, default=None) # Historic version of comments - comment_versions = JsonField(null=True, blank=True, default=None) + comment_versions = JSONField(null=True, blank=True, default=None) edit_comment_date = models.DateTimeField(null=True, blank=True, default=None) # Flag for mark some history entries as @@ -250,15 +251,25 @@ class HistoryEntry(models.Model): changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid], excluded_keys=("name")) + newcustattr = newcustattrs.get(aid, {}) if changes: + change_type = newcustattr.get("type", TEXT_TYPE) + old_value = oldcustattrs[aid].get("value", "") + new_value = newcustattrs[aid].get("value", "") + value_diff = get_diff_of_htmls(old_value, new_value) change = { - "name": newcustattrs.get(aid, {}).get("name", ""), - "changes": changes + "name": newcustattr.get("name", ""), + "changes": changes, + "type": change_type, + "value_diff": value_diff } custom_attributes["changed"].append(change) elif aid in oldcustattrs and aid not in newcustattrs: custom_attributes["deleted"].append(oldcustattrs[aid]) elif aid not in oldcustattrs and aid in newcustattrs: + new_value = newcustattrs[aid].get("value", "") + value_diff = get_diff_of_htmls("", new_value) + newcustattrs[aid]["value_diff"] = value_diff custom_attributes["new"].append(newcustattrs[aid]) if custom_attributes["new"] or custom_attributes["changed"] or custom_attributes["deleted"]: diff --git a/taiga/projects/history/permissions.py b/taiga/projects/history/permissions.py index 40e4f98b..3c8ad53f 100644 --- a/taiga/projects/history/permissions.py +++ b/taiga/projects/history/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/history/serializers.py b/taiga/projects/history/serializers.py index 0f2dc658..bf2ed595 100644 --- a/taiga/projects/history/serializers.py +++ b/taiga/projects/history/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,7 +17,7 @@ # along with this program. If not, see . from taiga.base.api import serializers -from taiga.base.fields import I18NJsonField, Field, MethodField +from taiga.base.fields import I18NJSONField, Field, MethodField from taiga.users.services import get_user_photo_url from taiga.users.gravatar import get_user_gravatar_id @@ -35,8 +35,8 @@ class HistoryEntrySerializer(serializers.LightSerializer): diff = Field() snapshot = Field() values = Field() - values_diff = I18NJsonField() - comment = I18NJsonField() + values_diff = I18NJSONField() + comment = I18NJSONField() comment_html = Field() delete_comment_date = Field() delete_comment_user = Field() diff --git a/taiga/projects/history/services.py b/taiga/projects/history/services.py index 1bf27dee..27be7cae 100644 --- a/taiga/projects/history/services.py +++ b/taiga/projects/history/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja b/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja index eadca5c3..9d3473e7 100644 --- a/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja +++ b/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja @@ -5,8 +5,10 @@ "blocked_note_html", "content", "content_html", + "epics_order", "backlog_order", "kanban_order", + "sprint_order", "taskboard_order", "us_order", "custom_attributes", @@ -170,40 +172,58 @@ {# CUSTOM ATTRIBUTES #} {% if values.new %} {% for attr in values['new']%} -
- - - - - - {% endfor %} - {% endif %} - {% if values.changed %} - {% for attr in values['changed'] %} - {% if attr.changes.value%} + {% if attr.type == "richtext" %} + + + + {% else %} - {% endif %} {% endfor %} {% endif %} + {% if values.changed %} + {% for attr in values['changed'] %} + {% if attr.changes.value%} + {% if attr.type == "richtext" %} + + + + {% else %} + + + + + + + + {% endif %} + {% endif %} + {% endfor %} + {% endif %} {% if values.deleted %} {% for attr in values['deleted']%} diff --git a/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja b/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja index ff0a1b0d..eabb019c 100644 --- a/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja +++ b/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja @@ -3,8 +3,10 @@ "description_html", "content_diff", "content_html", + "epics_order", "backlog_order", "kanban_order", + "sprint_order", "taskboard_order", "us_order", "blocked_note_diff", @@ -66,27 +68,27 @@ {% elif field_name == "custom_attributes" %} {# CUSTOM ATTRIBUTES #} - {% elif field_name == "attachments" %} - {% if values.new %} - {% for attr in values['new']%} + {% if values.new %} + {% for attr in values['new']%} + - {{ attr.name }}: * {{ attr.value }} - {% endfor %} - {% endif %} + {% endfor %} + {% endif %} - {% if values.changed %} - {% for attr in values['changed'] %} + {% if values.changed %} + {% for attr in values['changed'] %} - {{ attr.name }}: * {{ _("From:") }} {{ attr.changes.value.0 }} * {{ _("To:") }} {{ attr.changes.value.1 }} - {% endfor %} - {% endif %} + {% endfor %} + {% endif %} - {% if values.deleted %} - {% for attr in values['deleted']%} + {% if values.deleted %} + {% for attr in values['deleted']%} - {{ attr.name }}: {{ _("-deleted-") }} * {{ attr.value }} - {% endfor %} - {% endif %} + {% endfor %} + {% endif %} {% endif %} {% endfor %} diff --git a/taiga/projects/history/templatetags/functions.py b/taiga/projects/history/templatetags/functions.py index 8eefa6c1..e405e40c 100644 --- a/taiga/projects/history/templatetags/functions.py +++ b/taiga/projects/history/templatetags/functions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/history/utils.py b/taiga/projects/history/utils.py new file mode 100644 index 00000000..b4bd3980 --- /dev/null +++ b/taiga/projects/history/utils.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 . + +from taiga.projects.history.services import get_typename_for_model_class + + +def attach_total_comments_to_queryset(queryset, as_field="total_comments"): + """Attach a total comments counter to each object of the queryset. + + :param queryset: A Django projects queryset object. + :param as_field: Attach the counter as an attribute with this name. + + :return: Queryset object with the additional `as_field` field. + """ + model = queryset.model + sql = """ + SELECT COUNT(history_historyentry.id) + FROM history_historyentry + WHERE history_historyentry.key = CONCAT('{key_prefix}', {tbl}.id) AND + history_historyentry.comment is not null AND + history_historyentry.comment != '' + """ + + typename = get_typename_for_model_class(model) + + sql = sql.format(tbl=model._meta.db_table, key_prefix="{}:".format(typename)) + + queryset = queryset.extra(select={as_field: sql}) + return queryset diff --git a/taiga/projects/issues/__init__.py b/taiga/projects/issues/__init__.py index a259bd6b..a6a1368b 100644 --- a/taiga/projects/issues/__init__.py +++ b/taiga/projects/issues/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/admin.py b/taiga/projects/issues/admin.py index 06be1a03..9ce0a767 100644 --- a/taiga/projects/issues/admin.py +++ b/taiga/projects/issues/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -39,11 +39,11 @@ class IssueAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["status", "priority", "severity", "type", "milestone"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( project=self.obj.project) elif (db_field.name in ["owner", "assigned_to"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/issues/api.py b/taiga/projects/issues/api.py index a7479d64..7a59e784 100644 --- a/taiga/projects/issues/api.py +++ b/taiga/projects/issues/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/apps.py b/taiga/projects/issues/apps.py index de4de986..81a5ddce 100644 --- a/taiga/projects/issues/apps.py +++ b/taiga/projects/issues/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/migrations/0001_initial.py b/taiga/projects/issues/migrations/0001_initial.py index ba8814ff..afb9cca2 100644 --- a/taiga/projects/issues/migrations/0001_initial.py +++ b/taiga/projects/issues/migrations/0001_initial.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings import django.utils.timezone -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -20,7 +20,7 @@ class Migration(migrations.Migration): name='Issue', fields=[ ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), - ('tags', djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='tags')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=[], null=True, size=None, verbose_name='tags')), ('version', models.IntegerField(default=1, verbose_name='version')), ('is_blocked', models.BooleanField(default=False, verbose_name='is blocked')), ('blocked_note', models.TextField(blank=True, default='', verbose_name='blocked note')), diff --git a/taiga/projects/issues/migrations/0002_issue_external_reference.py b/taiga/projects/issues/migrations/0002_issue_external_reference.py index e88ddab2..60b8845c 100644 --- a/taiga/projects/issues/migrations/0002_issue_external_reference.py +++ b/taiga/projects/issues/migrations/0002_issue_external_reference.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='issue', name='external_reference', - field=djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='external reference'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=False, null=False), blank=True, default=None, null=True, size=None, verbose_name='external reference'), preserve_default=True, ), ] diff --git a/taiga/projects/issues/models.py b/taiga/projects/issues/models.py index 8f9c18a3..c2f3696b 100644 --- a/taiga/projects/issues/models.py +++ b/taiga/projects/issues/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/permissions.py b/taiga/projects/issues/permissions.py index d86f697c..6218e8e6 100644 --- a/taiga/projects/issues/permissions.py +++ b/taiga/projects/issues/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/serializers.py b/taiga/projects/issues/serializers.py index c8d3b5dc..80057dcc 100644 --- a/taiga/projects/issues/serializers.py +++ b/taiga/projects/issues/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -22,6 +22,7 @@ from taiga.base.neighbors import NeighborsSerializerMixin from taiga.mdrender.service import render as mdrender from taiga.projects.mixins.serializers import OwnerExtraInfoSerializerMixin +from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin from taiga.projects.mixins.serializers import AssignedToExtraInfoSerializerMixin from taiga.projects.mixins.serializers import StatusExtraInfoSerializerMixin from taiga.projects.notifications.mixins import WatchedResourceSerializer @@ -31,7 +32,7 @@ from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin class IssueListSerializer(VoteResourceSerializerMixin, WatchedResourceSerializer, OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin, - StatusExtraInfoSerializerMixin, + StatusExtraInfoSerializerMixin, ProjectExtraInfoSerializerMixin, TaggedInProjectResourceSerializer, serializers.LightSerializer): id = Field() ref = Field() diff --git a/taiga/projects/issues/services.py b/taiga/projects/issues/services.py index ad87a61e..a2646d78 100644 --- a/taiga/projects/issues/services.py +++ b/taiga/projects/issues/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/signals.py b/taiga/projects/issues/signals.py index 91bb5ebc..ce2b6cab 100644 --- a/taiga/projects/issues/signals.py +++ b/taiga/projects/issues/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/utils.py b/taiga/projects/issues/utils.py index 2053d923..c2227ff6 100644 --- a/taiga/projects/issues/utils.py +++ b/taiga/projects/issues/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/issues/validators.py b/taiga/projects/issues/validators.py index 5052fb54..7523fa0b 100644 --- a/taiga/projects/issues/validators.py +++ b/taiga/projects/issues/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/likes/admin.py b/taiga/projects/likes/admin.py index bab44a69..2c167f19 100644 --- a/taiga/projects/likes/admin.py +++ b/taiga/projects/likes/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/likes/mixins/viewsets.py b/taiga/projects/likes/mixins/viewsets.py index cce443c8..30abf977 100644 --- a/taiga/projects/likes/mixins/viewsets.py +++ b/taiga/projects/likes/mixins/viewsets.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/likes/models.py b/taiga/projects/likes/models.py index c363b8c8..e384f228 100644 --- a/taiga/projects/likes/models.py +++ b/taiga/projects/likes/models.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/likes/serializers.py b/taiga/projects/likes/serializers.py index ef058e70..6cf4b87a 100644 --- a/taiga/projects/likes/serializers.py +++ b/taiga/projects/likes/serializers.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/likes/services.py b/taiga/projects/likes/services.py index 617a4da9..aca6b395 100644 --- a/taiga/projects/likes/services.py +++ b/taiga/projects/likes/services.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/management/commands/block_user_projects.py b/taiga/projects/management/commands/block_user_projects.py new file mode 100644 index 00000000..09a48f3a --- /dev/null +++ b/taiga/projects/management/commands/block_user_projects.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.core.management.base import BaseCommand +from taiga.projects.choices import BLOCKED_BY_NONPAYMENT +from taiga.projects.models import Project + + +class Command(BaseCommand): + help = "Block user projects" + + def add_arguments(self, parser): + parser.add_argument("owner_usernames", + nargs="+", + help="") + + parser.add_argument("--is-private", + dest="is_private") + + parser.add_argument("--blocked-code", + dest="blocked_code") + + def handle(self, *args, **options): + owner_usernames = options["owner_usernames"] + projects = Project.objects.filter(owner__username__in=owner_usernames) + + is_private = options.get("is_private") + if is_private is not None: + is_private = is_private.lower() + is_private = is_private[0] in ["t", "y", "1"] + projects = projects.filter(is_private=is_private) + + blocked_code = options.get("blocked_code") + blocked_code = blocked_code if blocked_code is not None else BLOCKED_BY_NONPAYMENT + projects.update(blocked_code=blocked_code) diff --git a/taiga/projects/management/commands/change_project_slug.py b/taiga/projects/management/commands/change_project_slug.py index 8e8d012e..e078995f 100644 --- a/taiga/projects/management/commands/change_project_slug.py +++ b/taiga/projects/management/commands/change_project_slug.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -23,7 +23,7 @@ from django.test.utils import override_settings from taiga.base.utils.slug import slugify_uniquely from taiga.projects.models import Project from taiga.projects.history.models import HistoryEntry -from taiga.timeline.management.commands.rebuild_timeline import generate_timeline +from taiga.timeline.rebuilder import rebuild_timeline class Command(BaseCommand): @@ -58,4 +58,4 @@ class Command(BaseCommand): # Regenerate timeline self.stdout.write(self.style.SUCCESS("-> Regenerate timeline entries.")) - generate_timeline(None, None, project.id) + rebuild_timeline(None, None, project.id) diff --git a/taiga/projects/management/commands/sample_data.py b/taiga/projects/management/commands/sample_data.py index e8b47290..0026ac5b 100644 --- a/taiga/projects/management/commands/sample_data.py +++ b/taiga/projects/management/commands/sample_data.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -157,7 +157,7 @@ class Command(BaseCommand): for x in projects_range: project = self.create_project( x, - is_private=(x in [2, 4] or self.sd.boolean()), + is_private=x in [2, 4], blocked_code = BLOCKED_BY_STAFF if x in(blocked_projects_range) else None ) @@ -178,6 +178,9 @@ class Command(BaseCommand): if role.computable: computable_project_roles.add(role) + # Delete a random member so all the projects doesn't have the same team + Membership.objects.filter(project=project).exclude(user=project.owner).order_by("?").first().delete() + # added invitations for i in range(NUM_INVITATIONS): role = self.sd.db_object_from_queryset(project.roles.all()) @@ -318,7 +321,6 @@ class Command(BaseCommand): attachment = self.create_attachment(wiki_page, i+1) take_snapshot(wiki_page, - comment=self.sd.paragraph(), user=wiki_page.owner) # Add history entry @@ -374,7 +376,6 @@ class Command(BaseCommand): bug.save() take_snapshot(bug, - comment=self.sd.paragraph(), user=bug.owner) # Add history entry @@ -421,7 +422,6 @@ class Command(BaseCommand): attachment = self.create_attachment(task, i+1) take_snapshot(task, - comment=self.sd.paragraph(), user=task.owner) # Add history entry @@ -476,7 +476,6 @@ class Command(BaseCommand): take_snapshot(us, - comment=self.sd.paragraph(), user=us.owner) # Add history entry @@ -533,7 +532,6 @@ class Command(BaseCommand): epic.save() take_snapshot(epic, - comment=self.sd.paragraph(), user=epic.owner) # Add history entry @@ -559,9 +557,15 @@ class Command(BaseCommand): # Add history entry take_snapshot(epic, - comment=self.sd.paragraph(), user=epic.owner) + # Add history entry + epic.status=self.sd.db_object_from_queryset(project.epic_statuses.filter(is_closed=False)) + epic.save() + take_snapshot(epic, + comment=self.sd.paragraph(), + user=epic.owner) + return epic def create_project(self, counter, is_private=None, blocked_code=None): @@ -580,11 +584,12 @@ class Command(BaseCommand): total_story_points=self.sd.int(600, 3000), total_milestones=self.sd.int(5,10), tags=self.sd.words(1, 10).split(" "), - is_looking_for_people=counter in LOOKING_FOR_PEOPLE_PROJECTS_POSITIONS, - looking_for_people_note=self.sd.short_sentence(), - is_featured=counter in FEATURED_PROJECTS_POSITIONS, blocked_code=blocked_code) + project.is_looking_for_people = counter in LOOKING_FOR_PEOPLE_PROJECTS_POSITIONS + if project.is_looking_for_people: + project.looking_for_people_note = self.sd.short_sentence() + project.is_featured = counter in FEATURED_PROJECTS_POSITIONS project.is_kanban_activated = True project.is_epics_activated = True project.save() @@ -633,4 +638,3 @@ class Command(BaseCommand): def generate_color(self, tag): color = sha1(tag.encode("utf-8")).hexdigest()[0:6] return "#{}".format(color) - diff --git a/taiga/projects/migrations/0001_initial.py b/taiga/projects/migrations/0001_initial.py index 5a64c370..deee3a62 100644 --- a/taiga/projects/migrations/0001_initial.py +++ b/taiga/projects/migrations/0001_initial.py @@ -3,10 +3,9 @@ from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings -import django_pgjson.fields +import django.contrib.postgres.fields import django.utils.timezone import django.db.models.deletion -import djorm_pgarray.fields import taiga.projects.history.models @@ -40,7 +39,7 @@ class Migration(migrations.Migration): name='Project', fields=[ ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), - ('tags', djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='tags')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=[], null=True, size=None, verbose_name='tags')), ('name', models.CharField(max_length=250, unique=True, verbose_name='name')), ('slug', models.SlugField(max_length=250, unique=True, verbose_name='slug', blank=True)), ('description', models.TextField(verbose_name='description')), @@ -54,10 +53,10 @@ class Migration(migrations.Migration): ('is_issues_activated', models.BooleanField(default=True, verbose_name='active issues panel')), ('videoconferences', models.CharField(max_length=250, null=True, choices=[('appear-in', 'AppearIn'), ('talky', 'Talky')], verbose_name='videoconference system', blank=True)), ('videoconferences_salt', models.CharField(max_length=250, null=True, verbose_name='videoconference room salt', blank=True)), - ('anon_permissions', djorm_pgarray.fields.TextArrayField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('view_us', 'View user stories'), ('view_tasks', 'View tasks'), ('view_issues', 'View issues'), ('view_wiki_pages', 'View wiki pages'), ('view_wiki_links', 'View wiki links')], dbtype='text', default=[], verbose_name='anonymous permissions')), - ('public_permissions', djorm_pgarray.fields.TextArrayField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('view_us', 'View user stories'), ('view_issues', 'View issues'), ('vote_issues', 'Vote issues'), ('view_tasks', 'View tasks'), ('view_wiki_pages', 'View wiki pages'), ('view_wiki_links', 'View wiki links'), ('request_membership', 'Request membership'), ('add_us_to_project', 'Add user story to project'), ('add_comments_to_us', 'Add comments to user stories'), ('add_comments_to_task', 'Add comments to tasks'), ('add_issue', 'Add issues'), ('add_comments_issue', 'Add comments to issues'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link')], dbtype='text', default=[], verbose_name='user permissions')), + ('anon_permissions', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('view_us', 'View user stories'), ('view_tasks', 'View tasks'), ('view_issues', 'View issues'), ('view_wiki_pages', 'View wiki pages'), ('view_wiki_links', 'View wiki links')]), blank=True, default=[], null=True, size=None, verbose_name='anonymous permissions')), + ('public_permissions', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('view_us', 'View user stories'), ('view_issues', 'View issues'), ('vote_issues', 'Vote issues'), ('view_tasks', 'View tasks'), ('view_wiki_pages', 'View wiki pages'), ('view_wiki_links', 'View wiki links'), ('request_membership', 'Request membership'), ('add_us_to_project', 'Add user story to project'), ('add_comments_to_us', 'Add comments to user stories'), ('add_comments_to_task', 'Add comments to tasks'), ('add_issue', 'Add issues'), ('add_comments_issue', 'Add comments to issues'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='user permissions')), ('is_private', models.BooleanField(default=False, verbose_name='is private')), - ('tags_colors', djorm_pgarray.fields.TextArrayField(dbtype='text', dimension=2, default=[], null=False, verbose_name='tags colors')), + ('tags_colors', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), size=2), blank=True, default=[], null=True, size=None, verbose_name='tags colors')), ], options={ 'ordering': ['name'], diff --git a/taiga/projects/migrations/0002_auto_20140903_0920.py b/taiga/projects/migrations/0002_auto_20140903_0920.py index d9868ec7..93cf59da 100644 --- a/taiga/projects/migrations/0002_auto_20140903_0920.py +++ b/taiga/projects/migrations/0002_auto_20140903_0920.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import models, migrations import django.utils.timezone -import django_pgjson.fields +import taiga.base.db.models.fields import django.db.models.deletion import taiga.projects.history.models @@ -100,15 +100,15 @@ class Migration(migrations.Migration): ('is_issues_activated', models.BooleanField(verbose_name='active issues panel', default=True)), ('videoconferences', models.CharField(max_length=250, null=True, choices=[('appear-in', 'AppearIn'), ('talky', 'Talky')], verbose_name='videoconference system', blank=True)), ('videoconferences_salt', models.CharField(max_length=250, null=True, verbose_name='videoconference room salt', blank=True)), - ('default_options', django_pgjson.fields.JsonField(null=True, verbose_name='default options', blank=True)), - ('us_statuses', django_pgjson.fields.JsonField(null=True, verbose_name='us statuses', blank=True)), - ('points', django_pgjson.fields.JsonField(null=True, verbose_name='points', blank=True)), - ('task_statuses', django_pgjson.fields.JsonField(null=True, verbose_name='task statuses', blank=True)), - ('issue_statuses', django_pgjson.fields.JsonField(null=True, verbose_name='issue statuses', blank=True)), - ('issue_types', django_pgjson.fields.JsonField(null=True, verbose_name='issue types', blank=True)), - ('priorities', django_pgjson.fields.JsonField(null=True, verbose_name='priorities', blank=True)), - ('severities', django_pgjson.fields.JsonField(null=True, verbose_name='severities', blank=True)), - ('roles', django_pgjson.fields.JsonField(null=True, verbose_name='roles', blank=True)), + ('default_options', taiga.base.db.models.fields.JSONField(null=True, verbose_name='default options', blank=True)), + ('us_statuses', taiga.base.db.models.fields.JSONField(null=True, verbose_name='us statuses', blank=True)), + ('points', taiga.base.db.models.fields.JSONField(null=True, verbose_name='points', blank=True)), + ('task_statuses', taiga.base.db.models.fields.JSONField(null=True, verbose_name='task statuses', blank=True)), + ('issue_statuses', taiga.base.db.models.fields.JSONField(null=True, verbose_name='issue statuses', blank=True)), + ('issue_types', taiga.base.db.models.fields.JSONField(null=True, verbose_name='issue types', blank=True)), + ('priorities', taiga.base.db.models.fields.JSONField(null=True, verbose_name='priorities', blank=True)), + ('severities', taiga.base.db.models.fields.JSONField(null=True, verbose_name='severities', blank=True)), + ('roles', taiga.base.db.models.fields.JSONField(null=True, verbose_name='roles', blank=True)), ], options={ 'verbose_name_plural': 'project templates', diff --git a/taiga/projects/migrations/0010_project_modules_config.py b/taiga/projects/migrations/0010_project_modules_config.py index 49eaedb7..b2f14201 100644 --- a/taiga/projects/migrations/0010_project_modules_config.py +++ b/taiga/projects/migrations/0010_project_modules_config.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='project', name='modules_config', - field=django_pgjson.fields.JsonField(blank=True, null=True, verbose_name='modules config'), + field=taiga.base.db.models.fields.JSONField(blank=True, null=True, verbose_name='modules config'), preserve_default=True, ), ] diff --git a/taiga/projects/migrations/0011_auto_20141028_2057.py b/taiga/projects/migrations/0011_auto_20141028_2057.py index fd9a0a33..037b6b9c 100644 --- a/taiga/projects/migrations/0011_auto_20141028_2057.py +++ b/taiga/projects/migrations/0011_auto_20141028_2057.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -16,7 +16,7 @@ class Migration(migrations.Migration): name='ProjectModulesConfig', fields=[ ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('config', django_pgjson.fields.JsonField(null=True, verbose_name='modules config', blank=True)), + ('config', taiga.base.db.models.fields.JSONField(null=True, verbose_name='modules config', blank=True)), ('project', models.OneToOneField(to='projects.Project', verbose_name='project', related_name='modules_config')), ], options={ diff --git a/taiga/projects/migrations/0016_fix_json_field_not_null.py b/taiga/projects/migrations/0016_fix_json_field_not_null.py index aaa04543..4c280951 100644 --- a/taiga/projects/migrations/0016_fix_json_field_not_null.py +++ b/taiga/projects/migrations/0016_fix_json_field_not_null.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from django.db import models, migrations diff --git a/taiga/projects/migrations/0019_auto_20150311_0821.py b/taiga/projects/migrations/0019_auto_20150311_0821.py index 034bd3ff..2b5c42ac 100644 --- a/taiga/projects/migrations/0019_auto_20150311_0821.py +++ b/taiga/projects/migrations/0019_auto_20150311_0821.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='project', name='public_permissions', - field=djorm_pgarray.fields.TextArrayField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('vote_issues', 'Vote issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], verbose_name='user permissions', dbtype='text', default=[]), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('vote_issues', 'Vote issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='user permissions'), preserve_default=True, ), ] diff --git a/taiga/projects/migrations/0024_auto_20150810_1247.py b/taiga/projects/migrations/0024_auto_20150810_1247.py index f057816b..5eef1454 100644 --- a/taiga/projects/migrations/0024_auto_20150810_1247.py +++ b/taiga/projects/migrations/0024_auto_20150810_1247.py @@ -2,8 +2,8 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields from django.conf import settings +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -17,7 +17,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='project', name='public_permissions', - field=djorm_pgarray.fields.TextArrayField(default=[], dbtype='text', choices=[('view_project', 'View project'), ('star_project', 'Star project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('vote_us', 'Vote user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('vote_task', 'Vote task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('vote_issue', 'Vote issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], verbose_name='user permissions'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('star_project', 'Star project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('vote_us', 'Vote user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('vote_task', 'Vote task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('vote_issue', 'Vote issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='user permissions'), preserve_default=True, ), ] diff --git a/taiga/projects/migrations/0025_auto_20150901_1600.py b/taiga/projects/migrations/0025_auto_20150901_1600.py index 8859b14e..ec02ef9b 100644 --- a/taiga/projects/migrations/0025_auto_20150901_1600.py +++ b/taiga/projects/migrations/0025_auto_20150901_1600.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='project', name='public_permissions', - field=djorm_pgarray.fields.TextArrayField(default=[], choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], dbtype='text', verbose_name='user permissions'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='user permissions'), preserve_default=True, ), ] diff --git a/taiga/projects/migrations/0041_auto_20160519_1058.py b/taiga/projects/migrations/0041_auto_20160519_1058.py index c4b0a2fd..cffe98d5 100644 --- a/taiga/projects/migrations/0041_auto_20160519_1058.py +++ b/taiga/projects/migrations/0041_auto_20160519_1058.py @@ -2,8 +2,8 @@ # Generated by Django 1.9.2 on 2016-05-19 10:58 from __future__ import unicode_literals -from django.db import migrations -import djorm_pgarray.fields +from django.db import migrations, models +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -16,6 +16,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='project', name='public_permissions', - field=djorm_pgarray.fields.TextArrayField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('comment_us', 'Comment user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('comment_task', 'Comment task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('comment_issue', 'Comment issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('comment_wiki_page', 'Comment wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], dbtype='text', default=[], verbose_name='user permissions'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('comment_us', 'Comment user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('comment_task', 'Comment task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('comment_issue', 'Comment issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('comment_wiki_page', 'Comment wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='user permissions'), + ), ] diff --git a/taiga/projects/migrations/0049_auto_20160629_1443.py b/taiga/projects/migrations/0049_auto_20160629_1443.py index 8c2117b0..724cb1a9 100644 --- a/taiga/projects/migrations/0049_auto_20160629_1443.py +++ b/taiga/projects/migrations/0049_auto_20160629_1443.py @@ -2,10 +2,10 @@ # Generated by Django 1.9.2 on 2016-06-29 14:43 from __future__ import unicode_literals -import django.contrib.postgres.fields +import taiga.base.db.models.fields from django.db import migrations, models import django.db.models.deletion -import django_pgjson.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -71,7 +71,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='projecttemplate', name='epic_statuses', - field=django_pgjson.fields.JsonField(blank=True, null=True, verbose_name='epic statuses'), + field=taiga.base.db.models.fields.JSONField(blank=True, null=True, verbose_name='epic statuses'), ), migrations.AddField( model_name='projecttemplate', diff --git a/taiga/projects/migrations/0055_json_to_jsonb.py b/taiga/projects/migrations/0055_json_to_jsonb.py new file mode 100644 index 00000000..f84d35c0 --- /dev/null +++ b/taiga/projects/migrations/0055_json_to_jsonb.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:34 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0054_auto_20160928_0540'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "projects_projectmodulesconfig" + ALTER COLUMN "config" + TYPE jsonb + USING regexp_replace("config"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """, + reverse_sql=migrations.RunSQL.noop + ), + migrations.RunSQL( + """ + ALTER TABLE "projects_projecttemplate" + ALTER COLUMN "roles" + TYPE jsonb + USING regexp_replace("roles"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "default_options" + TYPE jsonb + USING regexp_replace("default_options"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "epic_statuses" + TYPE jsonb + USING regexp_replace("epic_statuses"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "us_statuses" + TYPE jsonb + USING regexp_replace("us_statuses"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "points" + TYPE jsonb + USING regexp_replace("points"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "task_statuses" + TYPE jsonb + USING regexp_replace("task_statuses"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "issue_statuses" + TYPE jsonb + USING regexp_replace("issue_statuses"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "issue_types" + TYPE jsonb + USING regexp_replace("issue_types"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "priorities" + TYPE jsonb + USING regexp_replace("priorities"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "severities" + TYPE jsonb + USING regexp_replace("severities"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """, + reverse_sql=migrations.RunSQL.noop + ), + ] diff --git a/taiga/projects/migrations/0056_auto_20161110_1518.py b/taiga/projects/migrations/0056_auto_20161110_1518.py new file mode 100644 index 00000000..1c03c79c --- /dev/null +++ b/taiga/projects/migrations/0056_auto_20161110_1518.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-11-10 15:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0055_json_to_jsonb'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='is_contact_activated', + field=models.BooleanField(default=True, verbose_name='active contact'), + ), + migrations.AddField( + model_name='projecttemplate', + name='is_contact_activated', + field=models.BooleanField(default=True, verbose_name='active contact'), + ), + ] diff --git a/taiga/projects/migrations/0057_auto_20161129_0945.py b/taiga/projects/migrations/0057_auto_20161129_0945.py new file mode 100644 index 00000000..0e161a4f --- /dev/null +++ b/taiga/projects/migrations/0057_auto_20161129_0945.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-11-29 09:45 +from __future__ import unicode_literals + +from django.db import migrations + + +DROP_INDEX = """ + DROP INDEX IF EXISTS projects_project_textquery_idx; +""" + + +# NOTE: This index is needed by taiga.projects.filters.QFilter +CREATE_INDEX = """ + CREATE INDEX projects_project_textquery_idx + ON projects_project + USING gin((setweight(to_tsvector('simple', + coalesce(projects_project.name, '')), 'A') || + setweight(to_tsvector('simple', + coalesce(inmutable_array_to_string(projects_project.tags), '')), 'B') || + setweight(to_tsvector('simple', + coalesce(projects_project.description, '')), 'C'))); +""" + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0056_auto_20161110_1518'), + ] + + operations = [ + migrations.RunSQL([DROP_INDEX, CREATE_INDEX], + [DROP_INDEX]), + ] diff --git a/taiga/projects/migrations/0058_auto_20161215_1347.py b/taiga/projects/migrations/0058_auto_20161215_1347.py new file mode 100644 index 00000000..341f2fb1 --- /dev/null +++ b/taiga/projects/migrations/0058_auto_20161215_1347.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-12-15 13:47 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +import django.core.serializers.json +from django.db import migrations, models +import taiga.base.db.models.fields.json + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0057_auto_20161129_0945'), + ] + + operations = [ + migrations.AddField( + model_name='projecttemplate', + name='epic_custom_attributes', + field=taiga.base.db.models.fields.json.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True, verbose_name='epic custom attributes'), + ), + migrations.AddField( + model_name='projecttemplate', + name='is_looking_for_people', + field=models.BooleanField(default=False, verbose_name='is looking for people'), + ), + migrations.AddField( + model_name='projecttemplate', + name='issue_custom_attributes', + field=taiga.base.db.models.fields.json.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True, verbose_name='issue custom attributes'), + ), + migrations.AddField( + model_name='projecttemplate', + name='looking_for_people_note', + field=models.TextField(blank=True, default='', verbose_name='loking for people note'), + ), + migrations.AddField( + model_name='projecttemplate', + name='tags', + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=[], null=True, size=None, verbose_name='tags'), + ), + migrations.AddField( + model_name='projecttemplate', + name='tags_colors', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), size=2), blank=True, default=[], null=True, size=None, verbose_name='tags colors'), + ), + migrations.AddField( + model_name='projecttemplate', + name='task_custom_attributes', + field=taiga.base.db.models.fields.json.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True, verbose_name='task custom attributes'), + ), + migrations.AddField( + model_name='projecttemplate', + name='us_custom_attributes', + field=taiga.base.db.models.fields.json.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True, verbose_name='us custom attributes'), + ), + ] diff --git a/taiga/projects/migrations/0059_auto_20170116_1633.py b/taiga/projects/migrations/0059_auto_20170116_1633.py new file mode 100644 index 00000000..dc368cbd --- /dev/null +++ b/taiga/projects/migrations/0059_auto_20170116_1633.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-16 16:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0058_auto_20161215_1347'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='looking_for_people_note', + field=models.TextField(blank=True, default='', verbose_name='looking for people note'), + ), + migrations.AlterField( + model_name='projecttemplate', + name='looking_for_people_note', + field=models.TextField(blank=True, default='', verbose_name='looking for people note'), + ), + ] diff --git a/taiga/projects/milestones/admin.py b/taiga/projects/milestones/admin.py index d3f8c994..d525df64 100644 --- a/taiga/projects/milestones/admin.py +++ b/taiga/projects/milestones/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -34,7 +34,7 @@ class MilestoneInline(admin.TabularInline): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["owner"]): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.parent_obj) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/milestones/api.py b/taiga/projects/milestones/api.py index 820e90c7..4fa233c0 100644 --- a/taiga/projects/milestones/api.py +++ b/taiga/projects/milestones/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -46,7 +46,13 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, serializer_class = serializers.MilestoneSerializer validator_class = validators.MilestoneValidator permission_classes = (permissions.MilestonePermission,) - filter_backends = (filters.CanViewMilestonesFilterBackend,) + filter_backends = ( + filters.CanViewMilestonesFilterBackend, + filters.CreatedDateFilter, + filters.ModifiedDateFilter, + filters.EstimatedStartFilter, + filters.EstimatedFinishFilter, + ) filter_fields = ( "project", "project__slug", diff --git a/taiga/projects/milestones/models.py b/taiga/projects/milestones/models.py index 86eb3251..39c125e1 100644 --- a/taiga/projects/milestones/models.py +++ b/taiga/projects/milestones/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/milestones/permissions.py b/taiga/projects/milestones/permissions.py index fedb1ee0..4029c846 100644 --- a/taiga/projects/milestones/permissions.py +++ b/taiga/projects/milestones/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/milestones/serializers.py b/taiga/projects/milestones/serializers.py index 44b3e8f4..484c089b 100644 --- a/taiga/projects/milestones/serializers.py +++ b/taiga/projects/milestones/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,9 +20,12 @@ from taiga.base.api import serializers from taiga.base.fields import Field, MethodField from taiga.projects.notifications.mixins import WatchedResourceSerializer from taiga.projects.userstories.serializers import UserStoryListSerializer +from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin -class MilestoneSerializer(WatchedResourceSerializer, serializers.LightSerializer): +class MilestoneSerializer(WatchedResourceSerializer, + ProjectExtraInfoSerializerMixin, + serializers.LightSerializer): id = Field() name = Field() slug = Field() diff --git a/taiga/projects/milestones/services.py b/taiga/projects/milestones/services.py index 289c4834..af50b81e 100644 --- a/taiga/projects/milestones/services.py +++ b/taiga/projects/milestones/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/milestones/utils.py b/taiga/projects/milestones/utils.py index bea1cf12..b67bf17e 100644 --- a/taiga/projects/milestones/utils.py +++ b/taiga/projects/milestones/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/milestones/validators.py b/taiga/projects/milestones/validators.py index b7d4d484..c37ae2b1 100644 --- a/taiga/projects/milestones/validators.py +++ b/taiga/projects/milestones/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/mixins/blocked.py b/taiga/projects/mixins/blocked.py index d7f729cc..bf63feb2 100644 --- a/taiga/projects/mixins/blocked.py +++ b/taiga/projects/mixins/blocked.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/mixins/by_ref.py b/taiga/projects/mixins/by_ref.py index 4e1dbad9..5a6793cd 100644 --- a/taiga/projects/mixins/by_ref.py +++ b/taiga/projects/mixins/by_ref.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/mixins/on_destroy.py b/taiga/projects/mixins/on_destroy.py index 61d4728d..80a160b2 100644 --- a/taiga/projects/mixins/on_destroy.py +++ b/taiga/projects/mixins/on_destroy.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/mixins/ordering.py b/taiga/projects/mixins/ordering.py index 18649c16..7c24b836 100644 --- a/taiga/projects/mixins/ordering.py +++ b/taiga/projects/mixins/ordering.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/mixins/serializers.py b/taiga/projects/mixins/serializers.py index c8a70932..3bef1aaa 100644 --- a/taiga/projects/mixins/serializers.py +++ b/taiga/projects/mixins/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/models.py b/taiga/projects/models.py index c23fcc4b..3a345088 100644 --- a/taiga/projects/models.py +++ b/taiga/projects/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -29,11 +29,15 @@ from django.utils.functional import cached_property from django_pglocks import advisory_lock -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from taiga.base.utils.time import timestamp_ms +from taiga.projects.custom_attributes.models import EpicCustomAttribute +from taiga.projects.custom_attributes.models import UserStoryCustomAttribute +from taiga.projects.custom_attributes.models import TaskCustomAttribute +from taiga.projects.custom_attributes.models import IssueCustomAttribute from taiga.projects.tagging.models import TaggedMixin -from taiga.projects.tagging.models import TagsColorsdMixin +from taiga.projects.tagging.models import TagsColorsMixin from taiga.base.utils.files import get_file_path from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.slug import slugify_uniquely_for_queryset @@ -82,10 +86,10 @@ class Membership(models.Model): null=True, blank=True) invitation_extra_text = models.TextField(null=True, blank=True, - verbose_name=_("invitation extra text")) + verbose_name=_("invitation extra text")) user_order = models.BigIntegerField(default=timestamp_ms, null=False, blank=False, - verbose_name=_("user order")) + verbose_name=_("user order")) class Meta: verbose_name = "membership" @@ -106,9 +110,9 @@ class Membership(models.Model): class ProjectDefaults(models.Model): default_epic_status = models.OneToOneField("projects.EpicStatus", - on_delete=models.SET_NULL, related_name="+", - null=True, blank=True, - verbose_name=_("default epic status")) + on_delete=models.SET_NULL, related_name="+", + null=True, blank=True, + verbose_name=_("default epic status")) default_us_status = models.OneToOneField("projects.UserStoryStatus", on_delete=models.SET_NULL, related_name="+", null=True, blank=True, @@ -139,7 +143,7 @@ class ProjectDefaults(models.Model): abstract = True -class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): +class Project(ProjectDefaults, TaggedMixin, TagsColorsMixin, models.Model): name = models.CharField(max_length=250, null=False, blank=False, verbose_name=_("name")) slug = models.SlugField(max_length=250, unique=True, null=False, blank=True, @@ -148,8 +152,8 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): verbose_name=_("description")) logo = models.FileField(upload_to=get_project_logo_file_path, - max_length=500, null=True, blank=True, - verbose_name=_("logo")) + max_length=500, null=True, blank=True, + verbose_name=_("logo")) created_date = models.DateTimeField(null=False, blank=False, verbose_name=_("created date"), @@ -164,7 +168,8 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): total_milestones = models.IntegerField(null=True, blank=True, verbose_name=_("total of milestones")) total_story_points = models.FloatField(null=True, blank=True, verbose_name=_("total story points")) - + is_contact_activated = models.BooleanField(default=True, null=False, blank=True, + verbose_name=_("active contact")) is_epics_activated = models.BooleanField(default=False, null=False, blank=True, verbose_name=_("active epics panel")) is_backlog_activated = models.BooleanField(default=True, null=False, blank=True, @@ -179,7 +184,7 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): choices=choices.VIDEOCONFERENCES_CHOICES, verbose_name=_("videoconference system")) videoconferences_extra_data = models.CharField(max_length=250, null=True, blank=True, - verbose_name=_("videoconference extra data")) + verbose_name=_("videoconference extra data")) creation_template = models.ForeignKey("projects.ProjectTemplate", related_name="projects", null=True, @@ -195,12 +200,12 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): null=True, blank=True, default=[], verbose_name=_("user permissions")) is_featured = models.BooleanField(default=False, null=False, blank=True, - verbose_name=_("is featured")) + verbose_name=_("is featured")) is_looking_for_people = models.BooleanField(default=False, null=False, blank=True, - verbose_name=_("is looking for people")) + verbose_name=_("is looking for people")) looking_for_people_note = models.TextField(default="", null=False, blank=True, - verbose_name=_("loking for people note")) + verbose_name=_("looking for people note")) epics_csv_uuid = models.CharField(max_length=32, editable=False, null=True, blank=True, default=None, db_index=True) @@ -217,36 +222,39 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): verbose_name=_("project transfer token")) blocked_code = models.CharField(null=True, blank=True, max_length=255, - choices=choices.BLOCKING_CODES + settings.EXTRA_BLOCKING_CODES, default=None, - verbose_name=_("blocked code")) - - #Totals: + choices=choices.BLOCKING_CODES + settings.EXTRA_BLOCKING_CODES, + default=None, verbose_name=_("blocked code")) + # Totals: totals_updated_datetime = models.DateTimeField(null=False, blank=False, auto_now_add=True, - verbose_name=_("updated date time"), db_index=True) + verbose_name=_("updated date time"), db_index=True) total_fans = models.PositiveIntegerField(null=False, blank=False, default=0, verbose_name=_("count"), db_index=True) total_fans_last_week = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("fans last week"), db_index=True) + verbose_name=_("fans last week"), db_index=True) total_fans_last_month = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("fans last month"), db_index=True) + verbose_name=_("fans last month"), db_index=True) total_fans_last_year = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("fans last year"), db_index=True) + verbose_name=_("fans last year"), db_index=True) total_activity = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("count"), db_index=True) + verbose_name=_("count"), + db_index=True) total_activity_last_week = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("activity last week"), db_index=True) + verbose_name=_("activity last week"), + db_index=True) total_activity_last_month = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("activity last month"), db_index=True) + verbose_name=_("activity last month"), + db_index=True) total_activity_last_year = models.PositiveIntegerField(null=False, blank=False, default=0, - verbose_name=_("activity last year"), db_index=True) + verbose_name=_("activity last year"), + db_index=True) _importing = None @@ -302,13 +310,13 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): self.total_fans = qs.count() - qs_week = qs.filter(created_date__gte=now-relativedelta(weeks=1)) + qs_week = qs.filter(created_date__gte=now - relativedelta(weeks=1)) self.total_fans_last_week = qs_week.count() - qs_month = qs.filter(created_date__gte=now-relativedelta(months=1)) + qs_month = qs.filter(created_date__gte=now - relativedelta(months=1)) self.total_fans_last_month = qs_month.count() - qs_year = qs.filter(created_date__gte=now-relativedelta(years=1)) + qs_year = qs.filter(created_date__gte=now - relativedelta(years=1)) self.total_fans_last_year = qs_year.count() tl_model = apps.get_model("timeline", "Timeline") @@ -317,13 +325,13 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): qs = tl_model.objects.filter(namespace=namespace) self.total_activity = qs.count() - qs_week = qs.filter(created__gte=now-relativedelta(weeks=1)) + qs_week = qs.filter(created__gte=now - relativedelta(weeks=1)) self.total_activity_last_week = qs_week.count() - qs_month = qs.filter(created__gte=now-relativedelta(months=1)) + qs_month = qs.filter(created__gte=now - relativedelta(months=1)) self.total_activity_last_month = qs_month.count() - qs_year = qs.filter(created__gte=now-relativedelta(years=1)) + qs_year = qs.filter(created__gte=now - relativedelta(years=1)) self.total_activity_last_year = qs_year.count() if save: @@ -357,7 +365,7 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): policy = model_cls.objects.create( project=self, user=user, - notify_level= NotifyLevel.involved) + notify_level=NotifyLevel.involved) del self.cached_notify_policies @@ -459,6 +467,8 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): # NOTE: Remember to update code in taiga.projects.admin.ProjectAdmin.delete_selected from taiga.events.apps import (connect_events_signals, disconnect_events_signals) + from taiga.projects.epics.apps import (connect_all_epics_signals, + disconnect_all_epics_signals) from taiga.projects.tasks.apps import (connect_all_tasks_signals, disconnect_all_tasks_signals) from taiga.projects.userstories.apps import (connect_all_userstories_signals, @@ -469,12 +479,14 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): disconnect_memberships_signals) disconnect_events_signals() + disconnect_all_epics_signals() disconnect_all_issues_signals() disconnect_all_tasks_signals() disconnect_all_userstories_signals() disconnect_memberships_signals() try: + self.epics.all().delete() self.tasks.all().delete() self.user_stories.all().delete() self.issues.all().delete() @@ -485,13 +497,14 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model): connect_all_issues_signals() connect_all_tasks_signals() connect_all_userstories_signals() + connect_all_epics_signals() connect_memberships_signals() class ProjectModulesConfig(models.Model): project = models.OneToOneField("Project", null=False, blank=False, - related_name="modules_config", verbose_name=_("project")) - config = JsonField(null=True, blank=True, verbose_name=_("modules config")) + related_name="modules_config", verbose_name=_("project")) + config = JSONField(null=True, blank=True, verbose_name=_("modules config")) class Meta: verbose_name = "project modules config" @@ -543,7 +556,7 @@ class UserStoryStatus(models.Model): is_closed = models.BooleanField(default=False, null=False, blank=True, verbose_name=_("is closed")) is_archived = models.BooleanField(default=False, null=False, blank=True, - verbose_name=_("is archived")) + verbose_name=_("is archived")) color = models.CharField(max_length=20, null=False, blank=False, default="#999999", verbose_name=_("color")) wip_limit = models.IntegerField(null=True, blank=True, default=None, @@ -717,7 +730,7 @@ class IssueType(models.Model): return self.name -class ProjectTemplate(models.Model): +class ProjectTemplate(TaggedMixin, TagsColorsMixin, models.Model): name = models.CharField(max_length=250, null=False, blank=False, verbose_name=_("name")) slug = models.SlugField(max_length=250, null=False, blank=True, @@ -725,7 +738,7 @@ class ProjectTemplate(models.Model): description = models.TextField(null=False, blank=False, verbose_name=_("description")) order = models.BigIntegerField(default=timestamp_ms, null=False, blank=False, - verbose_name=_("user order")) + verbose_name=_("user order")) created_date = models.DateTimeField(null=False, blank=False, verbose_name=_("created date"), default=timezone.now) @@ -734,7 +747,8 @@ class ProjectTemplate(models.Model): default_owner_role = models.CharField(max_length=50, null=False, blank=False, verbose_name=_("default owner's role")) - + is_contact_activated = models.BooleanField(default=True, null=False, blank=True, + verbose_name=_("active contact")) is_epics_activated = models.BooleanField(default=False, null=False, blank=True, verbose_name=_("active epics panel")) is_backlog_activated = models.BooleanField(default=True, null=False, blank=True, @@ -745,22 +759,31 @@ class ProjectTemplate(models.Model): verbose_name=_("active wiki panel")) is_issues_activated = models.BooleanField(default=True, null=False, blank=True, verbose_name=_("active issues panel")) + is_looking_for_people = models.BooleanField(default=False, null=False, blank=True, + verbose_name=_("is looking for people")) + looking_for_people_note = models.TextField(default="", null=False, blank=True, + verbose_name=_("looking for people note")) videoconferences = models.CharField(max_length=250, null=True, blank=True, choices=choices.VIDEOCONFERENCES_CHOICES, verbose_name=_("videoconference system")) videoconferences_extra_data = models.CharField(max_length=250, null=True, blank=True, - verbose_name=_("videoconference extra data")) + verbose_name=_("videoconference extra data")) + + default_options = JSONField(null=True, blank=True, verbose_name=_("default options")) + epic_statuses = JSONField(null=True, blank=True, verbose_name=_("epic statuses")) + us_statuses = JSONField(null=True, blank=True, verbose_name=_("us statuses")) + points = JSONField(null=True, blank=True, verbose_name=_("points")) + task_statuses = JSONField(null=True, blank=True, verbose_name=_("task statuses")) + issue_statuses = JSONField(null=True, blank=True, verbose_name=_("issue statuses")) + issue_types = JSONField(null=True, blank=True, verbose_name=_("issue types")) + priorities = JSONField(null=True, blank=True, verbose_name=_("priorities")) + severities = JSONField(null=True, blank=True, verbose_name=_("severities")) + roles = JSONField(null=True, blank=True, verbose_name=_("roles")) + epic_custom_attributes = JSONField(null=True, blank=True, verbose_name=_("epic custom attributes")) + us_custom_attributes = JSONField(null=True, blank=True, verbose_name=_("us custom attributes")) + task_custom_attributes = JSONField(null=True, blank=True, verbose_name=_("task custom attributes")) + issue_custom_attributes = JSONField(null=True, blank=True, verbose_name=_("issue custom attributes")) - default_options = JsonField(null=True, blank=True, verbose_name=_("default options")) - epic_statuses = JsonField(null=True, blank=True, verbose_name=_("epic statuses")) - us_statuses = JsonField(null=True, blank=True, verbose_name=_("us statuses")) - points = JsonField(null=True, blank=True, verbose_name=_("points")) - task_statuses = JsonField(null=True, blank=True, verbose_name=_("task statuses")) - issue_statuses = JsonField(null=True, blank=True, verbose_name=_("issue statuses")) - issue_types = JsonField(null=True, blank=True, verbose_name=_("issue types")) - priorities = JsonField(null=True, blank=True, verbose_name=_("priorities")) - severities = JsonField(null=True, blank=True, verbose_name=_("severities")) - roles = JsonField(null=True, blank=True, verbose_name=_("roles")) _importing = None class Meta: @@ -777,9 +800,12 @@ class ProjectTemplate(models.Model): def save(self, *args, **kwargs): if not self._importing or not self.modified_date: self.modified_date = timezone.now() + if not self.slug: + self.slug = slugify_uniquely(self.name, self.__class__) super().save(*args, **kwargs) def load_data_from_project(self, project): + self.is_contact_activated = project.is_contact_activated self.is_epics_activated = project.is_epics_activated self.is_backlog_activated = project.is_backlog_activated self.is_kanban_activated = project.is_kanban_activated @@ -883,12 +909,53 @@ class ProjectTemplate(models.Model): "computable": role.computable }) + self.epic_custom_attributes = [] + for ca in project.epiccustomattributes.all(): + self.epic_custom_attributes.append({ + "name": ca.name, + "description": ca.description, + "type": ca.type, + "order": ca.order + }) + + self.us_custom_attributes = [] + for ca in project.userstorycustomattributes.all(): + self.us_custom_attributes.append({ + "name": ca.name, + "description": ca.description, + "type": ca.type, + "order": ca.order + }) + + self.task_custom_attributes = [] + for ca in project.taskcustomattributes.all(): + self.task_custom_attributes.append({ + "name": ca.name, + "description": ca.description, + "type": ca.type, + "order": ca.order + }) + + self.issue_custom_attributes = [] + for ca in project.issuecustomattributes.all(): + self.issue_custom_attributes.append({ + "name": ca.name, + "description": ca.description, + "type": ca.type, + "order": ca.order + }) + try: owner_membership = Membership.objects.get(project=project, user=project.owner) self.default_owner_role = owner_membership.role.slug except Membership.DoesNotExist: self.default_owner_role = self.roles[0].get("slug", None) + self.tags = project.tags + self.tags_colors = project.tags_colors + self.is_looking_for_people = project.is_looking_for_people + self.looking_for_people_note = project.looking_for_people_note + def apply_to_project(self, project): Role = apps.get_model("users", "Role") @@ -896,6 +963,7 @@ class ProjectTemplate(models.Model): raise Exception("Project need an id (must be a saved project)") project.creation_template = self + project.is_contact_activated = self.is_contact_activated project.is_epics_activated = self.is_epics_activated project.is_backlog_activated = self.is_backlog_activated project.is_kanban_activated = self.is_kanban_activated @@ -1018,4 +1086,45 @@ class ProjectTemplate(models.Model): project.default_severity = Severity.objects.get(name=self.default_options["severity"], project=project) + for ca in self.epic_custom_attributes: + EpicCustomAttribute.objects.create( + name=ca["name"], + description=ca["description"], + type=ca["type"], + order=ca["order"], + project=project + ) + + for ca in self.us_custom_attributes: + UserStoryCustomAttribute.objects.create( + name=ca["name"], + description=ca["description"], + type=ca["type"], + order=ca["order"], + project=project + ) + + for ca in self.task_custom_attributes: + TaskCustomAttribute.objects.create( + name=ca["name"], + description=ca["description"], + type=ca["type"], + order=ca["order"], + project=project + ) + + for ca in self.issue_custom_attributes: + IssueCustomAttribute.objects.create( + name=ca["name"], + description=ca["description"], + type=ca["type"], + order=ca["order"], + project=project + ) + + project.tags = self.tags + project.tags_colors = self.tags_colors + project.is_looking_for_people = self.is_looking_for_people + project.looking_for_people_note = self.looking_for_people_note + return project diff --git a/taiga/projects/notifications/admin.py b/taiga/projects/notifications/admin.py index 5e0bd931..1da3c97c 100644 --- a/taiga/projects/notifications/admin.py +++ b/taiga/projects/notifications/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/api.py b/taiga/projects/notifications/api.py index 9936ae52..8a7a06dd 100644 --- a/taiga/projects/notifications/api.py +++ b/taiga/projects/notifications/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/choices.py b/taiga/projects/notifications/choices.py index fdf87328..9fbe0f07 100644 --- a/taiga/projects/notifications/choices.py +++ b/taiga/projects/notifications/choices.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/management/commands/send_notifications.py b/taiga/projects/notifications/management/commands/send_notifications.py index 4c87b569..988454eb 100644 --- a/taiga/projects/notifications/management/commands/send_notifications.py +++ b/taiga/projects/notifications/management/commands/send_notifications.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/mixins.py b/taiga/projects/notifications/mixins.py index e9dff950..cf23b490 100644 --- a/taiga/projects/notifications/mixins.py +++ b/taiga/projects/notifications/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/models.py b/taiga/projects/notifications/models.py index 4a2f56da..a8dc3e96 100644 --- a/taiga/projects/notifications/models.py +++ b/taiga/projects/notifications/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/permissions.py b/taiga/projects/notifications/permissions.py index 9515f393..1f6aa075 100644 --- a/taiga/projects/notifications/permissions.py +++ b/taiga/projects/notifications/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/serializers.py b/taiga/projects/notifications/serializers.py index 1578b7be..4387c19c 100644 --- a/taiga/projects/notifications/serializers.py +++ b/taiga/projects/notifications/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/services.py b/taiga/projects/notifications/services.py index b8453b16..2e1d6d55 100644 --- a/taiga/projects/notifications/services.py +++ b/taiga/projects/notifications/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/utils.py b/taiga/projects/notifications/utils.py index ae6bd34c..6246cd62 100644 --- a/taiga/projects/notifications/utils.py +++ b/taiga/projects/notifications/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/notifications/validators.py b/taiga/projects/notifications/validators.py index 40e02083..2fd4a537 100644 --- a/taiga/projects/notifications/validators.py +++ b/taiga/projects/notifications/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/occ/__init__.py b/taiga/projects/occ/__init__.py index 248c7af9..142fb2b3 100644 --- a/taiga/projects/occ/__init__.py +++ b/taiga/projects/occ/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/occ/mixins.py b/taiga/projects/occ/mixins.py index 7e837422..3807e1cf 100644 --- a/taiga/projects/occ/mixins.py +++ b/taiga/projects/occ/mixins.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/permissions.py b/taiga/projects/permissions.py index 7c10b5c2..6df6ae73 100644 --- a/taiga/projects/permissions.py +++ b/taiga/projects/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -83,6 +83,7 @@ class ProjectPermission(TaigaResourcePermission): edit_tag_perms = IsProjectAdmin() delete_tag_perms = IsProjectAdmin() mix_tags_perms = IsProjectAdmin() + duplicate_perms = IsAuthenticated() & HasProjectPerm('view_project') class ProjectFansPermission(TaigaResourcePermission): diff --git a/taiga/projects/references/api.py b/taiga/projects/references/api.py index a4ae20ec..10a08538 100644 --- a/taiga/projects/references/api.py +++ b/taiga/projects/references/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/references/models.py b/taiga/projects/references/models.py index 61097ecb..68f07dd3 100644 --- a/taiga/projects/references/models.py +++ b/taiga/projects/references/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -68,6 +68,19 @@ def make_reference(instance, project, create=False): return refval, refinstance +def recalc_reference_counter(project): + seqname = make_sequence_name(project) + max_ref_us = project.user_stories.all().aggregate(max=models.Max('ref')) + max_ref_task = project.tasks.all().aggregate(max=models.Max('ref')) + max_ref_issue = project.issues.all().aggregate(max=models.Max('ref')) + max_references = list(filter(lambda x: x is not None, [max_ref_us['max'], max_ref_task['max'], max_ref_issue['max']])) + + max_value = 0 + if len(max_references) > 0: + max_value = max(max_references) + seq.set_max(seqname, max_value) + + def create_sequence(sender, instance, created, **kwargs): if not created: return diff --git a/taiga/projects/references/permissions.py b/taiga/projects/references/permissions.py index 9baa2a5c..94f23767 100644 --- a/taiga/projects/references/permissions.py +++ b/taiga/projects/references/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/references/sequences.py b/taiga/projects/references/sequences.py index 711b5555..9c025387 100644 --- a/taiga/projects/references/sequences.py +++ b/taiga/projects/references/sequences.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/references/services.py b/taiga/projects/references/services.py index 6469937d..8db331c5 100644 --- a/taiga/projects/references/services.py +++ b/taiga/projects/references/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/references/validators.py b/taiga/projects/references/validators.py index e91adb21..ad51e42c 100644 --- a/taiga/projects/references/validators.py +++ b/taiga/projects/references/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index eb7b2e54..42e72575 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -213,6 +213,7 @@ class ProjectSerializer(serializers.LightSerializer): members = MethodField() total_milestones = Field() total_story_points = Field() + is_contact_activated = Field() is_epics_activated = Field() is_backlog_activated = Field() is_kanban_activated = Field() @@ -474,6 +475,7 @@ class ProjectTemplateSerializer(serializers.LightSerializer): created_date = Field() modified_date = Field() default_owner_role = Field() + is_contact_activated = Field() is_epics_activated = Field() is_backlog_activated = Field() is_kanban_activated = Field() diff --git a/taiga/projects/services/__init__.py b/taiga/projects/services/__init__.py index 3be0a9d8..1b0aa8b9 100644 --- a/taiga/projects/services/__init__.py +++ b/taiga/projects/services/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -52,6 +52,7 @@ from .projects import check_if_project_can_be_transfered from .projects import check_if_project_is_out_of_owner_limits from .projects import orphan_project from .projects import delete_project +from .projects import duplicate_project from .stats import get_stats_for_project_issues from .stats import get_stats_for_project diff --git a/taiga/projects/services/bulk_update_order.py b/taiga/projects/services/bulk_update_order.py index c07b48a2..093c97e8 100644 --- a/taiga/projects/services/bulk_update_order.py +++ b/taiga/projects/services/bulk_update_order.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/services/filters.py b/taiga/projects/services/filters.py index 0236e82b..ceecd29b 100644 --- a/taiga/projects/services/filters.py +++ b/taiga/projects/services/filters.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/services/invitations.py b/taiga/projects/services/invitations.py index 5e772bd2..1f644a86 100644 --- a/taiga/projects/services/invitations.py +++ b/taiga/projects/services/invitations.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/services/logo.py b/taiga/projects/services/logo.py index 1f4f7a38..96a3e292 100644 --- a/taiga/projects/services/logo.py +++ b/taiga/projects/services/logo.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 Taiga Agile LLC +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/projects/services/members.py b/taiga/projects/services/members.py index 1f91e215..e5d2f352 100644 --- a/taiga/projects/services/members.py +++ b/taiga/projects/services/members.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -16,13 +16,17 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from taiga.base.utils import db, text +from taiga.base.exceptions import ValidationError +from taiga.base.utils import db +from taiga.users.models import User from django.conf import settings +from django.core.validators import validate_email from django.utils.translation import ugettext as _ from .. import models + def get_members_from_bulk(bulk_data, **additional_fields): """Convert `bulk_data` into a list of members. @@ -34,6 +38,15 @@ def get_members_from_bulk(bulk_data, **additional_fields): members = [] for data in bulk_data: data_copy = data.copy() + username = data_copy.pop("username") + try: + validate_email(username) + data_copy["email"] = username + + except ValidationError: + user = User.objects.filter(username=username).first() + data_copy["user_id"] = user.id + data_copy.update(additional_fields) members.append(models.Membership(**data_copy)) return members @@ -42,7 +55,7 @@ def get_members_from_bulk(bulk_data, **additional_fields): def create_members_in_bulk(bulk_data, callback=None, precall=None, **additional_fields): """Create members from `bulk_data`. - :param bulk_data: List of dicts `{"project_id": <>, "role_id": <>, "email": <>}`. + :param bulk_data: List of dicts `{"project_id": <>, "role_id": <>, "username": <>}`. :param callback: Callback to execute after each task save. :param additional_fields: Additional fields when instantiating each task. @@ -71,9 +84,9 @@ def project_has_valid_admins(project, exclude_user=None): def can_user_leave_project(user, project): membership = project.memberships.get(user=user) if not membership.is_admin: - return True + return True - #The user can't leave if is the real owner of the project + # The user can't leave if is the real owner of the project if project.owner == user: return False diff --git a/taiga/projects/services/modules_config.py b/taiga/projects/services/modules_config.py index d48a7094..54e88c4b 100644 --- a/taiga/projects/services/modules_config.py +++ b/taiga/projects/services/modules_config.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/services/projects.py b/taiga/projects/services/projects.py index 2bd31d94..02e66539 100644 --- a/taiga/projects/services/projects.py +++ b/taiga/projects/services/projects.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,7 +19,12 @@ from django.apps import apps from django.utils.translation import ugettext as _ from taiga.celery import app +from taiga.base.api.utils import get_object_or_404 +from taiga.permissions import services as permissions_services +from taiga.projects.history.services import take_snapshot + from .. import choices +from ..apps import connect_projects_signals, disconnect_projects_signals ERROR_MAX_PUBLIC_PROJECTS_MEMBERSHIPS = 'max_public_projects_memberships' ERROR_MAX_PRIVATE_PROJECTS_MEMBERSHIPS = 'max_private_projects_memberships' @@ -27,7 +32,9 @@ ERROR_MAX_PUBLIC_PROJECTS = 'max_public_projects' ERROR_MAX_PRIVATE_PROJECTS = 'max_private_projects' ERROR_PROJECT_WITHOUT_OWNER = 'project_without_owner' -def check_if_project_privacity_can_be_changed(project, + +def check_if_project_privacity_can_be_changed( + project, current_memberships=None, current_private_projects=None, current_public_projects=None): @@ -91,7 +98,7 @@ def check_if_project_can_be_created_or_updated(project): if project.is_private: current_projects = project.owner.owned_projects.filter(is_private=True).count() max_projects = project.owner.max_private_projects - error_project_exceeded = _("You can't have more private projects") + error_project_exceeded = _("You can't have more private projects") current_memberships = project.memberships.count() or 1 max_memberships = project.owner.max_memberships_private_projects @@ -131,7 +138,7 @@ def check_if_project_can_be_transfered(project, new_owner): if project.is_private: current_projects = new_owner.owned_projects.filter(is_private=True).count() max_projects = new_owner.max_private_projects - error_project_exceeded = _("You can't have more private projects") + error_project_exceeded = _("You can't have more private projects") current_memberships = project.memberships.count() max_memberships = new_owner.max_memberships_private_projects @@ -154,7 +161,8 @@ def check_if_project_can_be_transfered(project, new_owner): return (True, None) -def check_if_project_is_out_of_owner_limits(project, +def check_if_project_is_out_of_owner_limits( + project, current_memberships=None, current_private_projects=None, current_public_projects=None): @@ -219,3 +227,47 @@ def delete_project(project_id): project.delete_related_content() project.delete() + + +def duplicate_project(project, **new_project_extra_args): + owner = new_project_extra_args.get("owner") + users = new_project_extra_args.pop("users") + + disconnect_projects_signals() + Project = apps.get_model("projects", "Project") + new_project = Project.objects.create(**new_project_extra_args) + connect_projects_signals() + + permissions_services.set_base_permissions_for_project(new_project) + + # Cloning the structure from the old project using templates + Template = apps.get_model("projects", "ProjectTemplate") + template = Template() + template.load_data_from_project(project) + template.apply_to_project(new_project) + new_project.creation_template = project.creation_template + new_project.save() + + # Creating the membership for the new owner + Membership = apps.get_model("projects", "Membership") + Membership.objects.create( + user=owner, + is_admin=True, + role=new_project.roles.get(slug=template.default_owner_role), + project=new_project + ) + + # Creating the extra memberships + for user in users: + project_memberships = project.memberships.exclude(user_id=owner.id) + membership = get_object_or_404(project_memberships, user_id=user["id"]) + Membership.objects.create( + user=membership.user, + is_admin=membership.is_admin, + role=new_project.roles.get(slug=membership.role.slug), + project=new_project + ) + + # Take initial snapshot for the project + take_snapshot(new_project, user=owner) + return new_project diff --git a/taiga/projects/services/stats.py b/taiga/projects/services/stats.py index 891be36b..7b18750e 100644 --- a/taiga/projects/services/stats.py +++ b/taiga/projects/services/stats.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/services/transfer.py b/taiga/projects/services/transfer.py index aa9ea934..6017d8ad 100644 --- a/taiga/projects/services/transfer.py +++ b/taiga/projects/services/transfer.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/signals.py b/taiga/projects/signals.py index b94e5cda..33bf4e59 100644 --- a/taiga/projects/signals.py +++ b/taiga/projects/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -20,9 +20,6 @@ from django.apps import apps from django.conf import settings from taiga.projects.notifications.services import create_notify_policy_if_not_exists -from taiga.base.utils.db import get_typename_for_model_class - -from easy_thumbnails.files import get_thumbnailer #################################### @@ -58,6 +55,13 @@ def project_post_save(sender, instance, created, **kwargs): if template is None: ProjectTemplate = apps.get_model("projects", "ProjectTemplate") template = ProjectTemplate.objects.get(slug=settings.DEFAULT_PROJECT_TEMPLATE) + + if instance.tags: + template.tags = instance.tags + + if instance.tags_colors: + template.tags_colors = instance.tags_colors + template.apply_to_project(instance) instance.save() diff --git a/taiga/projects/tagging/api.py b/taiga/projects/tagging/api.py index 01a7d07e..66d7ab7d 100644 --- a/taiga/projects/tagging/api.py +++ b/taiga/projects/tagging/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tagging/fields.py b/taiga/projects/tagging/fields.py index 3a33ca87..e1a9edf1 100644 --- a/taiga/projects/tagging/fields.py +++ b/taiga/projects/tagging/fields.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tagging/models.py b/taiga/projects/tagging/models.py index 970dae40..8c60e1d3 100644 --- a/taiga/projects/tagging/models.py +++ b/taiga/projects/tagging/models.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -30,7 +30,7 @@ class TaggedMixin(models.Model): abstract = True -class TagsColorsdMixin(models.Model): +class TagsColorsMixin(models.Model): tags_colors = ArrayField(ArrayField(models.TextField(null=True, blank=True), size=2), null=True, blank=True, default=[], verbose_name=_("tags colors")) diff --git a/taiga/projects/tagging/serializers.py b/taiga/projects/tagging/serializers.py index 494b508a..efafb376 100644 --- a/taiga/projects/tagging/serializers.py +++ b/taiga/projects/tagging/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tagging/services.py b/taiga/projects/tagging/services.py index 78fd00b6..e5b1072a 100644 --- a/taiga/projects/tagging/services.py +++ b/taiga/projects/tagging/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tagging/signals.py b/taiga/projects/tagging/signals.py index cc94461a..a60c3a37 100644 --- a/taiga/projects/tagging/signals.py +++ b/taiga/projects/tagging/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tagging/validators.py b/taiga/projects/tagging/validators.py index 4070045d..1252eeed 100644 --- a/taiga/projects/tagging/validators.py +++ b/taiga/projects/tagging/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/__init__.py b/taiga/projects/tasks/__init__.py index 78f3e8ae..2751da00 100644 --- a/taiga/projects/tasks/__init__.py +++ b/taiga/projects/tasks/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/admin.py b/taiga/projects/tasks/admin.py index 281cdb11..b090e596 100644 --- a/taiga/projects/tasks/admin.py +++ b/taiga/projects/tasks/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -39,11 +39,11 @@ class TaskAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["status", "milestone", "user_story"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( project=self.obj.project) elif (db_field.name in ["owner", "assigned_to"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/tasks/api.py b/taiga/projects/tasks/api.py index 39fb8cbc..60f8b6e4 100644 --- a/taiga/projects/tasks/api.py +++ b/taiga/projects/tasks/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -59,6 +59,8 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, Wa filters.QFilter, filters.CreatedDateFilter, filters.ModifiedDateFilter, + filters.MilestoneEstimatedStartFilter, + filters.MilestoneEstimatedFinishFilter, filters.FinishedDateFilter) filter_fields = ["user_story", "milestone", diff --git a/taiga/projects/tasks/apps.py b/taiga/projects/tasks/apps.py index 1ad2e96d..f7294215 100644 --- a/taiga/projects/tasks/apps.py +++ b/taiga/projects/tasks/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/migrations/0001_initial.py b/taiga/projects/tasks/migrations/0001_initial.py index 99537a7c..b07ae15f 100644 --- a/taiga/projects/tasks/migrations/0001_initial.py +++ b/taiga/projects/tasks/migrations/0001_initial.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields from django.conf import settings import django.utils.timezone @@ -21,7 +21,7 @@ class Migration(migrations.Migration): name='Task', fields=[ ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), - ('tags', djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='tags')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=[], null=True, size=None, verbose_name='tags')), ('version', models.IntegerField(default=1, verbose_name='version')), ('is_blocked', models.BooleanField(verbose_name='is blocked', default=False)), ('blocked_note', models.TextField(blank=True, verbose_name='blocked note', default='')), diff --git a/taiga/projects/tasks/migrations/0003_task_external_reference.py b/taiga/projects/tasks/migrations/0003_task_external_reference.py index 8222116e..9de35ca6 100644 --- a/taiga/projects/tasks/migrations/0003_task_external_reference.py +++ b/taiga/projects/tasks/migrations/0003_task_external_reference.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='task', name='external_reference', - field=djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='external reference'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=False, null=False), blank=True, default=None, null=True, size=None, verbose_name='external reference'), preserve_default=True, ), ] diff --git a/taiga/projects/tasks/migrations/0009_auto_20151104_1131.py b/taiga/projects/tasks/migrations/0009_auto_20151104_1131.py index da11ea7b..064f8848 100644 --- a/taiga/projects/tasks/migrations/0009_auto_20151104_1131.py +++ b/taiga/projects/tasks/migrations/0009_auto_20151104_1131.py @@ -12,11 +12,11 @@ WITH status_update AS( WITH status_update AS( WITH history_entries AS ( SELECT - diff#>>'{status, 1}' new_status_id, + diff #>>'{status, 1}' new_status_id, regexp_split_to_array(key, ':') as split_key, created_at as date FROM history_historyentry - WHERE diff#>>'{status, 1}' != '' + WHERE diff #>>'{status, 1}' != '' ) SELECT split_key[2] as object_id, diff --git a/taiga/projects/tasks/models.py b/taiga/projects/tasks/models.py index a0abe570..5b7b0045 100644 --- a/taiga/projects/tasks/models.py +++ b/taiga/projects/tasks/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/permissions.py b/taiga/projects/tasks/permissions.py index a1cbdfe1..46c6356f 100644 --- a/taiga/projects/tasks/permissions.py +++ b/taiga/projects/tasks/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/serializers.py b/taiga/projects/tasks/serializers.py index f0621581..04cab33e 100644 --- a/taiga/projects/tasks/serializers.py +++ b/taiga/projects/tasks/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -23,16 +23,20 @@ from taiga.base.neighbors import NeighborsSerializerMixin from taiga.mdrender.service import render as mdrender from taiga.projects.attachments.serializers import BasicAttachmentsInfoSerializerMixin from taiga.projects.mixins.serializers import OwnerExtraInfoSerializerMixin +from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin from taiga.projects.mixins.serializers import AssignedToExtraInfoSerializerMixin from taiga.projects.mixins.serializers import StatusExtraInfoSerializerMixin from taiga.projects.notifications.mixins import WatchedResourceSerializer from taiga.projects.tagging.serializers import TaggedInProjectResourceSerializer from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin +from taiga.projects.history.mixins import TotalCommentsSerializerMixin + class TaskListSerializer(VoteResourceSerializerMixin, WatchedResourceSerializer, OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin, - StatusExtraInfoSerializerMixin, BasicAttachmentsInfoSerializerMixin, - TaggedInProjectResourceSerializer, serializers.LightSerializer): + StatusExtraInfoSerializerMixin, ProjectExtraInfoSerializerMixin, + BasicAttachmentsInfoSerializerMixin, TaggedInProjectResourceSerializer, + TotalCommentsSerializerMixin, serializers.LightSerializer): id = Field() user_story = Field(attr="user_story_id") diff --git a/taiga/projects/tasks/services.py b/taiga/projects/tasks/services.py index b785d373..52f1bb55 100644 --- a/taiga/projects/tasks/services.py +++ b/taiga/projects/tasks/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/signals.py b/taiga/projects/tasks/signals.py index dfb698e5..aa40ca96 100644 --- a/taiga/projects/tasks/signals.py +++ b/taiga/projects/tasks/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/tasks/utils.py b/taiga/projects/tasks/utils.py index 0d8661fc..ee61a1f3 100644 --- a/taiga/projects/tasks/utils.py +++ b/taiga/projects/tasks/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -23,6 +23,7 @@ from taiga.projects.notifications.utils import attach_total_watchers_to_queryset from taiga.projects.notifications.utils import attach_is_watcher_to_queryset from taiga.projects.votes.utils import attach_total_voters_to_queryset from taiga.projects.votes.utils import attach_is_voter_to_queryset +from taiga.projects.history.utils import attach_total_comments_to_queryset def attach_user_story_extra_info(queryset, as_field="user_story_extra_info"): @@ -76,4 +77,5 @@ def attach_extra_info(queryset, user=None, include_attachments=False): queryset = attach_is_voter_to_queryset(queryset, user) queryset = attach_is_watcher_to_queryset(queryset, user) queryset = attach_user_story_extra_info(queryset) + queryset = attach_total_comments_to_queryset(queryset) return queryset diff --git a/taiga/projects/tasks/validators.py b/taiga/projects/tasks/validators.py index d7498cc6..7f38f80b 100644 --- a/taiga/projects/tasks/validators.py +++ b/taiga/projects/tasks/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/throttling.py b/taiga/projects/throttling.py index 7694b274..ac127285 100644 --- a/taiga/projects/throttling.py +++ b/taiga/projects/throttling.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,9 +19,9 @@ from taiga.base import throttling -class MembershipsRateThrottle(throttling.UserRateThrottle): +class MembershipsRateThrottle(throttling.ThrottleByActionMixin, throttling.UserRateThrottle): scope = "create-memberships" - throttled_methods = ["POST", "PUT"] + throttled_actions = ["create", "resend_invitation", "bulk_create"] def exceeded_throttling_restriction(self, request, view): self.created_memberships = 0 diff --git a/taiga/projects/translations.py b/taiga/projects/translations.py index 3f22f6e0..fdd8f2c0 100644 --- a/taiga/projects/translations.py +++ b/taiga/projects/translations.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/__init__.py b/taiga/projects/userstories/__init__.py index e73d4118..e18c925d 100644 --- a/taiga/projects/userstories/__init__.py +++ b/taiga/projects/userstories/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/admin.py b/taiga/projects/userstories/admin.py index 50c8bb7b..f95b433a 100644 --- a/taiga/projects/userstories/admin.py +++ b/taiga/projects/userstories/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -54,11 +54,11 @@ class UserStoryAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["status", "milestone", "generated_from_issue"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( project=self.obj.project) elif (db_field.name in ["owner", "assigned_to"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py index bb888f88..e536b8e6 100644 --- a/taiga/projects/userstories/api.py +++ b/taiga/projects/userstories/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -70,6 +70,8 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi base_filters.CreatedDateFilter, base_filters.ModifiedDateFilter, base_filters.FinishDateFilter, + base_filters.MilestoneEstimatedStartFilter, + base_filters.MilestoneEstimatedFinishFilter, base_filters.OrderByFilterMixin) filter_fields = ["project", "project__slug", @@ -104,6 +106,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi include_attachments = "include_attachments" in self.request.QUERY_PARAMS include_tasks = "include_tasks" in self.request.QUERY_PARAMS + epic_id = self.request.QUERY_PARAMS.get("epic", None) # We can be filtering by more than one epic so epic_id can consist # of different ids separete by comma. In that situation we will use diff --git a/taiga/projects/userstories/apps.py b/taiga/projects/userstories/apps.py index d2fa8ce1..8401fafd 100644 --- a/taiga/projects/userstories/apps.py +++ b/taiga/projects/userstories/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/filters.py b/taiga/projects/userstories/filters.py index ec19b6a7..316c1921 100644 --- a/taiga/projects/userstories/filters.py +++ b/taiga/projects/userstories/filters.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/migrations/0001_initial.py b/taiga/projects/userstories/migrations/0001_initial.py index 688a9050..3672aca5 100644 --- a/taiga/projects/userstories/migrations/0001_initial.py +++ b/taiga/projects/userstories/migrations/0001_initial.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings import django.utils.timezone -import djorm_pgarray.fields +import django.contrib.postgres.fields import django.db.models.deletion @@ -38,7 +38,7 @@ class Migration(migrations.Migration): name='UserStory', fields=[ ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), - ('tags', djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='tags')), + ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=[], null=True, size=None, verbose_name='tags')), ('version', models.IntegerField(default=1, verbose_name='version')), ('is_blocked', models.BooleanField(default=False, verbose_name='is blocked')), ('blocked_note', models.TextField(default='', blank=True, verbose_name='blocked note')), diff --git a/taiga/projects/userstories/migrations/0007_userstory_external_reference.py b/taiga/projects/userstories/migrations/0007_userstory_external_reference.py index 3cbbceeb..fbfd1f3f 100644 --- a/taiga/projects/userstories/migrations/0007_userstory_external_reference.py +++ b/taiga/projects/userstories/migrations/0007_userstory_external_reference.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='userstory', name='external_reference', - field=djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='external reference'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=False, null=False), blank=True, default=None, null=True, size=None, verbose_name='external reference'), preserve_default=True, ), ] diff --git a/taiga/projects/userstories/models.py b/taiga/projects/userstories/models.py index aaf78ad4..a6f3a414 100644 --- a/taiga/projects/userstories/models.py +++ b/taiga/projects/userstories/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -143,14 +143,14 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod return self.role_points def get_total_points(self): - not_null_role_points = [rp for rp in self.role_points.all() if rp.points.value is not None] + not_null_role_points = [ + rp.points.value + for rp in self.role_points.all() + if rp.points.value is not None + ] #If we only have None values the sum should be None if not not_null_role_points: return None - total = 0.0 - for rp in not_null_role_points: - total += rp.points.value - - return total + return sum(not_null_role_points) diff --git a/taiga/projects/userstories/permissions.py b/taiga/projects/userstories/permissions.py index 2d200446..a762c670 100644 --- a/taiga/projects/userstories/permissions.py +++ b/taiga/projects/userstories/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/serializers.py b/taiga/projects/userstories/serializers.py index b7c43466..3dcd2196 100644 --- a/taiga/projects/userstories/serializers.py +++ b/taiga/projects/userstories/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -29,6 +29,7 @@ from taiga.projects.mixins.serializers import StatusExtraInfoSerializerMixin from taiga.projects.notifications.mixins import WatchedResourceSerializer from taiga.projects.tagging.serializers import TaggedInProjectResourceSerializer from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin +from taiga.projects.history.mixins import TotalCommentsSerializerMixin class OriginIssueSerializer(serializers.LightSerializer): @@ -47,7 +48,7 @@ class UserStoryListSerializer(ProjectExtraInfoSerializerMixin, VoteResourceSerializerMixin, WatchedResourceSerializer, OwnerExtraInfoSerializerMixin, AssignedToExtraInfoSerializerMixin, StatusExtraInfoSerializerMixin, BasicAttachmentsInfoSerializerMixin, - TaggedInProjectResourceSerializer, + TaggedInProjectResourceSerializer, TotalCommentsSerializerMixin, serializers.LightSerializer): id = Field() diff --git a/taiga/projects/userstories/services.py b/taiga/projects/userstories/services.py index a7ded8e5..efd66229 100644 --- a/taiga/projects/userstories/services.py +++ b/taiga/projects/userstories/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/signals.py b/taiga/projects/userstories/signals.py index ba643b61..dc7f9151 100644 --- a/taiga/projects/userstories/signals.py +++ b/taiga/projects/userstories/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/userstories/utils.py b/taiga/projects/userstories/utils.py index 57e4ecd3..3a903491 100644 --- a/taiga/projects/userstories/utils.py +++ b/taiga/projects/userstories/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -21,6 +21,7 @@ from taiga.projects.attachments.utils import attach_basic_attachments from taiga.projects.notifications.utils import attach_watchers_to_queryset from taiga.projects.notifications.utils import attach_total_watchers_to_queryset from taiga.projects.notifications.utils import attach_is_watcher_to_queryset +from taiga.projects.history.utils import attach_total_comments_to_queryset from taiga.projects.votes.utils import attach_total_voters_to_queryset from taiga.projects.votes.utils import attach_is_voter_to_queryset @@ -174,4 +175,5 @@ def attach_extra_info(queryset, user=None, include_attachments=False, include_ta queryset = attach_total_watchers_to_queryset(queryset) queryset = attach_is_voter_to_queryset(queryset, user) queryset = attach_is_watcher_to_queryset(queryset, user) + queryset = attach_total_comments_to_queryset(queryset) return queryset diff --git a/taiga/projects/userstories/validators.py b/taiga/projects/userstories/validators.py index cf901d10..0ebdcc4b 100644 --- a/taiga/projects/userstories/validators.py +++ b/taiga/projects/userstories/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/utils.py b/taiga/projects/utils.py index fbec4853..ed26647c 100644 --- a/taiga/projects/utils.py +++ b/taiga/projects/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . + def attach_members(queryset, as_field="members_attr"): """Attach a json members representation to each object of the queryset. diff --git a/taiga/projects/validators.py b/taiga/projects/validators.py index 54a43178..fdbba552 100644 --- a/taiga/projects/validators.py +++ b/taiga/projects/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -21,10 +21,12 @@ from django.utils.translation import ugettext as _ from taiga.base.api import serializers from taiga.base.api import validators +from taiga.base.api.fields import validate_user_email_allowed_domains, InvalidEmailValidationError from taiga.base.exceptions import ValidationError -from taiga.base.fields import JsonField +from taiga.base.fields import JSONField from taiga.base.fields import PgArrayField -from taiga.users.models import Role +from taiga.users.models import User, Role + from .tagging.fields import TagsField @@ -112,21 +114,22 @@ class IssueTypeValidator(DuplicatedNameInProjectValidator, validators.ModelValid ###################################################### class MembershipValidator(validators.ModelValidator): - email = serializers.EmailField(required=True) + username = serializers.CharField(required=True) class Meta: model = models.Membership - # IMPORTANT: Maintain the MembershipAdminSerializer Meta up to date - # with this info (excluding here user_email and email) - read_only_fields = ("user",) - exclude = ("token", "email") + read_only_fields = ("user", "email") - def validate_email(self, attrs, source): - project = attrs.get("project", None) + def restore_object(self, attrs, instance=None): + username = attrs.pop("username", None) + obj = super(MembershipValidator, self).restore_object(attrs, instance=instance) + obj.username = username + return obj + + def _validate_member_doesnt_exist(self, attrs, email): + project = attrs.get("project", None if self.object is None else self.object.project) if project is None: - project = self.object.project - - email = attrs[source] + return attrs qs = models.Membership.objects.all() @@ -139,14 +142,12 @@ class MembershipValidator(validators.ModelValidator): Q(project_id=project.id, email=email)) if qs.count() > 0: - raise ValidationError(_("Email address is already taken")) - - return attrs + raise ValidationError(_("The user yet exists in the project")) def validate_role(self, attrs, source): - project = attrs.get("project", None) + project = attrs.get("project", None if self.object is None else self.object.project) if project is None: - project = self.object.project + return attrs role = attrs[source] @@ -155,10 +156,35 @@ class MembershipValidator(validators.ModelValidator): return attrs + def validate_username(self, attrs, source): + username = attrs.get(source, None) + try: + validate_user_email_allowed_domains(username) + + except ValidationError: + # If the validation comes from a request let's check the user is a valid contact + request = self.context.get("request", None) + if request is not None and request.user.is_authenticated(): + valid_usernames = request.user.contacts_visible_by_user(request.user).values_list("username", flat=True) + if username not in valid_usernames: + raise ValidationError(_("The user must be a valid contact")) + + user = User.objects.filter(Q(username=username) | Q(email=username)).first() + if user is not None: + email = user.email + self.user = user + + else: + email = username + + self.email = email + self._validate_member_doesnt_exist(attrs, email) + return attrs + def validate_is_admin(self, attrs, source): - project = attrs.get("project", None) + project = attrs.get("project", None if self.object is None else self.object.project) if project is None: - project = self.object.project + return attrs if (self.object and self.object.user): if self.object.user.id == project.owner_id and not attrs[source]: @@ -171,20 +197,35 @@ class MembershipValidator(validators.ModelValidator): return attrs + def is_valid(self): + errors = super().is_valid() + if hasattr(self, "email") and self.object is not None: + self.object.email = self.email -class MembershipAdminValidator(MembershipValidator): - class Meta: - model = models.Membership - # IMPORTANT: Maintain the MembershipSerializer Meta up to date - # with this info (excluding there user_email and email) - read_only_fields = ("user",) - exclude = ("token",) + if hasattr(self, "user") and self.object is not None: + self.object.user = self.user + + return errors class _MemberBulkValidator(validators.Validator): - email = serializers.EmailField() + username = serializers.CharField() role_id = serializers.IntegerField() + def validate_username(self, attrs, source): + username = attrs.get(source) + try: + validate_user_email_allowed_domains(username) + except InvalidEmailValidationError: + # If the validation comes from a request let's check the user is a valid contact + request = self.context.get("request", None) + if request is not None and request.user.is_authenticated(): + valid_usernames = set(request.user.contacts_visible_by_user(request.user).values_list("username", flat=True)) + if username not in valid_usernames: + raise ValidationError(_("The user must be a valid contact")) + + return attrs + class MembersBulkValidator(ProjectExistsValidator, validators.Validator): project_id = serializers.IntegerField() @@ -192,12 +233,10 @@ class MembersBulkValidator(ProjectExistsValidator, validators.Validator): invitation_extra_text = serializers.CharField(required=False, max_length=255) def validate_bulk_memberships(self, attrs, source): - filters = { - "project__id": attrs["project_id"], - "id__in": [r["role_id"] for r in attrs["bulk_memberships"]] - } + project_id = attrs["project_id"] + role_ids = [r["role_id"] for r in attrs["bulk_memberships"]] - if Role.objects.filter(**filters).count() != len(set(filters["id__in"])): + if Role.objects.filter(project_id=project_id, id__in=role_ids).count() != len(set(role_ids)): raise ValidationError(_("Invalid role ids. All roles must belong to the same project.")) return attrs @@ -222,15 +261,15 @@ class ProjectValidator(validators.ModelValidator): ###################################################### class ProjectTemplateValidator(validators.ModelValidator): - default_options = JsonField(required=False, label=_("Default options")) - us_statuses = JsonField(required=False, label=_("User story's statuses")) - points = JsonField(required=False, label=_("Points")) - task_statuses = JsonField(required=False, label=_("Task's statuses")) - issue_statuses = JsonField(required=False, label=_("Issue's statuses")) - issue_types = JsonField(required=False, label=_("Issue's types")) - priorities = JsonField(required=False, label=_("Priorities")) - severities = JsonField(required=False, label=_("Severities")) - roles = JsonField(required=False, label=_("Roles")) + default_options = JSONField(required=False, label=_("Default options")) + us_statuses = JSONField(required=False, label=_("User story's statuses")) + points = JSONField(required=False, label=_("Points")) + task_statuses = JSONField(required=False, label=_("Task's statuses")) + issue_statuses = JSONField(required=False, label=_("Issue's statuses")) + issue_types = JSONField(required=False, label=_("Issue's types")) + priorities = JSONField(required=False, label=_("Priorities")) + severities = JSONField(required=False, label=_("Severities")) + roles = JSONField(required=False, label=_("Roles")) class Meta: model = models.ProjectTemplate @@ -238,9 +277,25 @@ class ProjectTemplateValidator(validators.ModelValidator): ###################################################### -# Project order bulk serializers +# Project order bulk validators ###################################################### class UpdateProjectOrderBulkValidator(ProjectExistsValidator, validators.Validator): project_id = serializers.IntegerField() order = serializers.IntegerField() + + +###################################################### +# Project duplication validator +###################################################### + + +class DuplicateProjectMemberValidator(validators.Validator): + id = serializers.CharField() + + +class DuplicateProjectValidator(validators.Validator): + name = serializers.CharField() + description = serializers.CharField() + is_private = serializers.BooleanField() + users = DuplicateProjectMemberValidator(many=True) diff --git a/taiga/projects/votes/admin.py b/taiga/projects/votes/admin.py index d7a040e5..c5550802 100644 --- a/taiga/projects/votes/admin.py +++ b/taiga/projects/votes/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/votes/mixins/serializers.py b/taiga/projects/votes/mixins/serializers.py index 9f9d1049..e2f57318 100644 --- a/taiga/projects/votes/mixins/serializers.py +++ b/taiga/projects/votes/mixins/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/votes/mixins/viewsets.py b/taiga/projects/votes/mixins/viewsets.py index 50490ba7..4eaa43e6 100644 --- a/taiga/projects/votes/mixins/viewsets.py +++ b/taiga/projects/votes/mixins/viewsets.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/votes/models.py b/taiga/projects/votes/models.py index cfc0947e..8e5dbf18 100644 --- a/taiga/projects/votes/models.py +++ b/taiga/projects/votes/models.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/votes/serializers.py b/taiga/projects/votes/serializers.py index b97bd3bf..db479b24 100644 --- a/taiga/projects/votes/serializers.py +++ b/taiga/projects/votes/serializers.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/votes/services.py b/taiga/projects/votes/services.py index 5c99efa9..f6c3c1f2 100644 --- a/taiga/projects/votes/services.py +++ b/taiga/projects/votes/services.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/votes/utils.py b/taiga/projects/votes/utils.py index 077abd46..87f6f828 100644 --- a/taiga/projects/votes/utils.py +++ b/taiga/projects/votes/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/wiki/admin.py b/taiga/projects/wiki/admin.py index 73834938..a42b0c3e 100644 --- a/taiga/projects/wiki/admin.py +++ b/taiga/projects/wiki/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -38,7 +38,7 @@ class WikiPageAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if (db_field.name in ["owner", "last_modifier"] and getattr(self, 'obj', None)): - kwargs["queryset"] = db_field.related.model.objects.filter( + kwargs["queryset"] = db_field.related_model.objects.filter( memberships__project=self.obj.project) return super().formfield_for_foreignkey(db_field, request, **kwargs) diff --git a/taiga/projects/wiki/api.py b/taiga/projects/wiki/api.py index d6a3d44a..bc3002ab 100644 --- a/taiga/projects/wiki/api.py +++ b/taiga/projects/wiki/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/wiki/migrations/0005_auto_20161201_1628.py b/taiga/projects/wiki/migrations/0005_auto_20161201_1628.py new file mode 100644 index 00000000..87723645 --- /dev/null +++ b/taiga/projects/wiki/migrations/0005_auto_20161201_1628.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-12-01 16:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wiki', '0004_auto_20160928_0540'), + ] + + operations = [ + migrations.AlterField( + model_name='wikipage', + name='slug', + field=models.SlugField(allow_unicode=True, max_length=500, verbose_name='slug'), + ), + ] diff --git a/taiga/projects/wiki/models.py b/taiga/projects/wiki/models.py index 1c51fff0..e280448a 100644 --- a/taiga/projects/wiki/models.py +++ b/taiga/projects/wiki/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -33,7 +33,7 @@ class WikiPage(OCCModelMixin, WatchedModelMixin, models.Model): project = models.ForeignKey("projects.Project", null=False, blank=False, related_name="wiki_pages", verbose_name=_("project")) slug = models.SlugField(max_length=500, db_index=True, null=False, blank=False, - verbose_name=_("slug")) + verbose_name=_("slug"), allow_unicode=True) content = models.TextField(null=False, blank=True, verbose_name=_("content")) owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, diff --git a/taiga/projects/wiki/permissions.py b/taiga/projects/wiki/permissions.py index 5f2311b2..1b005778 100644 --- a/taiga/projects/wiki/permissions.py +++ b/taiga/projects/wiki/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/wiki/serializers.py b/taiga/projects/wiki/serializers.py index a7e36c60..8a3a33a8 100644 --- a/taiga/projects/wiki/serializers.py +++ b/taiga/projects/wiki/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,11 +19,15 @@ from taiga.base.api import serializers from taiga.base.fields import Field, MethodField from taiga.projects.history import services as history_service +from taiga.projects.mixins.serializers import ProjectExtraInfoSerializerMixin from taiga.projects.notifications.mixins import WatchedResourceSerializer from taiga.mdrender.service import render as mdrender -class WikiPageSerializer(WatchedResourceSerializer, serializers.LightSerializer): +class WikiPageSerializer( + WatchedResourceSerializer, ProjectExtraInfoSerializerMixin, + serializers.LightSerializer +): id = Field() project = Field(attr="project_id") slug = Field() diff --git a/taiga/projects/wiki/utils.py b/taiga/projects/wiki/utils.py index ecbf7602..e623cf04 100644 --- a/taiga/projects/wiki/utils.py +++ b/taiga/projects/wiki/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/projects/wiki/validators.py b/taiga/projects/wiki/validators.py index 033fac1b..fd7c4ba8 100644 --- a/taiga/projects/wiki/validators.py +++ b/taiga/projects/wiki/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,12 +17,15 @@ # along with this program. If not, see . from taiga.base.api import validators +from taiga.base.api import serializers from taiga.projects.notifications.validators import WatchersValidator from . import models class WikiPageValidator(WatchersValidator, validators.ModelValidator): + slug = serializers.CharField() + class Meta: model = models.WikiPage read_only_fields = ('modified_date', 'created_date', 'owner') diff --git a/taiga/routers.py b/taiga/routers.py index 92f2d58c..231755f2 100644 --- a/taiga/routers.py +++ b/taiga/routers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,6 +17,7 @@ # along with this program. If not, see . from taiga.base import routers +from django.conf import settings router = routers.DefaultRouter(trailing_slash=False) @@ -223,6 +224,10 @@ router.register(r"history/task", TaskHistory, base_name="task-history") router.register(r"history/issue", IssueHistory, base_name="issue-history") router.register(r"history/wiki", WikiHistory, base_name="wiki-history") +# Contact +from taiga.projects.contact.api import ContactViewSet +router.register(r"contact", ContactViewSet, base_name="contact") + # Timelines from taiga.timeline.api import ProfileTimeline @@ -279,6 +284,23 @@ from taiga.external_apps.api import Application, ApplicationToken router.register(r"applications", Application, base_name="applications") router.register(r"application-tokens", ApplicationToken, base_name="application-tokens") +# Third party importers +if settings.IMPORTERS.get('trello', {}).get('active', False): + from taiga.importers.trello.api import TrelloImporterViewSet + router.register(r"importers/trello", TrelloImporterViewSet, base_name="importers-trello") + +if settings.IMPORTERS.get('jira', {}).get('active', False): + from taiga.importers.jira.api import JiraImporterViewSet + router.register(r"importers/jira", JiraImporterViewSet, base_name="importers-jira") + +if settings.IMPORTERS.get('github', {}).get('active', False): + from taiga.importers.github.api import GithubImporterViewSet + router.register(r"importers/github", GithubImporterViewSet, base_name="importers-github") + +if settings.IMPORTERS.get('asana', {}).get('active', False): + from taiga.importers.asana.api import AsanaImporterViewSet + router.register(r"importers/asana", AsanaImporterViewSet, base_name="importers-asana") + # Stats # - see taiga.stats.routers and taiga.stats.apps diff --git a/taiga/searches/api.py b/taiga/searches/api.py index 0c252b5a..562c0b7e 100644 --- a/taiga/searches/api.py +++ b/taiga/searches/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/searches/serializers.py b/taiga/searches/serializers.py index 7adc34b2..f2a8c66c 100644 --- a/taiga/searches/serializers.py +++ b/taiga/searches/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/searches/services.py b/taiga/searches/services.py index adda60bb..6e43efd2 100644 --- a/taiga/searches/services.py +++ b/taiga/searches/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -55,23 +55,23 @@ def search_issues(project, text): def search_wiki_pages(project, text): model = apps.get_model("wiki", "WikiPage") queryset = model.objects.filter(project_id=project.pk) - tsquery = "to_tsquery('english_nostop', %s)" + tsquery = "to_tsquery('simple', %s)" tsvector = """ - setweight(to_tsvector('english_nostop', coalesce(wiki_wikipage.slug)), 'A') || - setweight(to_tsvector('english_nostop', coalesce(wiki_wikipage.content)), 'B') + setweight(to_tsvector('simple', coalesce(wiki_wikipage.slug)), 'A') || + setweight(to_tsvector('simple', coalesce(wiki_wikipage.content)), 'B') """ return _search_by_query(queryset, tsquery, tsvector, text) def _search_items(queryset, table, text): - tsquery = "to_tsquery('english_nostop', %s)" + tsquery = "to_tsquery('simple', %s)" tsvector = """ - setweight(to_tsvector('english_nostop', + setweight(to_tsvector('simple', coalesce({table}.subject) || ' ' || coalesce({table}.ref)), 'A') || - setweight(to_tsvector('english_nostop', coalesce(inmutable_array_to_string({table}.tags))), 'B') || - setweight(to_tsvector('english_nostop', coalesce({table}.description)), 'C') + setweight(to_tsvector('simple', coalesce(inmutable_array_to_string({table}.tags))), 'B') || + setweight(to_tsvector('simple', coalesce({table}.description)), 'C') """.format(table=table) return _search_by_query(queryset, tsquery, tsvector, text) diff --git a/taiga/stats/__init__.py b/taiga/stats/__init__.py index b393a91f..11b8fc6f 100644 --- a/taiga/stats/__init__.py +++ b/taiga/stats/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/stats/api.py b/taiga/stats/api.py index 5efeed1f..1b2d1c2f 100644 --- a/taiga/stats/api.py +++ b/taiga/stats/api.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/stats/apps.py b/taiga/stats/apps.py index 7e4d9729..47a26838 100644 --- a/taiga/stats/apps.py +++ b/taiga/stats/apps.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/stats/permissions.py b/taiga/stats/permissions.py index 83d8aca7..39e65c4c 100644 --- a/taiga/stats/permissions.py +++ b/taiga/stats/permissions.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/stats/routers.py b/taiga/stats/routers.py index 4623f3ca..b39d0d79 100644 --- a/taiga/stats/routers.py +++ b/taiga/stats/routers.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/stats/services.py b/taiga/stats/services.py index da551016..62f88716 100644 --- a/taiga/stats/services.py +++ b/taiga/stats/services.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 diff --git a/taiga/timeline/__init__.py b/taiga/timeline/__init__.py index 2ce879cf..dfbd438a 100644 --- a/taiga/timeline/__init__.py +++ b/taiga/timeline/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/api.py b/taiga/timeline/api.py index 2ebe5fa2..b23a5cbc 100644 --- a/taiga/timeline/api.py +++ b/taiga/timeline/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/apps.py b/taiga/timeline/apps.py index fa951716..75388a53 100644 --- a/taiga/timeline/apps.py +++ b/taiga/timeline/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/management/commands/_clear_unnecessary_new_membership_entries.py b/taiga/timeline/management/commands/_clear_unnecessary_new_membership_entries.py index 2f5f581c..d8ec58a2 100644 --- a/taiga/timeline/management/commands/_clear_unnecessary_new_membership_entries.py +++ b/taiga/timeline/management/commands/_clear_unnecessary_new_membership_entries.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py b/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py deleted file mode 100644 index f3a5fa57..00000000 --- a/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -# 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 . - -# Examples: -# python manage.py rebuild_timeline_for_user_creation --settings=settings.local_timeline - -from django.contrib.auth import get_user_model -from django.contrib.contenttypes.models import ContentType -from django.core.management.base import BaseCommand -from django.db.models import Model -from django.test.utils import override_settings - -from taiga.timeline.service import _get_impl_key_from_model, -from taiga.timeline.service import _timeline_impl_map, -from taiga.timeline.service import extract_user_info) -from taiga.timeline.models import Timeline -from taiga.timeline.signals import _push_to_timelines - -from unittest.mock import patch - -import gc - - -class BulkCreator(object): - def __init__(self): - self.timeline_objects = [] - self.created = None - - def create_element(self, element): - self.timeline_objects.append(element) - if len(self.timeline_objects) > 1000: - self.flush() - - def flush(self): - Timeline.objects.bulk_create(self.timeline_objects, batch_size=1000) - del self.timeline_objects - self.timeline_objects = [] - gc.collect() - -bulk_creator = BulkCreator() - - -def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, - namespace:str="default", extra_data:dict={}): - assert isinstance(obj, Model), "obj must be a instance of Model" - assert isinstance(instance, Model), "instance must be a instance of Model" - event_type_key = _get_impl_key_from_model(instance.__class__, event_type) - impl = _timeline_impl_map.get(event_type_key, None) - - bulk_creator.create_element(Timeline( - content_object=obj, - namespace=namespace, - event_type=event_type_key, - project=None, - data=impl(instance, extra_data=extra_data), - data_content_type = ContentType.objects.get_for_model(instance.__class__), - created=created_datetime, - )) - - -def generate_timeline(): - with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline): - # Users api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case - users = get_user_model().objects.order_by("date_joined") - for user in users.iterator(): - print("User:", user.date_joined) - extra_data = { - "values_diff": {}, - "user": extract_user_info(user), - } - _push_to_timelines(None, user, user, "create", user.date_joined, extra_data=extra_data) - del extra_data - - bulk_creator.flush() - - -class Command(BaseCommand): - help = 'Regenerate project timeline' - - @override_settings(DEBUG=False) - def handle(self, *args, **options): - generate_timeline() diff --git a/taiga/timeline/management/commands/_update_timeline_for_updated_tasks.py b/taiga/timeline/management/commands/_update_timeline_for_updated_tasks.py index 6c37b17c..4da0ca42 100644 --- a/taiga/timeline/management/commands/_update_timeline_for_updated_tasks.py +++ b/taiga/timeline/management/commands/_update_timeline_for_updated_tasks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/management/commands/rebuild_timeline.py b/taiga/timeline/management/commands/rebuild_timeline.py index 674f6b9d..cb9e0bfe 100644 --- a/taiga/timeline/management/commands/rebuild_timeline.py +++ b/taiga/timeline/management/commands/rebuild_timeline.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -21,154 +21,43 @@ # python manage.py rebuild_timeline --settings=settings.local_timeline --purge # python manage.py rebuild_timeline --settings=settings.local_timeline --initial_date 2014-10-02 -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ObjectDoesNotExist from django.core.management.base import BaseCommand -from django.db.models import Model from django.test.utils import override_settings -from taiga.projects.models import Project -from taiga.projects.history.models import HistoryEntry from taiga.timeline.models import Timeline -from taiga.timeline.service import _get_impl_key_from_model,_timeline_impl_map, extract_user_info -from taiga.timeline.signals import on_new_history_entry, _push_to_timelines +from taiga.timeline.rebuilder import rebuild_timeline -from unittest.mock import patch from optparse import make_option -import gc - - -class BulkCreator(object): - def __init__(self): - self.timeline_objects = [] - self.created = None - - def create_element(self, element): - self.timeline_objects.append(element) - if len(self.timeline_objects) > 1000: - self.flush() - - def flush(self): - Timeline.objects.bulk_create(self.timeline_objects, batch_size=1000) - del self.timeline_objects - self.timeline_objects = [] - gc.collect() - -bulk_creator = BulkCreator() - - -def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, - namespace:str="default", extra_data:dict={}): - assert isinstance(obj, Model), "obj must be a instance of Model" - assert isinstance(instance, Model), "instance must be a instance of Model" - event_type_key = _get_impl_key_from_model(instance.__class__, event_type) - impl = _timeline_impl_map.get(event_type_key, None) - - bulk_creator.create_element(Timeline( - content_object=obj, - namespace=namespace, - event_type=event_type_key, - project=instance.project, - data=impl(instance, extra_data=extra_data), - data_content_type=ContentType.objects.get_for_model(instance.__class__), - created=created_datetime, - )) - - -def generate_timeline(initial_date, final_date, project_id): - if initial_date or final_date or project_id: - timelines = Timeline.objects.all() - if initial_date: - timelines = timelines.filter(created__gte=initial_date) - if final_date: - timelines = timelines.filter(created__lt=final_date) - if project_id: - timelines = timelines.filter(project__id=project_id) - - timelines.delete() - - with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline): - # Projects api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case - projects = Project.objects.order_by("created_date") - history_entries = HistoryEntry.objects.order_by("created_at") - - if initial_date: - projects = projects.filter(created_date__gte=initial_date) - history_entries = history_entries.filter(created_at__gte=initial_date) - - if final_date: - projects = projects.filter(created_date__lt=final_date) - history_entries = history_entries.filter(created_at__lt=final_date) - - if project_id: - project = Project.objects.get(id=project_id) - epic_keys = ['epics.epic:%s'%(id) for id in project.epics.values_list("id", flat=True)] - us_keys = ['userstories.userstory:%s'%(id) for id in project.user_stories.values_list("id", - flat=True)] - tasks_keys = ['tasks.task:%s'%(id) for id in project.tasks.values_list("id", flat=True)] - issue_keys = ['issues.issue:%s'%(id) for id in project.issues.values_list("id", flat=True)] - wiki_keys = ['wiki.wikipage:%s'%(id) for id in project.wiki_pages.values_list("id", flat=True)] - keys = epic_keys + us_keys + tasks_keys + issue_keys + wiki_keys - - projects = projects.filter(id=project_id) - history_entries = history_entries.filter(key__in=keys) - - #Memberships - for membership in project.memberships.exclude(user=None).exclude(user=project.owner): - _push_to_timelines(project, membership.user, membership, "create", membership.created_at) - - for project in projects.iterator(): - print("Project:", project) - extra_data = { - "values_diff": {}, - "user": extract_user_info(project.owner), - } - _push_to_timelines(project, project.owner, project, "create", project.created_date, - extra_data=extra_data) - del extra_data - - for historyEntry in history_entries.iterator(): - print("History entry:", historyEntry.created_at) - try: - on_new_history_entry(None, historyEntry, None) - except ObjectDoesNotExist as e: - print("Ignoring") - - bulk_creator.flush() - class Command(BaseCommand): help = 'Regenerate project timeline' - option_list = BaseCommand.option_list + ( - make_option('--purge', - action='store_true', - dest='purge', - default=False, - help='Purge existing timelines'), - ) + ( - make_option('--initial_date', - action='store', - dest='initial_date', - default=None, - help='Initial date for timeline generation'), - ) + ( - make_option('--final_date', - action='store', - dest='final_date', - default=None, - help='Final date for timeline generation'), - ) + ( - make_option('--project', - action='store', - dest='project', - default=None, - help='Selected project id for timeline generation'), - ) + + def add_arguments(self, parser): + parser.add_argument('--purge', + action='store_true', + dest='purge', + default=False, + help='Purge existing timelines') + parser.add_argument('--initial_date', + action='store', + dest='initial_date', + default=None, + help='Initial date for timeline generation') + parser.add_argument('--final_date', + action='store', + dest='final_date', + default=None, + help='Final date for timeline generation') + parser.add_argument('--project', + action='store', + dest='project', + default=None, + help='Selected project id for timeline generation') @override_settings(DEBUG=False) def handle(self, *args, **options): if options["purge"] == True: Timeline.objects.all().delete() - generate_timeline(options["initial_date"], options["final_date"], options["project"]) + rebuild_timeline(options["initial_date"], options["final_date"], options["project"]) diff --git a/taiga/timeline/management/commands/rebuild_timeline_iterating_per_projects.py b/taiga/timeline/management/commands/rebuild_timeline_iterating_per_projects.py index f4c1a0a4..83651196 100644 --- a/taiga/timeline/management/commands/rebuild_timeline_iterating_per_projects.py +++ b/taiga/timeline/management/commands/rebuild_timeline_iterating_per_projects.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/migrations/0001_initial.py b/taiga/timeline/migrations/0001_initial.py index 5a2a5fb6..2f6fa746 100644 --- a/taiga/timeline/migrations/0001_initial.py +++ b/taiga/timeline/migrations/0001_initial.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields import django.utils.timezone @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('object_id', models.PositiveIntegerField()), ('namespace', models.SlugField(default='default')), ('event_type', models.SlugField()), - ('data', django_pgjson.fields.JsonField()), + ('data', taiga.base.db.models.fields.JSONField()), ('created', models.DateTimeField(default=django.utils.timezone.now)), ('content_type', models.ForeignKey(to='contenttypes.ContentType')), ], diff --git a/taiga/timeline/migrations/0002_auto_20150327_1056.py b/taiga/timeline/migrations/0002_auto_20150327_1056.py index 6c8a2066..ee6f2199 100644 --- a/taiga/timeline/migrations/0002_auto_20150327_1056.py +++ b/taiga/timeline/migrations/0002_auto_20150327_1056.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields from django.utils import timezone class Migration(migrations.Migration): @@ -27,7 +27,7 @@ class Migration(migrations.Migration): ('namespace', models.SlugField(default='default')), ('event_type', models.SlugField()), ('project', models.ForeignKey(to='projects.Project')), - ('data', django_pgjson.fields.JsonField()), + ('data', taiga.base.db.models.fields.JSONField()), ('data_content_type', models.ForeignKey(to='contenttypes.ContentType', related_name='data_timelines')), ('created', models.DateTimeField(default=timezone.now)), ('content_type', models.ForeignKey(to='contenttypes.ContentType', related_name='content_type_timelines')), diff --git a/taiga/timeline/migrations/0006_json_to_jsonb.py b/taiga/timeline/migrations/0006_json_to_jsonb.py new file mode 100644 index 00000000..de9b3830 --- /dev/null +++ b/taiga/timeline/migrations/0006_json_to_jsonb.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:35 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + + +class Migration(migrations.Migration): + + dependencies = [ + ('timeline', '0005_auto_20160706_0723'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="timeline_timeline", + column_name="data", + ), + reverse_sql=migrations.RunSQL.noop + ), + ] diff --git a/taiga/timeline/models.py b/taiga/timeline/models.py index ebee7da5..3e75c0ea 100644 --- a/taiga/timeline/models.py +++ b/taiga/timeline/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,7 +17,7 @@ # along with this program. If not, see . from django.db import models -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from django.utils import timezone from django.contrib.contenttypes.models import ContentType @@ -33,7 +33,7 @@ class Timeline(models.Model): namespace = models.CharField(max_length=250, default="default", db_index=True) event_type = models.CharField(max_length=250, db_index=True) project = models.ForeignKey(Project, null=True) - data = JsonField() + data = JSONField() data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") created = models.DateTimeField(default=timezone.now, db_index=True) diff --git a/taiga/timeline/permissions.py b/taiga/timeline/permissions.py index 61a20130..2a7d537d 100644 --- a/taiga/timeline/permissions.py +++ b/taiga/timeline/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/rebuilder.py b/taiga/timeline/rebuilder.py new file mode 100644 index 00000000..c7ebcfef --- /dev/null +++ b/taiga/timeline/rebuilder.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Model +from django.test.utils import override_settings + +from taiga.projects.models import Project +from taiga.projects.history.models import HistoryEntry +from .models import Timeline +from .service import _get_impl_key_from_model, _timeline_impl_map, extract_user_info +from .signals import on_new_history_entry, _push_to_timelines + +from unittest.mock import patch + +import gc + + +class BulkCreator(object): + def __init__(self): + self.timeline_objects = [] + self.created = None + + def create_element(self, element): + self.timeline_objects.append(element) + if len(self.timeline_objects) > 999: + self.flush() + + def flush(self): + Timeline.objects.bulk_create(self.timeline_objects, batch_size=1000) + del self.timeline_objects + self.timeline_objects = [] + gc.collect() + +bulk_creator = BulkCreator() + + +def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, + namespace:str="default", extra_data:dict={}): + assert isinstance(obj, Model), "obj must be a instance of Model" + assert isinstance(instance, Model), "instance must be a instance of Model" + event_type_key = _get_impl_key_from_model(instance.__class__, event_type) + impl = _timeline_impl_map.get(event_type_key, None) + + bulk_creator.create_element(Timeline( + content_object=obj, + namespace=namespace, + event_type=event_type_key, + project=instance.project, + data=impl(instance, extra_data=extra_data), + data_content_type=ContentType.objects.get_for_model(instance.__class__), + created=created_datetime, + )) + + +@override_settings(CELERY_ENABLED=False) +def rebuild_timeline(initial_date, final_date, project_id): + if initial_date or final_date or project_id: + timelines = Timeline.objects.all() + if initial_date: + timelines = timelines.filter(created__gte=initial_date) + if final_date: + timelines = timelines.filter(created__lt=final_date) + if project_id: + timelines = timelines.filter(project__id=project_id) + + timelines.delete() + + with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline): + # Projects api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case + projects = Project.objects.order_by("created_date") + history_entries = HistoryEntry.objects.order_by("created_at") + + if initial_date: + projects = projects.filter(created_date__gte=initial_date) + history_entries = history_entries.filter(created_at__gte=initial_date) + + if final_date: + projects = projects.filter(created_date__lt=final_date) + history_entries = history_entries.filter(created_at__lt=final_date) + + if project_id: + project = Project.objects.get(id=project_id) + epic_keys = ['epics.epic:%s'%(id) for id in project.epics.values_list("id", flat=True)] + us_keys = ['userstories.userstory:%s'%(id) for id in project.user_stories.values_list("id", + flat=True)] + tasks_keys = ['tasks.task:%s'%(id) for id in project.tasks.values_list("id", flat=True)] + issue_keys = ['issues.issue:%s'%(id) for id in project.issues.values_list("id", flat=True)] + wiki_keys = ['wiki.wikipage:%s'%(id) for id in project.wiki_pages.values_list("id", flat=True)] + keys = epic_keys + us_keys + tasks_keys + issue_keys + wiki_keys + + projects = projects.filter(id=project_id) + history_entries = history_entries.filter(key__in=keys) + + #Memberships + for membership in project.memberships.exclude(user=None).exclude(user=project.owner): + _push_to_timelines(project, membership.user, membership, "create", membership.created_at, refresh_totals=False) + + for project in projects.iterator(): + print("Project:", project) + extra_data = { + "values_diff": {}, + "user": extract_user_info(project.owner), + } + _push_to_timelines(project, project.owner, project, 'create', + project.created_date, extra_data=extra_data, + refresh_totals=False) + del extra_data + + for historyEntry in history_entries.iterator(): + print("History entry:", historyEntry.created_at) + try: + historyEntry.refresh_totals = False + on_new_history_entry(None, historyEntry, None) + except ObjectDoesNotExist as e: + print("Ignoring") + + for project in projects.iterator(): + project.refresh_totals() + + bulk_creator.flush() diff --git a/taiga/timeline/serializers.py b/taiga/timeline/serializers.py index 0b831d04..263b38ab 100644 --- a/taiga/timeline/serializers.py +++ b/taiga/timeline/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/timeline/service.py b/taiga/timeline/service.py index 03ca3e96..44985e84 100644 --- a/taiga/timeline/service.py +++ b/taiga/timeline/service.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -93,7 +93,7 @@ def _push_to_timeline(objects, instance: object, event_type: str, created_dateti @app.task def push_to_timelines(project_id, user_id, obj_app_label, obj_model_name, obj_id, event_type, - created_datetime, extra_data={}): + created_datetime, extra_data={}, refresh_totals=True): ObjModel = apps.get_model(obj_app_label, obj_model_name) try: @@ -120,7 +120,8 @@ def push_to_timelines(project_id, user_id, obj_app_label, obj_model_name, obj_id namespace=build_project_namespace(project), extra_data=extra_data) - project.refresh_totals() + if refresh_totals: + project.refresh_totals() if hasattr(obj, "get_related_people"): related_people = obj.get_related_people() diff --git a/taiga/timeline/signals.py b/taiga/timeline/signals.py index 7f754b63..ce2fee93 100644 --- a/taiga/timeline/signals.py +++ b/taiga/timeline/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -31,7 +31,7 @@ from taiga.timeline.service import (push_to_timelines, extract_user_info) -def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_data={}): +def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_data={}, refresh_totals=True): project_id = None if project is None else project.id ct = ContentType.objects.get_for_model(obj) @@ -43,7 +43,8 @@ def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_d obj.id, event_type, created_datetime, - extra_data=extra_data)) + extra_data=extra_data, + refresh_totals=refresh_totals)) else: push_to_timelines(project_id, user.id, @@ -52,7 +53,8 @@ def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_d obj.id, event_type, created_datetime, - extra_data=extra_data) + extra_data=extra_data, + refresh_totals=refresh_totals) def _clean_description_fields(values_diff): @@ -73,6 +75,8 @@ def on_new_history_entry(sender, instance, created, **kwargs): if instance.user["pk"] is None: return None + refresh_totals = getattr(instance, "refresh_totals", True) + model = history_services.get_model_from_key(instance.key) pk = history_services.get_pk_from_key(instance.key) obj = model.objects.get(pk=pk) @@ -105,7 +109,7 @@ def on_new_history_entry(sender, instance, created, **kwargs): extra_data["comment_edited"] = True created_datetime = instance.created_at - _push_to_timelines(project, user, obj, event_type, created_datetime, extra_data=extra_data) + _push_to_timelines(project, user, obj, event_type, created_datetime, extra_data=extra_data, refresh_totals=refresh_totals) def create_membership_push_to_timeline(sender, instance, created, **kwargs): diff --git a/taiga/timeline/timeline_implementations.py b/taiga/timeline/timeline_implementations.py index 1d480e82..e7ed9f9c 100644 --- a/taiga/timeline/timeline_implementations.py +++ b/taiga/timeline/timeline_implementations.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/urls.py b/taiga/urls.py index 95b27ce8..25dfc18e 100644 --- a/taiga/urls.py +++ b/taiga/urls.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,7 +17,7 @@ # along with this program. If not, see . from django.conf import settings -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin from .routers import router diff --git a/taiga/users/admin.py b/taiga/users/admin.py index 9c52a9bb..212231fe 100644 --- a/taiga/users/admin.py +++ b/taiga/users/admin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/users/api.py b/taiga/users/api.py index ddfab0e7..d812b475 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -46,9 +46,10 @@ from . import validators from . import permissions from . import filters as user_filters from . import services +from . import utils as user_utils from .signals import user_cancel_account as user_cancel_account_signal from .signals import user_change_email as user_change_email_signal - +from .throttling import UserDetailRateThrottle class UsersViewSet(ModelCrudViewSet): permission_classes = (permissions.UserPermission,) @@ -56,8 +57,9 @@ class UsersViewSet(ModelCrudViewSet): serializer_class = serializers.UserSerializer admin_validator_class = validators.UserAdminValidator validator_class = validators.UserValidator - queryset = models.User.objects.all().prefetch_related("memberships") filter_backends = (MembersFilterBackend,) + throttle_classes = (UserDetailRateThrottle,) + model = models.User def get_serializer_class(self): if self.action in ["partial_update", "update", "retrieve", "by_username"]: @@ -75,6 +77,12 @@ class UsersViewSet(ModelCrudViewSet): return self.validator_class + def get_queryset(self): + qs = super().get_queryset() + qs = qs.prefetch_related("memberships") + qs = user_utils.attach_extra_info(qs, user=self.request.user) + return qs + def create(self, *args, **kwargs): raise exc.NotSupported() @@ -92,7 +100,7 @@ class UsersViewSet(ModelCrudViewSet): return response.Ok(serializer.data) def retrieve(self, request, *args, **kwargs): - self.object = get_object_or_404(models.User, **kwargs) + self.object = get_object_or_404(self.get_queryset(), **kwargs) self.check_permissions(request, 'retrieve', self.object) serializer = self.get_serializer(self.object) return response.Ok(serializer.data) diff --git a/taiga/users/filters.py b/taiga/users/filters.py index 46dd88ac..2c358c0d 100644 --- a/taiga/users/filters.py +++ b/taiga/users/filters.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -17,13 +17,29 @@ # along with this program. If not, see . from taiga.base.filters import PermissionBasedFilterBackend +from taiga.base.utils.db import to_tsquery + from . import services class ContactsFilterBackend(PermissionBasedFilterBackend): def filter_queryset(self, user, request, queryset, view): - qs = queryset.filter(is_active=True) - project_ids = services.get_visible_project_ids(user, request.user) - qs = qs.filter(memberships__project_id__in=project_ids) - qs = qs.exclude(id=user.id) + qs = user.contacts_visible_by_user(request.user) + + exclude_project = request.QUERY_PARAMS.get('exclude_project', None) + if exclude_project: + qs = qs.exclude(projects__id=exclude_project) + + q = request.QUERY_PARAMS.get('q', None) + if q: + table = qs.model._meta.db_table + where_clause = (""" + to_tsvector('simple', + coalesce({table}.username, '') || ' ' || + coalesce({table}.full_name) || ' ' || + coalesce({table}.email, '')) @@ to_tsquery('simple', %s) + """.format(table=table)) + + qs = qs.extra(where=[where_clause], params=[to_tsquery(q)]) + return qs.distinct() diff --git a/taiga/users/forms.py b/taiga/users/forms.py index 1d466f67..f9d7f0d6 100644 --- a/taiga/users/forms.py +++ b/taiga/users/forms.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/users/gravatar.py b/taiga/users/gravatar.py index b8329d95..e5e219fa 100644 --- a/taiga/users/gravatar.py +++ b/taiga/users/gravatar.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/taiga/users/migrations/0001_initial.py b/taiga/users/migrations/0001_initial.py index ad8d2071..d81f77fa 100644 --- a/taiga/users/migrations/0001_initial.py +++ b/taiga/users/migrations/0001_initial.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields import django.utils.timezone import re import django.core.validators diff --git a/taiga/users/migrations/0002_auto_20140903_0916.py b/taiga/users/migrations/0002_auto_20140903_0916.py index 40dd3086..fd663d44 100644 --- a/taiga/users/migrations/0002_auto_20140903_0916.py +++ b/taiga/users/migrations/0002_auto_20140903_0916.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -18,7 +18,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), ('name', models.CharField(verbose_name='name', max_length=200)), ('slug', models.SlugField(verbose_name='slug', max_length=250, blank=True)), - ('permissions', djorm_pgarray.fields.TextArrayField(dbtype='text', verbose_name='permissions', choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('vote_issues', 'Vote issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], default=[])), + ('permissions', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('vote_issues', 'Vote issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='permissions')), ('order', models.IntegerField(verbose_name='order', default=10)), ('computable', models.BooleanField(default=True)), ], diff --git a/taiga/users/migrations/0007_auto_20150209_1611.py b/taiga/users/migrations/0007_auto_20150209_1611.py index 4f1bc2ca..27f1cd3d 100644 --- a/taiga/users/migrations/0007_auto_20150209_1611.py +++ b/taiga/users/migrations/0007_auto_20150209_1611.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings -import django_pgjson.fields +import taiga.base.db.models.fields def migrate_github_id(apps, schema_editor): @@ -27,7 +27,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), ('key', models.SlugField()), ('value', models.CharField(max_length=300)), - ('extra', django_pgjson.fields.JsonField()), + ('extra', taiga.base.db.models.fields.JSONField()), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ], options={ diff --git a/taiga/users/migrations/0012_auto_20150812_1142.py b/taiga/users/migrations/0012_auto_20150812_1142.py index fff8c17b..8d5354b0 100644 --- a/taiga/users/migrations/0012_auto_20150812_1142.py +++ b/taiga/users/migrations/0012_auto_20150812_1142.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields - +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +14,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='role', name='permissions', - field=djorm_pgarray.fields.TextArrayField(choices=[('view_project', 'View project'), ('star_project', 'Star project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('vote_us', 'Vote user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('vote_task', 'Vote task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('vote_issue', 'Vote issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], verbose_name='permissions', default=[], dbtype='text'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('star_project', 'Star project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('vote_us', 'Vote user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('vote_task', 'Vote task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('vote_issue', 'Vote issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='permissions'), preserve_default=True, ), ] diff --git a/taiga/users/migrations/0013_auto_20150901_1600.py b/taiga/users/migrations/0013_auto_20150901_1600.py index 8d2e4143..15492e03 100644 --- a/taiga/users/migrations/0013_auto_20150901_1600.py +++ b/taiga/users/migrations/0013_auto_20150901_1600.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import djorm_pgarray.fields - +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -15,7 +14,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='role', name='permissions', - field=djorm_pgarray.fields.TextArrayField(default=[], choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], dbtype='text', verbose_name='permissions'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='permissions'), preserve_default=True, ), ] diff --git a/taiga/users/migrations/0019_auto_20160519_1058.py b/taiga/users/migrations/0019_auto_20160519_1058.py index 69780084..a83dbcea 100644 --- a/taiga/users/migrations/0019_auto_20160519_1058.py +++ b/taiga/users/migrations/0019_auto_20160519_1058.py @@ -2,9 +2,8 @@ # Generated by Django 1.9.2 on 2016-05-19 10:58 from __future__ import unicode_literals -from django.db import migrations -import djorm_pgarray.fields - +from django.db import models, migrations +import django.contrib.postgres.fields class Migration(migrations.Migration): @@ -16,6 +15,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='role', name='permissions', - field=djorm_pgarray.fields.TextArrayField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('comment_us', 'Comment user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('comment_task', 'Comment task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('comment_issue', 'Comment issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('comment_wiki_page', 'Comment wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')], dbtype='text', default=[], verbose_name='permissions'), + field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('comment_us', 'Comment user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('comment_task', 'Comment task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('comment_issue', 'Comment issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('comment_wiki_page', 'Comment wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='permissions'), ), ] diff --git a/taiga/users/migrations/0023_json_to_jsonb.py b/taiga/users/migrations/0023_json_to_jsonb.py new file mode 100644 index 00000000..feb42127 --- /dev/null +++ b/taiga/users/migrations/0023_json_to_jsonb.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:35 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0022_auto_20160629_1443'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="users_authdata", + column_name="extra", + ), + reverse_sql=migrations.RunSQL.noop + ), + ] diff --git a/taiga/users/models.py b/taiga/users/models.py index b574d61f..9bf72000 100644 --- a/taiga/users/models.py +++ b/taiga/users/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -34,7 +34,7 @@ from django.dispatch import receiver from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from django_pglocks import advisory_lock from taiga.auth.tokens import get_token_for_user @@ -46,6 +46,8 @@ from taiga.permissions.choices import MEMBERS_PERMISSIONS from taiga.projects.choices import BLOCKED_BY_OWNER_LEAVING from taiga.projects.notifications.choices import NotifyLevel +from . import services + def get_user_model_safe(): """ @@ -259,6 +261,13 @@ class User(AbstractBaseUser, PermissionsMixin): def get_full_name(self): return self.full_name or self.username or self.email + def contacts_visible_by_user(self, user): + qs = User.objects.filter(is_active=True) + project_ids = services.get_visible_project_ids(self, user) + qs = qs.filter(memberships__project_id__in=project_ids) + qs = qs.exclude(id=self.id) + return qs + def save(self, *args, **kwargs): get_token_for_user(self, "cancel_account") super().save(*args, **kwargs) @@ -325,7 +334,7 @@ class AuthData(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="auth_data") key = models.SlugField(max_length=50) value = models.CharField(max_length=300) - extra = JsonField() + extra = JSONField() class Meta: unique_together = ["key", "value"] diff --git a/taiga/users/permissions.py b/taiga/users/permissions.py index 7edca716..ed002cc5 100644 --- a/taiga/users/permissions.py +++ b/taiga/users/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/users/serializers.py b/taiga/users/serializers.py index a720e46e..36d9a5a8 100644 --- a/taiga/users/serializers.py +++ b/taiga/users/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -54,7 +54,6 @@ class UserSerializer(serializers.LightSerializer): big_photo = MethodField() gravatar_id = MethodField() roles = MethodField() - projects_with_me = MethodField() def get_full_name_display(self, obj): return obj.get_full_name() if obj else "" @@ -69,21 +68,10 @@ class UserSerializer(serializers.LightSerializer): return get_user_gravatar_id(user) def get_roles(self, user): - return user.memberships. order_by("role__name").values_list("role__name", flat=True).distinct() + if hasattr(user, "roles_attr"): + return user.roles_attr - def get_projects_with_me(self, user): - request = self.context.get("request", None) - requesting_user = request and request.user or None - - if not requesting_user or not requesting_user.is_authenticated(): - return [] - - else: - project_ids = requesting_user.memberships.values_list("project__id", flat=True) - memberships = user.memberships.filter(project__id__in=project_ids) - project_ids = memberships.values_list("project__id", flat=True) - projects = Project.objects.filter(id__in=project_ids) - return ContactProjectDetailSerializer(projects, many=True).data + return user.memberships.order_by("role__name").values_list("role__name", flat=True).distinct() class UserAdminSerializer(UserSerializer): diff --git a/taiga/users/services.py b/taiga/users/services.py index 4b49353e..ab1afdd0 100644 --- a/taiga/users/services.py +++ b/taiga/users/services.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -312,7 +312,7 @@ def get_watched_list(for_user, from_user, type=None, q=None): if q: filters_sql += """ AND ( - to_tsvector('english_nostop', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('english_nostop', %(q)s) + to_tsvector('simple', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('simple', %(q)s) ) """ @@ -412,7 +412,7 @@ def get_liked_list(for_user, from_user, type=None, q=None): if q: filters_sql += """ AND ( - to_tsvector('english_nostop', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('english_nostop', %(q)s) + to_tsvector('simple', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('simple', %(q)s) ) """ @@ -495,7 +495,7 @@ def get_voted_list(for_user, from_user, type=None, q=None): if q: filters_sql += """ AND ( - to_tsvector('english_nostop', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('english_nostop', %(q)s) + to_tsvector('simple', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('simple', %(q)s) ) """ @@ -582,7 +582,7 @@ def get_voted_list(for_user, from_user, type=None, q=None): ] -def has_available_slot_for_import_new_project(owner, is_private, total_memberships): +def has_available_slot_for_new_project(owner, is_private, total_memberships): if is_private: current_projects = owner.owned_projects.filter(is_private=True).count() max_projects = owner.max_private_projects diff --git a/taiga/users/signals.py b/taiga/users/signals.py index d812f5fc..8d1bd03e 100644 --- a/taiga/users/signals.py +++ b/taiga/users/signals.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/users/throttling.py b/taiga/users/throttling.py new file mode 100644 index 00000000..c27aed9f --- /dev/null +++ b/taiga/users/throttling.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 taiga.base import throttling + + +class UserDetailRateThrottle(throttling.GlobalThrottlingMixin, throttling.ThrottleByActionMixin, throttling.SimpleRateThrottle): + scope = "user-detail" + throttled_actions = ["by_username", "retrieve"] diff --git a/taiga/users/utils.py b/taiga/users/utils.py new file mode 100644 index 00000000..96ca287b --- /dev/null +++ b/taiga/users/utils.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 . + + +def attach_roles(queryset, as_field="roles_attr"): + """Attach roles to each object of the queryset. + + :param queryset: A Django user stories queryset object. + :param as_field: Attach the roles as an attribute with this name. + + :return: Queryset object with the additional `as_field` field. + """ + model = queryset.model + sql = """SELECT ARRAY( + SELECT DISTINCT(users_role.name) + FROM projects_membership + INNER JOIN users_role ON users_role.id = projects_membership.role_id + WHERE projects_membership.user_id = {tbl}.id + ORDER BY users_role.name) + """ + sql = sql.format(tbl=model._meta.db_table) + queryset = queryset.extra(select={as_field: sql}) + return queryset + + +def attach_extra_info(queryset, user=None): + queryset = attach_roles(queryset) + return queryset diff --git a/taiga/users/validators.py b/taiga/users/validators.py index 279e6ce4..45c42be0 100644 --- a/taiga/users/validators.py +++ b/taiga/users/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/userstorage/api.py b/taiga/userstorage/api.py index 94c5ea00..465a8f8e 100644 --- a/taiga/userstorage/api.py +++ b/taiga/userstorage/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/userstorage/filters.py b/taiga/userstorage/filters.py index 479c0220..70493df1 100644 --- a/taiga/userstorage/filters.py +++ b/taiga/userstorage/filters.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/userstorage/migrations/0001_initial.py b/taiga/userstorage/migrations/0001_initial.py index 1857d669..1776be95 100644 --- a/taiga/userstorage/migrations/0001_initial.py +++ b/taiga/userstorage/migrations/0001_initial.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields from django.conf import settings @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('created_date', models.DateTimeField(auto_now_add=True, verbose_name='created date')), ('modified_date', models.DateTimeField(verbose_name='modified date', auto_now=True)), ('key', models.CharField(max_length=255, verbose_name='key')), - ('value', django_pgjson.fields.JsonField(verbose_name='value', blank=True, default=None, null=True)), + ('value', taiga.base.db.models.fields.JSONField(verbose_name='value', blank=True, default=None, null=True)), ('owner', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='owner', related_name='storage_entries')), ], options={ diff --git a/taiga/userstorage/migrations/0002_fix_json_field_not_null.py b/taiga/userstorage/migrations/0002_fix_json_field_not_null.py index f993919a..1aa5c568 100644 --- a/taiga/userstorage/migrations/0002_fix_json_field_not_null.py +++ b/taiga/userstorage/migrations/0002_fix_json_field_not_null.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField from django.db import models, migrations diff --git a/taiga/userstorage/migrations/0003_json_to_jsonb.py b/taiga/userstorage/migrations/0003_json_to_jsonb.py new file mode 100644 index 00000000..e828d74c --- /dev/null +++ b/taiga/userstorage/migrations/0003_json_to_jsonb.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:35 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + + +class Migration(migrations.Migration): + + dependencies = [ + ('userstorage', '0002_fix_json_field_not_null'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "{table_name}" + ALTER COLUMN "{column_name}" + TYPE jsonb + USING regexp_replace("{column_name}"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """.format( + table_name="userstorage_storageentry", + column_name="value", + ), + reverse_sql=migrations.RunSQL.noop + ), + ] diff --git a/taiga/userstorage/models.py b/taiga/userstorage/models.py index 9888ea7f..f7559747 100644 --- a/taiga/userstorage/models.py +++ b/taiga/userstorage/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,7 +19,7 @@ from django.db import models from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField class StorageEntry(models.Model): @@ -30,7 +30,7 @@ class StorageEntry(models.Model): modified_date = models.DateTimeField(auto_now=True, null=False, blank=False, verbose_name=_("modified date")) key = models.CharField(max_length=255, null=False, blank=False, verbose_name=_("key")) - value = JsonField(blank=True, default=None, null=True, verbose_name=_("value")) + value = JSONField(blank=True, default=None, null=True, verbose_name=_("value")) class Meta: verbose_name = "storage entry" diff --git a/taiga/userstorage/permissions.py b/taiga/userstorage/permissions.py index 0b7083eb..fffc9002 100644 --- a/taiga/userstorage/permissions.py +++ b/taiga/userstorage/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/userstorage/serializers.py b/taiga/userstorage/serializers.py index 38765f19..a080ccc1 100644 --- a/taiga/userstorage/serializers.py +++ b/taiga/userstorage/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/userstorage/validators.py b/taiga/userstorage/validators.py index 615b88d7..43646e88 100644 --- a/taiga/userstorage/validators.py +++ b/taiga/userstorage/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/__init__.py b/taiga/webhooks/__init__.py index 71041013..13bc7989 100644 --- a/taiga/webhooks/__init__.py +++ b/taiga/webhooks/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/api.py b/taiga/webhooks/api.py index 4648a73a..d6a9b0ef 100644 --- a/taiga/webhooks/api.py +++ b/taiga/webhooks/api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/apps.py b/taiga/webhooks/apps.py index 52e39eb0..3cee531a 100644 --- a/taiga/webhooks/apps.py +++ b/taiga/webhooks/apps.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/migrations/0001_initial.py b/taiga/webhooks/migrations/0001_initial.py index 79ec8ba9..366bf848 100644 --- a/taiga/webhooks/migrations/0001_initial.py +++ b/taiga/webhooks/migrations/0001_initial.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -30,7 +30,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('url', models.URLField(verbose_name='URL')), ('status', models.IntegerField(verbose_name='Status code')), - ('request_data', django_pgjson.fields.JsonField(verbose_name='Request data')), + ('request_data', taiga.base.db.models.fields.JSONField(verbose_name='Request data')), ('response_data', models.TextField(verbose_name='Response data')), ('webhook', models.ForeignKey(related_name='logs', to='webhooks.Webhook')), ], diff --git a/taiga/webhooks/migrations/0003_auto_20150122_1021.py b/taiga/webhooks/migrations/0003_auto_20150122_1021.py index fa565ba3..a1f7a34d 100644 --- a/taiga/webhooks/migrations/0003_auto_20150122_1021.py +++ b/taiga/webhooks/migrations/0003_auto_20150122_1021.py @@ -3,7 +3,9 @@ from __future__ import unicode_literals from django.db import models, migrations import datetime -import django_pgjson.fields +import taiga.base.db.models.fields + +from django.utils import timezone class Migration(migrations.Migration): @@ -16,7 +18,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='webhooklog', name='created', - field=models.DateTimeField(default=datetime.datetime(2015, 1, 22, 10, 21, 17, 188643), auto_now_add=True), + field=models.DateTimeField( + default=datetime.datetime(2015, 1, 22, 10, 21, 17, 188643, timezone.get_default_timezone()), + auto_now_add=True), preserve_default=False, ), migrations.AddField( @@ -28,13 +32,13 @@ class Migration(migrations.Migration): migrations.AddField( model_name='webhooklog', name='request_headers', - field=django_pgjson.fields.JsonField(default={}, verbose_name='Request headers'), + field=taiga.base.db.models.fields.JSONField(default={}, verbose_name='Request headers'), preserve_default=True, ), migrations.AddField( model_name='webhooklog', name='response_headers', - field=django_pgjson.fields.JsonField(default={}, verbose_name='Response headers'), + field=taiga.base.db.models.fields.JSONField(default={}, verbose_name='Response headers'), preserve_default=True, ), ] diff --git a/taiga/webhooks/migrations/0005_auto_20150505_1639.py b/taiga/webhooks/migrations/0005_auto_20150505_1639.py index 2e8df5e4..8f143b4a 100644 --- a/taiga/webhooks/migrations/0005_auto_20150505_1639.py +++ b/taiga/webhooks/migrations/0005_auto_20150505_1639.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models, migrations -import django_pgjson.fields +import taiga.base.db.models.fields class Migration(migrations.Migration): @@ -21,13 +21,13 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='webhooklog', name='request_data', - field=django_pgjson.fields.JsonField(verbose_name='request data'), + field=taiga.base.db.models.fields.JSONField(verbose_name='request data'), preserve_default=True, ), migrations.AlterField( model_name='webhooklog', name='request_headers', - field=django_pgjson.fields.JsonField(verbose_name='request headers', default={}), + field=taiga.base.db.models.fields.JSONField(verbose_name='request headers', default={}), preserve_default=True, ), migrations.AlterField( @@ -39,7 +39,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='webhooklog', name='response_headers', - field=django_pgjson.fields.JsonField(verbose_name='response headers', default={}), + field=taiga.base.db.models.fields.JSONField(verbose_name='response headers', default={}), preserve_default=True, ), migrations.AlterField( diff --git a/taiga/webhooks/migrations/0006_json_to_jsonb.py b/taiga/webhooks/migrations/0006_json_to_jsonb.py new file mode 100644 index 00000000..fb86ed7b --- /dev/null +++ b/taiga/webhooks/migrations/0006_json_to_jsonb.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-10-26 11:35 +from __future__ import unicode_literals + +from django.db import migrations +from django.contrib.postgres.fields import JSONField + + +class Migration(migrations.Migration): + + dependencies = [ + ('webhooks', '0005_auto_20150505_1639'), + ] + + operations = [ + migrations.RunSQL( + """ + ALTER TABLE "webhooks_webhooklog" + ALTER COLUMN "request_headers" + TYPE jsonb + USING regexp_replace("request_headers"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "request_data" + TYPE jsonb + USING regexp_replace("request_data"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb, + + ALTER COLUMN "response_headers" + TYPE jsonb + USING regexp_replace("response_headers"::text, '[\\\\]+u0000', '\\\\\\\\u0000', 'g')::jsonb; + """, + reverse_sql=migrations.RunSQL.noop + ), + ] diff --git a/taiga/webhooks/models.py b/taiga/webhooks/models.py index 53969525..f7468497 100644 --- a/taiga/webhooks/models.py +++ b/taiga/webhooks/models.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -19,7 +19,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from django_pgjson.fields import JsonField +from taiga.base.db.models.fields import JSONField class Webhook(models.Model): @@ -39,10 +39,10 @@ class WebhookLog(models.Model): related_name="logs") url = models.URLField(null=False, blank=False, verbose_name=_("URL")) status = models.IntegerField(null=False, blank=False, verbose_name=_("status code")) - request_data = JsonField(null=False, blank=False, verbose_name=_("request data")) - request_headers = JsonField(null=False, blank=False, verbose_name=_("request headers"), default={}) + request_data = JSONField(null=False, blank=False, verbose_name=_("request data")) + request_headers = JSONField(null=False, blank=False, verbose_name=_("request headers"), default={}) response_data = models.TextField(null=False, blank=False, verbose_name=_("response data")) - response_headers = JsonField(null=False, blank=False, verbose_name=_("response headers"), default={}) + response_headers = JSONField(null=False, blank=False, verbose_name=_("response headers"), default={}) duration = models.FloatField(null=False, blank=False, verbose_name=_("duration"), default=0) created = models.DateTimeField(auto_now_add=True) diff --git a/taiga/webhooks/permissions.py b/taiga/webhooks/permissions.py index f16cb76c..ef942651 100644 --- a/taiga/webhooks/permissions.py +++ b/taiga/webhooks/permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/serializers.py b/taiga/webhooks/serializers.py index a6e89589..04516c06 100644 --- a/taiga/webhooks/serializers.py +++ b/taiga/webhooks/serializers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/signal_handlers.py b/taiga/webhooks/signal_handlers.py index 5186454a..0f589286 100644 --- a/taiga/webhooks/signal_handlers.py +++ b/taiga/webhooks/signal_handlers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/tasks.py b/taiga/webhooks/tasks.py index a0b737d0..343a87ac 100644 --- a/taiga/webhooks/tasks.py +++ b/taiga/webhooks/tasks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # Copyright (C) 2013 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-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/webhooks/validators.py b/taiga/webhooks/validators.py index b95e2e64..5bb5dcf2 100644 --- a/taiga/webhooks/validators.py +++ b/taiga/webhooks/validators.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/taiga/wsgi.py b/taiga/wsgi.py index 356c63ef..3158cb6c 100644 --- a/taiga/wsgi.py +++ b/taiga/wsgi.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/conftest.py b/tests/conftest.py index 76d2540a..fd93acb1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -36,3 +36,5 @@ def pytest_runtest_setup(item): def pytest_configure(config): django.setup() + from taiga.celery import app + app.conf.task_always_eager = True diff --git a/tests/factories.py b/tests/factories.py index 1b54568d..8e98aaa9 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -66,6 +66,10 @@ class ProjectTemplateFactory(Factory): priorities = [] severities = [] roles = [] + epic_custom_attributes = [] + us_custom_attributes = [] + task_custom_attributes = [] + issue_custom_attributes = [] default_owner_role = "tester" diff --git a/tests/fixtures.py b/tests/fixtures.py index aaeabb42..886f0f48 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_application_tokens_resources.py b/tests/integration/resources_permissions/test_application_tokens_resources.py index 43c46952..e52df3b1 100644 --- a/tests/integration/resources_permissions/test_application_tokens_resources.py +++ b/tests/integration/resources_permissions/test_application_tokens_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_attachment_resources.py b/tests/integration/resources_permissions/test_attachment_resources.py index 456c96f2..3f0d34f2 100644 --- a/tests/integration/resources_permissions/test_attachment_resources.py +++ b/tests/integration/resources_permissions/test_attachment_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -1027,3 +1027,67 @@ def test_wiki_attachment_list(client, data, data_wiki): results = helper_test_http_method_and_count(client, 'get', url, None, users) assert results == [(200, 2), (200, 2), (200, 2), (200, 4), (200, 4)] + + +def test_create_attachment_by_external_user_without_comment_permission(client): + issue = f.create_issue() + user = f.UserFactory() + + assert issue.owner != user + assert issue.project.owner != user + + url = reverse("issue-attachments-list") + + data = {"description": "test", + "object_id": issue.pk, + "project": issue.project.id, + "attached_file": SimpleUploadedFile("test.txt", b"test"), + "from_comment": True} + + client.login(user) + response = client.post(url, data) + assert response.status_code == 403 + + +def test_create_attachment_by_external_user_with_comment_permission_but_without_from_comment_flag(client): + project = f.ProjectFactory(public_permissions=['comment_issue']) + issue = f.create_issue(project=project) + + user = f.UserFactory() + + assert issue.owner != user + assert issue.project.owner != user + + url = reverse("issue-attachments-list") + + data = {"description": "test", + "object_id": issue.pk, + "project": issue.project.id, + "attached_file": SimpleUploadedFile("test.txt", b"test"), + "from_comment": False} + + client.login(user) + response = client.post(url, data) + assert response.status_code == 403 + + +def test_create_attachment_by_external_user_with_comment_permission_and_with_from_comment_flag(client): + project = f.ProjectFactory(public_permissions=['comment_issue']) + issue = f.create_issue(project=project) + + user = f.UserFactory() + + assert issue.owner != user + assert issue.project.owner != user + + url = reverse("issue-attachments-list") + + data = {"description": "test", + "object_id": issue.pk, + "project": issue.project.id, + "attached_file": SimpleUploadedFile("test.txt", b"test"), + "from_comment": True} + + client.login(user) + response = client.post(url, data) + assert response.status_code == 201 diff --git a/tests/integration/resources_permissions/test_auth_resources.py b/tests/integration/resources_permissions/test_auth_resources.py index 4604936f..133bc5fa 100644 --- a/tests/integration/resources_permissions/test_auth_resources.py +++ b/tests/integration/resources_permissions/test_auth_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_contact.py b/tests/integration/resources_permissions/test_contact.py new file mode 100644 index 00000000..7b5a1bec --- /dev/null +++ b/tests/integration/resources_permissions/test_contact.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 . + +from django.core.urlresolvers import reverse + +from tests import factories as f +from tests.utils import helper_test_http_method + +from taiga.base.utils import json + +import pytest +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def data(): + m = type("Models", (object,), {}) + m.user = f.UserFactory.create() + m.project = f.ProjectFactory.create() + f.MembershipFactory(user=m.project.owner, project=m.project, is_admin=True) + + return m + + +def test_contact_create(client, data): + url = reverse("contact-list") + users = [None, data.user] + + contact_data = json.dumps({ + "project": data.project.id, + "comment": "Testing comment" + }) + results = helper_test_http_method(client, 'post', url, contact_data, users) + assert results == [401, 201] diff --git a/tests/integration/resources_permissions/test_epics_custom_attributes_resource.py b/tests/integration/resources_permissions/test_epics_custom_attributes_resource.py index c35b93bd..f1cae79d 100644 --- a/tests/integration/resources_permissions/test_epics_custom_attributes_resource.py +++ b/tests/integration/resources_permissions/test_epics_custom_attributes_resource.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_epics_related_userstories_resources.py b/tests/integration/resources_permissions/test_epics_related_userstories_resources.py index 04dd28c1..565d9425 100644 --- a/tests/integration/resources_permissions/test_epics_related_userstories_resources.py +++ b/tests/integration/resources_permissions/test_epics_related_userstories_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_epics_resources.py b/tests/integration/resources_permissions/test_epics_resources.py index 15d9d05b..bfde0530 100644 --- a/tests/integration/resources_permissions/test_epics_resources.py +++ b/tests/integration/resources_permissions/test_epics_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_feedback.py b/tests/integration/resources_permissions/test_feedback.py index 752c1e71..566e10cd 100644 --- a/tests/integration/resources_permissions/test_feedback.py +++ b/tests/integration/resources_permissions/test_feedback.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_history_resources.py b/tests/integration/resources_permissions/test_history_resources.py index 95d77928..1742c14c 100644 --- a/tests/integration/resources_permissions/test_history_resources.py +++ b/tests/integration/resources_permissions/test_history_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py b/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py index 1ec28157..e8377289 100644 --- a/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py +++ b/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_issues_resources.py b/tests/integration/resources_permissions/test_issues_resources.py index b270c126..2fb507e1 100644 --- a/tests/integration/resources_permissions/test_issues_resources.py +++ b/tests/integration/resources_permissions/test_issues_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_milestones_resources.py b/tests/integration/resources_permissions/test_milestones_resources.py index 1a3ab5cb..4e78e6e1 100644 --- a/tests/integration/resources_permissions/test_milestones_resources.py +++ b/tests/integration/resources_permissions/test_milestones_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_modules_resources.py b/tests/integration/resources_permissions/test_modules_resources.py index 68f552f4..7cdbc9bb 100644 --- a/tests/integration/resources_permissions/test_modules_resources.py +++ b/tests/integration/resources_permissions/test_modules_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_projects_choices_resources.py b/tests/integration/resources_permissions/test_projects_choices_resources.py index 504b6a6f..62a91d5b 100644 --- a/tests/integration/resources_permissions/test_projects_choices_resources.py +++ b/tests/integration/resources_permissions/test_projects_choices_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -1856,24 +1856,28 @@ def test_membership_update(client, data): membership_data = serializers.MembershipSerializer(data.public_membership).data membership_data["token"] = "test" + membership_data["username"] = data.public_membership.user.email membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'put', public_url, membership_data, users) assert results == [401, 403, 403, 403, 200] membership_data = serializers.MembershipSerializer(data.private_membership1).data membership_data["token"] = "test" + membership_data["username"] = data.private_membership1.user.email membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'put', private1_url, membership_data, users) assert results == [401, 403, 403, 403, 200] membership_data = serializers.MembershipSerializer(data.private_membership2).data membership_data["token"] = "test" + membership_data["username"] = data.private_membership2.user.email membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'put', private2_url, membership_data, users) assert results == [401, 403, 403, 403, 200] membership_data = serializers.MembershipSerializer(data.blocked_membership).data membership_data["token"] = "test" + membership_data["username"] = data.blocked_membership.user.email membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'put', blocked_url, membership_data, users) assert results == [401, 403, 403, 403, 451] @@ -1972,29 +1976,33 @@ def test_membership_create(client, data): ] membership_data = serializers.MembershipSerializer(data.public_membership).data - membership_data["id"] = None - membership_data["email"] = "test1@test.com" + del(membership_data["id"]) + del(membership_data["user"]) + membership_data["username"] = "test1@test.com" membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'post', url, membership_data, users) assert results == [401, 403, 403, 403, 201] membership_data = serializers.MembershipSerializer(data.private_membership1).data - membership_data["id"] = None - membership_data["email"] = "test2@test.com" + del(membership_data["id"]) + del(membership_data["user"]) + membership_data["username"] = "test2@test.com" membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'post', url, membership_data, users) assert results == [401, 403, 403, 403, 201] membership_data = serializers.MembershipSerializer(data.private_membership2).data - membership_data["id"] = None - membership_data["email"] = "test3@test.com" + del(membership_data["id"]) + del(membership_data["user"]) + membership_data["username"] = "test3@test.com" membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'post', url, membership_data, users) assert results == [401, 403, 403, 403, 201] membership_data = serializers.MembershipSerializer(data.blocked_membership).data - membership_data["id"] = None - membership_data["email"] = "test4@test.com" + del(membership_data["id"]) + del(membership_data["user"]) + membership_data["username"] = "test4@test.com" membership_data = json.dumps(membership_data) results = helper_test_http_method(client, 'post', url, membership_data, users) assert results == [401, 403, 403, 403, 451] @@ -2014,8 +2022,8 @@ def test_membership_action_bulk_create(client, data): bulk_data = { "project_id": data.public_project.id, "bulk_memberships": [ - {"role_id": data.public_membership.role.pk, "email": "test1@test.com"}, - {"role_id": data.public_membership.role.pk, "email": "test2@test.com"}, + {"role_id": data.public_membership.role.pk, "username": "test1@test.com"}, + {"role_id": data.public_membership.role.pk, "username": "test2@test.com"}, ] } bulk_data = json.dumps(bulk_data) @@ -2025,8 +2033,8 @@ def test_membership_action_bulk_create(client, data): bulk_data = { "project_id": data.private_project1.id, "bulk_memberships": [ - {"role_id": data.private_membership1.role.pk, "email": "test1@test.com"}, - {"role_id": data.private_membership1.role.pk, "email": "test2@test.com"}, + {"role_id": data.private_membership1.role.pk, "username": "test1@test.com"}, + {"role_id": data.private_membership1.role.pk, "username": "test2@test.com"}, ] } bulk_data = json.dumps(bulk_data) @@ -2036,8 +2044,8 @@ def test_membership_action_bulk_create(client, data): bulk_data = { "project_id": data.private_project2.id, "bulk_memberships": [ - {"role_id": data.private_membership2.role.pk, "email": "test1@test.com"}, - {"role_id": data.private_membership2.role.pk, "email": "test2@test.com"}, + {"role_id": data.private_membership2.role.pk, "username": "test1@test.com"}, + {"role_id": data.private_membership2.role.pk, "username": "test2@test.com"}, ] } bulk_data = json.dumps(bulk_data) @@ -2047,8 +2055,8 @@ def test_membership_action_bulk_create(client, data): bulk_data = { "project_id": data.blocked_project.id, "bulk_memberships": [ - {"role_id": data.blocked_membership.role.pk, "email": "test1@test.com"}, - {"role_id": data.blocked_membership.role.pk, "email": "test2@test.com"}, + {"role_id": data.blocked_membership.role.pk, "username": "test1@test.com"}, + {"role_id": data.blocked_membership.role.pk, "username": "test2@test.com"}, ] } bulk_data = json.dumps(bulk_data) diff --git a/tests/integration/resources_permissions/test_projects_resource.py b/tests/integration/resources_permissions/test_projects_resource.py index 3d62dc6e..9f2fbed2 100644 --- a/tests/integration/resources_permissions/test_projects_resource.py +++ b/tests/integration/resources_permissions/test_projects_resource.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -97,18 +97,22 @@ def data(): f.MembershipFactory(project=m.public_project, user=m.project_owner, + role__project=m.public_project, is_admin=True) f.MembershipFactory(project=m.private_project1, user=m.project_owner, + role__project=m.private_project1, is_admin=True) f.MembershipFactory(project=m.private_project2, user=m.project_owner, + role__project=m.private_project2, is_admin=True) f.MembershipFactory(project=m.blocked_project, user=m.project_owner, + role__project=m.blocked_project, is_admin=True) ContentType = apps.get_model("contenttypes", "ContentType") @@ -163,12 +167,18 @@ def test_project_update(client, data): ] project_data = ProjectSerializer(data.private_project2).data + # Because in serializer is a dict anin model is a list of lists + project_data["tags_colors"] = list(map(list, project_data["tags_colors"].items())) project_data["is_private"] = False + results = helper_test_http_method(client, 'put', url, json.dumps(project_data), users) assert results == [401, 403, 403, 200] project_data = ProjectSerializer(data.blocked_project).data + # Because in serializer is a dict anin model is a list of lists + project_data["tags_colors"] = list(map(list, project_data["tags_colors"].items())) project_data["is_private"] = False + results = helper_test_http_method(client, 'put', blocked_url, json.dumps(project_data), users) assert results == [401, 403, 403, 451] @@ -658,3 +668,33 @@ def test_project_list_with_discover_mode_enabled(client, data): projects_data = json.loads(response.content.decode('utf-8')) assert len(projects_data) == 2 assert response.status_code == 200 + + +def test_project_duplicate(client, data): + public_url = reverse('projects-duplicate', kwargs={"pk": data.public_project.pk}) + private1_url = reverse('projects-duplicate', kwargs={"pk": data.private_project1.pk}) + private2_url = reverse('projects-duplicate', kwargs={"pk": data.private_project2.pk}) + blocked_url = reverse('projects-duplicate', kwargs={"pk": data.blocked_project.pk}) + + users = [ + None, + data.registered_user, + data.project_member_with_perms, + data.project_owner + ] + + data = json.dumps({ + "name": "test", + "description": "description", + "is_private": True, + "users": [] + }) + + results = helper_test_http_method(client, 'post', public_url, data, users) + assert results == [401, 201, 201, 201] + results = helper_test_http_method(client, 'post', private1_url, data, users) + assert results == [401, 201, 201, 201] + results = helper_test_http_method(client, 'post', private2_url, data, users) + assert results == [404, 404, 201, 201] + results = helper_test_http_method(client, 'post', blocked_url, data, users) + assert results == [404, 404, 451, 451] diff --git a/tests/integration/resources_permissions/test_resolver_resources.py b/tests/integration/resources_permissions/test_resolver_resources.py index 7e98b692..ec1b4251 100644 --- a/tests/integration/resources_permissions/test_resolver_resources.py +++ b/tests/integration/resources_permissions/test_resolver_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_search_resources.py b/tests/integration/resources_permissions/test_search_resources.py index 633b3131..dcd76cbf 100644 --- a/tests/integration/resources_permissions/test_search_resources.py +++ b/tests/integration/resources_permissions/test_search_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_storage_resources.py b/tests/integration/resources_permissions/test_storage_resources.py index 8d2ddf3b..10d6abdd 100644 --- a/tests/integration/resources_permissions/test_storage_resources.py +++ b/tests/integration/resources_permissions/test_storage_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py b/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py index 1efe9a8d..d42a59d6 100644 --- a/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py +++ b/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_tasks_resources.py b/tests/integration/resources_permissions/test_tasks_resources.py index 31e5f34c..18c3593c 100644 --- a/tests/integration/resources_permissions/test_tasks_resources.py +++ b/tests/integration/resources_permissions/test_tasks_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_timelines_resources.py b/tests/integration/resources_permissions/test_timelines_resources.py index a6930a29..f32e7092 100644 --- a/tests/integration/resources_permissions/test_timelines_resources.py +++ b/tests/integration/resources_permissions/test_timelines_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_users_resources.py b/tests/integration/resources_permissions/test_users_resources.py index b15d9cde..b02c163c 100644 --- a/tests/integration/resources_permissions/test_users_resources.py +++ b/tests/integration/resources_permissions/test_users_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py b/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py index 077219ce..925f2f2e 100644 --- a/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py +++ b/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_userstories_resources.py b/tests/integration/resources_permissions/test_userstories_resources.py index aba70f81..578beff9 100644 --- a/tests/integration/resources_permissions/test_userstories_resources.py +++ b/tests/integration/resources_permissions/test_userstories_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_webhooks_resources.py b/tests/integration/resources_permissions/test_webhooks_resources.py index 34d4cf00..e951cdf0 100644 --- a/tests/integration/resources_permissions/test_webhooks_resources.py +++ b/tests/integration/resources_permissions/test_webhooks_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/resources_permissions/test_wiki_resources.py b/tests/integration/resources_permissions/test_wiki_resources.py index f4cdbc12..1bda977a 100644 --- a/tests/integration/resources_permissions/test_wiki_resources.py +++ b/tests/integration/resources_permissions/test_wiki_resources.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_application_tokens.py b/tests/integration/test_application_tokens.py index cae52b6c..ed969eaf 100644 --- a/tests/integration/test_application_tokens.py +++ b/tests/integration/test_application_tokens.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_attachments.py b/tests/integration/test_attachments.py index 06a49441..4c734edc 100644 --- a/tests/integration/test_attachments.py +++ b/tests/integration/test_attachments.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_auth_api.py b/tests/integration/test_auth_api.py index 0504881b..49b8c7f9 100644 --- a/tests/integration/test_auth_api.py +++ b/tests/integration/test_auth_api.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -62,23 +62,21 @@ def test_respond_201_when_the_email_domain_is_in_allowed_domains(client, setting assert response.status_code == 201 -def test_respond_201_with_invitation_without_public_registration(client, register_form, settings): +def test_respond_201_with_invitation_login(client, settings): settings.PUBLIC_REGISTER_ENABLED = False user = factories.UserFactory() membership = factories.MembershipFactory(user=user) - register_form.update({ - "type": "private", - "existing": "1", - "token": membership.token, + auth_data = { + "type": "normal", + "invitation_token": membership.token, "username": user.username, - "email": user.email, "password": user.username, - }) + } - response = client.post(reverse("auth-register"), register_form) + response = client.post(reverse("auth-list"), auth_data) - assert response.status_code == 201, response.data + assert response.status_code == 200, response.data def test_response_200_in_public_registration(client, settings): @@ -186,3 +184,83 @@ def test_auth_uppercase_ignore(client, settings): response = client.post(reverse("auth-list"), login_form) assert response.status_code == 400 + + +def test_login_fail_throttling(client, settings): + settings.PUBLIC_REGISTER_ENABLED = True + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["login-fail"] = "1/minute" + + register_form = {"username": "valid_username_login_fail", + "password": "valid_password", + "full_name": "fullname", + "email": "valid_username_login_fail@email.com", + "type": "public"} + response = client.post(reverse("auth-register"), register_form) + + login_form = {"type": "normal", + "username": "valid_username_login_fail", + "password": "valid_password"} + + response = client.post(reverse("auth-list"), login_form) + assert response.status_code == 200 + + login_form = {"type": "normal", + "username": "invalid_username_login_fail", + "password": "invalid_password"} + + response = client.post(reverse("auth-list"), login_form) + assert response.status_code == 400 + + login_form = {"type": "normal", + "username": "invalid_username_login_fail", + "password": "invalid_password"} + + response = client.post(reverse("auth-list"), login_form) + assert response.status_code == 429 + + login_form = {"type": "normal", + "username": "valid_username_login_fail", + "password": "valid_password"} + + response = client.post(reverse("auth-list"), login_form) + assert response.status_code == 429 + + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["login-fail"] = None + +def test_register_success_throttling(client, settings): + settings.PUBLIC_REGISTER_ENABLED = True + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["register-success"] = "1/minute" + + register_form = {"username": "valid_username_register_success", + "password": "valid_password", + "full_name": "fullname", + "email": "", + "type": "public"} + response = client.post(reverse("auth-register"), register_form) + assert response.status_code == 400 + + register_form = {"username": "valid_username_register_success", + "password": "valid_password", + "full_name": "fullname", + "email": "valid_username_register_success@email.com", + "type": "public"} + response = client.post(reverse("auth-register"), register_form) + assert response.status_code == 201 + + register_form = {"username": "valid_username_register_success2", + "password": "valid_password2", + "full_name": "fullname", + "email": "valid_username_register_success2@email.com", + "type": "public"} + response = client.post(reverse("auth-register"), register_form) + assert response.status_code == 429 + + register_form = {"username": "valid_username_register_success2", + "password": "valid_password2", + "full_name": "fullname", + "email": "", + "type": "public"} + response = client.post(reverse("auth-register"), register_form) + assert response.status_code == 429 + + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["register-success"] = None diff --git a/tests/integration/test_contact.py b/tests/integration/test_contact.py new file mode 100644 index 00000000..17676f55 --- /dev/null +++ b/tests/integration/test_contact.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 . + +from django.core import mail +from django.core.urlresolvers import reverse + +from tests import factories as f + +from taiga.base.utils import json + +import pytest +pytestmark = pytest.mark.django_db + + +def test_create_comment(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create() + f.MembershipFactory(user=project.owner, project=project, is_admin=True) + + url = reverse("contact-list") + + contact_data = json.dumps({ + "project": project.id, + "comment": "Testing comment" + }) + + client.login(user) + + assert len(mail.outbox) == 0 + response = client.post(url, contact_data, content_type="application/json") + assert response.status_code == 201 + assert len(mail.outbox) == 1 + assert mail.outbox[0].to == [project.owner.email] + + + +def test_create_comment_disabled(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create() + project.is_contact_activated = False + project.save() + f.MembershipFactory(user=project.owner, project=project, is_admin=True) + + url = reverse("contact-list") + + contact_data = json.dumps({ + "project": project.id, + "comment": "Testing comment" + }) + + client.login(user) + + response = client.post(url, contact_data, content_type="application/json") + assert response.status_code == 403 diff --git a/tests/integration/test_custom_attributes_epics.py b/tests/integration/test_custom_attributes_epics.py index e24f1d8f..c70ea88e 100644 --- a/tests/integration/test_custom_attributes_epics.py +++ b/tests/integration/test_custom_attributes_epics.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_custom_attributes_issues.py b/tests/integration/test_custom_attributes_issues.py index 50b1a696..1db1632c 100644 --- a/tests/integration/test_custom_attributes_issues.py +++ b/tests/integration/test_custom_attributes_issues.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_custom_attributes_tasks.py b/tests/integration/test_custom_attributes_tasks.py index 0f6fa3a4..efabb8c4 100644 --- a/tests/integration/test_custom_attributes_tasks.py +++ b/tests/integration/test_custom_attributes_tasks.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_custom_attributes_user_stories.py b/tests/integration/test_custom_attributes_user_stories.py index 9358c40f..7b2fc7a1 100644 --- a/tests/integration/test_custom_attributes_user_stories.py +++ b/tests/integration/test_custom_attributes_user_stories.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_epics.py b/tests/integration/test_epics.py index 64d3edb7..d6f6930a 100644 --- a/tests/integration/test_epics.py +++ b/tests/integration/test_epics.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_epics_tags.py b/tests/integration/test_epics_tags.py index 3e2c66c8..2f367ab6 100644 --- a/tests/integration/test_epics_tags.py +++ b/tests/integration/test_epics_tags.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_exporter_api.py b/tests/integration/test_exporter_api.py index c71fbbb5..055bafe3 100644 --- a/tests/integration/test_exporter_api.py +++ b/tests/integration/test_exporter_api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -40,8 +40,6 @@ def test_invalid_project_export(client): def test_valid_project_export_with_celery_disabled(client, settings): - settings.CELERY_ENABLED = False - user = f.UserFactory.create() project = f.ProjectFactory.create(owner=user) f.MembershipFactory(project=project, user=user, is_admin=True) @@ -57,8 +55,6 @@ def test_valid_project_export_with_celery_disabled(client, settings): def test_valid_project_export_with_celery_disabled_and_gzip(client, settings): - settings.CELERY_ENABLED = False - user = f.UserFactory.create() project = f.ProjectFactory.create(owner=user) f.MembershipFactory(project=project, user=user, is_admin=True) @@ -93,6 +89,7 @@ def test_valid_project_export_with_celery_enabled(client, settings): args = (project.id, project.slug, response_data["export_id"], "plain") kwargs = {"countdown": settings.EXPORTS_TTL} delete_project_dump_mock.apply_async.assert_called_once_with(args, **kwargs) + settings.CELERY_ENABLED = False def test_valid_project_export_with_celery_enabled_and_gzip(client, settings): @@ -115,10 +112,10 @@ def test_valid_project_export_with_celery_enabled_and_gzip(client, settings): args = (project.id, project.slug, response_data["export_id"], "gzip") kwargs = {"countdown": settings.EXPORTS_TTL} delete_project_dump_mock.apply_async.assert_called_once_with(args, **kwargs) + settings.CELERY_ENABLED = False def test_valid_project_with_throttling(client, settings): - settings.CELERY_ENABLED = False settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["import-dump-mode"] = "1/minute" user = f.UserFactory.create() diff --git a/tests/integration/test_fan_projects.py b/tests/integration/test_fan_projects.py index 680872d2..fd0d8ab5 100644 --- a/tests/integration/test_fan_projects.py +++ b/tests/integration/test_fan_projects.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_feedback.py b/tests/integration/test_feedback.py index 82de1139..7f4bc108 100644 --- a/tests/integration/test_feedback.py +++ b/tests/integration/test_feedback.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_history.py b/tests/integration/test_history.py index cc6d8d43..0d2a7341 100644 --- a/tests/integration/test_history.py +++ b/tests/integration/test_history.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -18,11 +18,12 @@ # along with this program. If not, see . import pytest -import datetime from unittest.mock import patch from django.core.urlresolvers import reverse +from django.utils import timezone + from .. import factories as f from taiga.base.utils import json @@ -286,7 +287,7 @@ def test_get_comment_versions(client): key=key, diff={}, user={"pk": project.owner.id}, - edit_comment_date=datetime.datetime.now(), + edit_comment_date=timezone.now(), comment_versions = [{ "comment_html": "

test

", "date": "2016-05-09T09:34:27.221Z", diff --git a/tests/integration/test_hooks_bitbucket.py b/tests/integration/test_hooks_bitbucket.py index 6dbe06c3..0b685411 100644 --- a/tests/integration/test_hooks_bitbucket.py +++ b/tests/integration/test_hooks_bitbucket.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_hooks_github.py b/tests/integration/test_hooks_github.py index cf3b8bad..180b8061 100644 --- a/tests/integration/test_hooks_github.py +++ b/tests/integration/test_hooks_github.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_hooks_gitlab.py b/tests/integration/test_hooks_gitlab.py index b40c94e6..8f54811d 100644 --- a/tests/integration/test_hooks_gitlab.py +++ b/tests/integration/test_hooks_gitlab.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_hooks_gogs.py b/tests/integration/test_hooks_gogs.py index 11685d1a..16a783ab 100644 --- a/tests/integration/test_hooks_gogs.py +++ b/tests/integration/test_hooks_gogs.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_importer_api.py b/tests/integration/test_importer_api.py index 6a2e7883..9764e9a8 100644 --- a/tests/integration/test_importer_api.py +++ b/tests/integration/test_importer_api.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -1147,7 +1147,6 @@ def test_invalid_dump_import(client): def test_valid_dump_import_without_enough_public_projects_slots(client, settings): - settings.CELERY_ENABLED = False user = f.UserFactory.create(max_public_projects=0) client.login(user) @@ -1170,7 +1169,6 @@ def test_valid_dump_import_without_enough_public_projects_slots(client, settings 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) @@ -1193,7 +1191,6 @@ def test_valid_dump_import_without_enough_private_projects_slots(client, setting 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) @@ -1241,7 +1238,6 @@ def test_valid_dump_import_without_enough_membership_private_project_slots_one_p 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) @@ -1289,7 +1285,6 @@ def test_valid_dump_import_without_enough_membership_public_project_slots_one_pr def test_valid_dump_import_with_enough_membership_private_project_slots_multiple_projects(client, settings): - settings.CELERY_ENABLED = False user = f.UserFactory.create(max_memberships_private_projects=10) project = f.ProjectFactory.create(owner=user) @@ -1344,8 +1339,6 @@ def test_valid_dump_import_with_enough_membership_private_project_slots_multiple def test_valid_dump_import_with_enough_membership_public_project_slots_multiple_projects(client, settings): - settings.CELERY_ENABLED = False - user = f.UserFactory.create(max_memberships_public_projects=10) project = f.ProjectFactory.create(owner=user) f.MembershipFactory.create(project=project) @@ -1400,7 +1393,6 @@ def test_valid_dump_import_with_enough_membership_public_project_slots_multiple_ 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) @@ -1443,7 +1435,6 @@ def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_private_pro 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) @@ -1486,8 +1477,6 @@ def test_valid_dump_import_with_the_limit_of_membership_whit_you_for_public_proj def test_valid_dump_import_with_celery_disabled(client, settings): - settings.CELERY_ENABLED = False - user = f.UserFactory.create() client.login(user) @@ -1508,7 +1497,6 @@ def test_valid_dump_import_with_celery_disabled(client, settings): 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) @@ -1568,6 +1556,7 @@ def test_valid_dump_import_with_celery_enabled(client, settings): assert response.status_code == 202 assert "import_id" in response.data assert Project.objects.filter(slug="valid-project").count() == 1 + settings.CELERY_ENABLED = False def test_invalid_dump_import_with_celery_enabled(client, settings): @@ -1611,6 +1600,7 @@ def test_invalid_dump_import_with_celery_enabled(client, settings): assert response.status_code == 202 assert "import_id" in response.data assert Project.objects.filter(slug="invalid-project").count() == 0 + settings.CELERY_ENABLED = False def test_dump_import_throttling(client, settings): diff --git a/tests/integration/test_importers_asana_api.py b/tests/integration/test_importers_asana_api.py new file mode 100644 index 00000000..66ddfd4c --- /dev/null +++ b/tests/integration/test_importers_asana_api.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import pytest +import json + +from unittest import mock + +from django.core.urlresolvers import reverse + +from .. import factories as f +from taiga.importers import exceptions +from taiga.base.utils import json +from taiga.base import exceptions as exc + + +pytestmark = pytest.mark.django_db + + +def test_auth_url(client, settings): + user = f.UserFactory.create() + client.login(user) + settings.IMPORTERS['asana']['callback_url'] = "http://testserver/url" + settings.IMPORTERS['asana']['app_id'] = "test-id" + settings.IMPORTERS['asana']['app_secret'] = "test-secret" + + url = reverse("importers-asana-auth-url") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + AsanaImporterMock.get_auth_url.return_value = "https://auth_url" + response = client.get(url, content_type="application/json") + assert AsanaImporterMock.get_auth_url.calledWith( + settings.IMPORTERS['asana']['app_id'], + settings.IMPORTERS['asana']['app_secret'], + settings.IMPORTERS['asana']['callback_url'] + ) + + assert response.status_code == 200 + assert 'url' in response.data + assert response.data['url'] == "https://auth_url" + + +def test_authorize(client, settings): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-asana-authorize") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + AsanaImporterMock.get_access_token.return_value = "token" + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"code": "code"})) + assert AsanaImporterMock.get_access_token.calledWith( + settings.IMPORTERS['asana']['app_id'], + settings.IMPORTERS['asana']['app_secret'], + "code" + ) + + assert response.status_code == 200 + assert 'token' in response.data + assert response.data['token'] == "token" + + +def test_authorize_without_code(client): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-asana-authorize") + + response = client.post(authorize_url, content_type="application/json", data=json.dumps({})) + + assert response.status_code == 400 + assert 'token' not in response.data + assert '_error_message' in response.data + assert response.data['_error_message'] == "Code param needed" + + +def test_authorize_with_bad_verify(client, settings): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-asana-authorize") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + AsanaImporterMock.get_access_token.side_effect = exceptions.InvalidRequest() + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"code": "bad"})) + assert AsanaImporterMock.get_access_token.calledWith( + settings.IMPORTERS['asana']['app_id'], + settings.IMPORTERS['asana']['app_secret'], + "bad" + ) + + assert response.status_code == 400 + assert 'token' not in response.data + assert '_error_message' in response.data + assert response.data['_error_message'] == "Invalid Asana API request" + + +def test_import_asana_list_users(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-asana-list-users") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "username": "user1", "full_name": "user1", "detected_user": None}, + {"id": 2, "username": "user2", "full_name": "user2", "detected_user": None} + ] + AsanaImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 200 + assert response.data[0]["id"] == 1 + assert response.data[1]["id"] == 2 + + +def test_import_asana_list_users_without_project(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-asana-list-users") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "username": "user1", "full_name": "user1", "detected_user": None}, + {"id": 2, "username": "user2", "full_name": "user2", "detected_user": None} + ] + AsanaImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + + +def test_import_asana_list_users_with_problem_on_request(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-asana-list-users") + + with mock.patch('taiga.importers.asana.importer.AsanaClient') as AsanaClientMock: + instance = mock.Mock() + instance.workspaces.find_all.side_effect = exceptions.InvalidRequest() + AsanaClientMock.oauth.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 400 + + +def test_import_asana_list_projects(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-asana-list-projects") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + instance = mock.Mock() + instance.list_projects.return_value = ["project1", "project2"] + AsanaImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 200 + assert response.data[0] == "project1" + assert response.data[1] == "project2" + + +def test_import_asana_list_projects_with_problem_on_request(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-asana-list-projects") + + with mock.patch('taiga.importers.asana.importer.AsanaClient') as AsanaClientMock: + instance = mock.Mock() + instance.workspaces.find_all.side_effect = exc.WrongArguments("Invalid Request") + AsanaClientMock.oauth.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + + +def test_import_asana_project_without_project_id(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-asana-import-project") + + with mock.patch('taiga.importers.asana.tasks.AsanaImporter') as AsanaImporterMock: + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + settings.CELERY_ENABLED = False + + +def test_import_asana_project_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="async-imported-project") + client.login(user) + + url = reverse("importers-asana-import-project") + + with mock.patch('taiga.importers.asana.tasks.AsanaImporter') as AsanaImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + AsanaImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 202 + assert "task_id" in response.data + settings.CELERY_ENABLED = False + + +def test_import_asana_project_with_celery_disabled(client, settings): + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="imported-project") + client.login(user) + + url = reverse("importers-asana-import-project") + + with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + AsanaImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 200 + assert "slug" in response.data + assert response.data['slug'] == "imported-project" diff --git a/tests/integration/test_importers_github_api.py b/tests/integration/test_importers_github_api.py new file mode 100644 index 00000000..94fac6b1 --- /dev/null +++ b/tests/integration/test_importers_github_api.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import pytest +import json + +from unittest import mock + +from django.core.urlresolvers import reverse + +from .. import factories as f +from taiga.importers import exceptions +from taiga.base.utils import json +from taiga.base import exceptions as exc + + +pytestmark = pytest.mark.django_db + + +def test_auth_url(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-auth-url")+"?uri=http://localhost:9001/project/new?from=github" + + response = client.get(url, content_type="application/json") + + assert response.status_code == 200 + assert 'url' in response.data + assert response.data['url'] == "https://github.com/login/oauth/authorize?client_id=&scope=user,repo&redirect_uri=http://localhost:9001/project/new?from=github" + +def test_authorize(client, settings): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-github-authorize") + + with mock.patch('taiga.importers.github.api.GithubImporter') as GithubImporterMock: + GithubImporterMock.get_access_token.return_value = "token" + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"code": "code"})) + assert GithubImporterMock.get_access_token.calledWith( + settings.IMPORTERS['github']['client_id'], + settings.IMPORTERS['github']['client_secret'], + "code" + ) + + assert response.status_code == 200 + assert 'token' in response.data + assert response.data['token'] == "token" + +def test_authorize_without_code(client): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-github-authorize") + + response = client.post(authorize_url, content_type="application/json", data=json.dumps({})) + + assert response.status_code == 400 + assert 'token' not in response.data + assert '_error_message' in response.data + assert response.data['_error_message'] == "Code param needed" + + +def test_authorize_with_bad_verify(client, settings): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-github-authorize") + + with mock.patch('taiga.importers.github.api.GithubImporter') as GithubImporterMock: + GithubImporterMock.get_access_token.side_effect = exceptions.InvalidAuthResult() + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"code": "bad"})) + assert GithubImporterMock.get_access_token.calledWith( + settings.IMPORTERS['github']['client_id'], + settings.IMPORTERS['github']['client_secret'], + "bad" + ) + + assert response.status_code == 400 + assert 'token' not in response.data + assert '_error_message' in response.data + assert response.data['_error_message'] == "Invalid auth data" + + +def test_import_github_list_users(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-list-users") + + with mock.patch('taiga.importers.github.api.GithubImporter') as GithubImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "username": "user1", "full_name": "user1", "detected_user": None}, + {"id": 2, "username": "user2", "full_name": "user2", "detected_user": None} + ] + GithubImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 200 + assert response.data[0]["id"] == 1 + assert response.data[1]["id"] == 2 + + +def test_import_github_list_users_without_project(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-list-users") + + with mock.patch('taiga.importers.github.api.GithubImporter') as GithubImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "username": "user1", "full_name": "user1", "detected_user": None}, + {"id": 2, "username": "user2", "full_name": "user2", "detected_user": None} + ] + GithubImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + + +def test_import_github_list_users_with_problem_on_request(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-list-users") + + with mock.patch('taiga.importers.github.importer.GithubClient') as GithubClientMock: + instance = mock.Mock() + instance.get.side_effect = exc.WrongArguments("Invalid Request") + GithubClientMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 400 + + +def test_import_github_list_projects(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-list-projects") + + with mock.patch('taiga.importers.github.api.GithubImporter') as GithubImporterMock: + instance = mock.Mock() + instance.list_projects.return_value = ["project1", "project2"] + GithubImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 200 + assert response.data[0] == "project1" + assert response.data[1] == "project2" + + +def test_import_github_list_projects_with_problem_on_request(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-list-projects") + + with mock.patch('taiga.importers.github.importer.GithubClient') as GithubClientMock: + instance = mock.Mock() + instance.get.side_effect = exc.WrongArguments("Invalid Request") + GithubClientMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + + +def test_import_github_project_without_project_id(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-github-import-project") + + with mock.patch('taiga.importers.github.tasks.GithubImporter') as GithubImporterMock: + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + settings.CELERY_ENABLED = False + + +def test_import_github_project_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="async-imported-project") + client.login(user) + + url = reverse("importers-github-import-project") + + with mock.patch('taiga.importers.github.tasks.GithubImporter') as GithubImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + GithubImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 202 + assert "task_id" in response.data + settings.CELERY_ENABLED = False + + +def test_import_github_project_with_celery_disabled(client, settings): + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="imported-project") + client.login(user) + + url = reverse("importers-github-import-project") + + with mock.patch('taiga.importers.github.api.GithubImporter') as GithubImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + GithubImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 200 + assert "slug" in response.data + assert response.data['slug'] == "imported-project" diff --git a/tests/integration/test_importers_jira_api.py b/tests/integration/test_importers_jira_api.py new file mode 100644 index 00000000..d2c014fb --- /dev/null +++ b/tests/integration/test_importers_jira_api.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import pytest +import json + +from unittest import mock + +from django.core.urlresolvers import reverse + +from .. import factories as f +from taiga.base.utils import json +from taiga.base import exceptions as exc +from taiga.users.models import AuthData + + +pytestmark = pytest.mark.django_db + + +fake_token = "access.secret" + + +def test_auth_url(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-auth-url")+"?url=http://jiraserver" + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as JiraNormalImporter: + JiraNormalImporter.get_auth_url.return_value = ("test_oauth_token", "test_oauth_secret", "http://jira-server-url") + response = client.get(url, content_type="application/json") + + auth_data = user.auth_data.get(key="jira-oauth") + assert auth_data.extra['oauth_token'] == "test_oauth_token" + assert auth_data.extra['oauth_secret'] == "test_oauth_secret" + assert auth_data.extra['url'] == "http://jiraserver" + + assert response.status_code == 200 + assert 'url' in response.data + assert response.data['url'] == "http://jira-server-url" + + +def test_authorize(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-auth-url") + authorize_url = reverse("importers-jira-authorize") + + AuthData.objects.get_or_create( + user=user, + key="jira-oauth", + value="", + extra={ + "oauth_token": "test-oauth-token", + "oauth_secret": "test-oauth-secret", + "url": "http://jiraserver", + } + ) + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as JiraNormalImporter: + JiraNormalImporter.get_access_token.return_value = { + "access_token": "test-access-token", + "access_token_secret": "test-access-token-secret" + } + response = client.post(authorize_url, content_type="application/json", data={}) + + assert response.status_code == 200 + assert 'token' in response.data + assert response.data['token'] == "test-access-token.test-access-token-secret" + assert 'url' in response.data + assert response.data['url'] == "http://jiraserver" + + +def test_authorize_without_token_and_secret(client): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-jira-authorize") + AuthData.objects.filter(user=user, key="jira-oauth").delete() + + response = client.post(authorize_url, content_type="application/json", data={}) + + assert response.status_code == 400 + assert 'token' not in response.data + + +def test_import_jira_list_users(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-list-users") + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as JiraNormalImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "fullName": "user1", "email": None}, + {"id": 2, "fullName": "user2", "email": None} + ] + JiraNormalImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver", "project": 1})) + + assert response.status_code == 200 + assert response.data[0]["id"] == 1 + assert response.data[1]["id"] == 2 + + +def test_import_jira_list_users_without_project(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-list-users") + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as JiraNormalImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "fullName": "user1", "email": None}, + {"id": 2, "fullName": "user2", "email": None} + ] + JiraNormalImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver"})) + + assert response.status_code == 400 + + +def test_import_jira_list_users_with_problem_on_request(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-list-users") + + with mock.patch('taiga.importers.jira.common.JiraClient') as JiraClientMock: + instance = mock.Mock() + instance.get.side_effect = exc.WrongArguments("Invalid Request") + JiraClientMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver", "project": 1})) + + assert response.status_code == 400 + + +def test_import_jira_list_projects(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-list-projects") + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as JiraNormalImporterMock: + with mock.patch('taiga.importers.jira.api.JiraAgileImporter') as JiraAgileImporterMock: + instance = mock.Mock() + instance.list_projects.return_value = [{"name": "project1"}, {"name": "project2"}] + JiraNormalImporterMock.return_value = instance + instance_agile = mock.Mock() + instance_agile.list_projects.return_value = [{"name": "agile1"}, {"name": "agile2"}] + JiraAgileImporterMock.return_value = instance_agile + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver"})) + + assert response.status_code == 200 + assert response.data[0] == {"name": "agile1"} + assert response.data[1] == {"name": "agile2"} + assert response.data[2] == {"name": "project1"} + assert response.data[3] == {"name": "project2"} + + +def test_import_jira_list_projects_with_problem_on_request(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-list-projects") + + with mock.patch('taiga.importers.jira.common.JiraClient') as JiraClientMock: + instance = mock.Mock() + instance.get.side_effect = exc.WrongArguments("Invalid Request") + JiraClientMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver"})) + + assert response.status_code == 400 + + +def test_import_jira_project_without_project_id(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-import-project") + + with mock.patch('taiga.importers.jira.tasks.JiraNormalImporter') as JiraNormalImporterMock: + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver"})) + + assert response.status_code == 400 + settings.CELERY_ENABLED = False + + +def test_import_jira_project_without_url(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-jira-import-project") + + with mock.patch('taiga.importers.jira.tasks.JiraNormalImporter') as JiraNormalImporterMock: + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "project_id": 1})) + + assert response.status_code == 400 + settings.CELERY_ENABLED = False + + +def test_import_jira_project_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="async-imported-project") + client.login(user) + + url = reverse("importers-jira-import-project") + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as ApiJiraNormalImporterMock: + with mock.patch('taiga.importers.jira.tasks.JiraNormalImporter') as TasksJiraNormalImporterMock: + TasksJiraNormalImporterMock.return_value.import_project.return_value = project + ApiJiraNormalImporterMock.return_value.list_issue_types.return_value = [] + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver", "project": 1})) + + assert response.status_code == 202 + assert "task_id" in response.data + settings.CELERY_ENABLED = False + + +def test_import_jira_project_with_celery_disabled(client, settings): + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="imported-project") + client.login(user) + + url = reverse("importers-jira-import-project") + + with mock.patch('taiga.importers.jira.api.JiraNormalImporter') as JiraNormalImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + instance.list_issue_types.return_value = [] + JiraNormalImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "access.secret", "url": "http://jiraserver", "project": 1})) + + assert response.status_code == 200 + assert "slug" in response.data + assert response.data['slug'] == "imported-project" diff --git a/tests/integration/test_importers_trello_api.py b/tests/integration/test_importers_trello_api.py new file mode 100644 index 00000000..9497e28e --- /dev/null +++ b/tests/integration/test_importers_trello_api.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 . + +import pytest +import json + +from unittest import mock + +from django.core.urlresolvers import reverse + +from .. import factories as f +from taiga.base.utils import json +from taiga.base import exceptions as exc + + +pytestmark = pytest.mark.django_db + + +def test_auth_url(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-auth-url") + + with mock.patch('taiga.importers.trello.importer.OAuth1Session') as OAuth1SessionMock: + session = mock.Mock() + session.fetch_request_token.return_value = {"oauth_token": "token", "oauth_token_secret": "token"} + OAuth1SessionMock.return_value = session + + response = client.get(url, content_type="application/json") + + assert response.status_code == 200 + assert 'url' in response.data + assert response.data['url'] == "https://trello.com/1/OAuthAuthorizeToken?oauth_token=token&scope=read,write,account&expiration=1day&name=Taiga&return_url=http://localhost:9001/project/new/import/trello" + + +def test_authorize(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-auth-url") + authorize_url = reverse("importers-trello-authorize") + + with mock.patch('taiga.importers.trello.importer.OAuth1Session') as OAuth1SessionMock: + session = mock.Mock() + session.fetch_request_token.return_value = {"oauth_token": "token", "oauth_token_secret": "token"} + session.fetch_access_token.return_value = {"oauth_token": "token", "oauth_token_secret": "token"} + OAuth1SessionMock.return_value = session + + client.get(url, content_type="application/json") + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"oauth_verifier": "token"})) + + assert response.status_code == 200 + assert 'token' in response.data + assert response.data['token'] == "token" + +def test_authorize_without_token_and_secret(client): + user = f.UserFactory.create() + client.login(user) + + authorize_url = reverse("importers-trello-authorize") + + with mock.patch('taiga.importers.trello.importer.OAuth1Session') as OAuth1SessionMock: + session = mock.Mock() + session.fetch_access_token.return_value = {"oauth_token": "token", "oauth_token_secret": "token"} + OAuth1SessionMock.return_value = session + + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"oauth_verifier": "token"})) + + assert response.status_code == 400 + assert 'token' not in response.data + + +def test_authorize_with_bad_verify(client): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-auth-url") + authorize_url = reverse("importers-trello-authorize") + + with mock.patch('taiga.importers.trello.importer.OAuth1Session') as OAuth1SessionMock: + session = mock.Mock() + session.fetch_request_token.return_value = {"oauth_token": "token", "oauth_token_secret": "token"} + session.fetch_access_token.side_effect = Exception("Bad Token") + OAuth1SessionMock.return_value = session + + client.get(url, content_type="application/json") + response = client.post(authorize_url, content_type="application/json", data=json.dumps({"oauth_verifier": "token"})) + + assert response.status_code == 400 + assert 'token' not in response.data + + +def test_import_trello_list_users(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-list-users") + + with mock.patch('taiga.importers.trello.api.TrelloImporter') as TrelloImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "fullName": "user1", "email": None}, + {"id": 2, "fullName": "user2", "email": None} + ] + TrelloImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 200 + assert response.data[0]["id"] == 1 + assert response.data[1]["id"] == 2 + + +def test_import_trello_list_users_without_project(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-list-users") + + with mock.patch('taiga.importers.trello.api.TrelloImporter') as TrelloImporterMock: + instance = mock.Mock() + instance.list_users.return_value = [ + {"id": 1, "fullName": "user1", "email": None}, + {"id": 2, "fullName": "user2", "email": None} + ] + TrelloImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + + +def test_import_trello_list_users_with_problem_on_request(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-list-users") + + with mock.patch('taiga.importers.trello.importer.TrelloClient') as TrelloClientMock: + instance = mock.Mock() + instance.get.side_effect = exc.WrongArguments("Invalid Request") + TrelloClientMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 400 + + +def test_import_trello_list_projects(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-list-projects") + + with mock.patch('taiga.importers.trello.api.TrelloImporter') as TrelloImporterMock: + instance = mock.Mock() + instance.list_projects.return_value = ["project1", "project2"] + TrelloImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 200 + assert response.data[0] == "project1" + assert response.data[1] == "project2" + + +def test_import_trello_list_projects_with_problem_on_request(client, settings): + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-list-projects") + + with mock.patch('taiga.importers.trello.importer.TrelloClient') as TrelloClientMock: + instance = mock.Mock() + instance.get.side_effect = exc.WrongArguments("Invalid Request") + TrelloClientMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + + +def test_import_trello_project_without_project_id(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + client.login(user) + + url = reverse("importers-trello-import-project") + + with mock.patch('taiga.importers.trello.tasks.TrelloImporter') as TrelloImporterMock: + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"})) + + assert response.status_code == 400 + settings.CELERY_ENABLED = False + + +def test_import_trello_project_with_celery_enabled(client, settings): + settings.CELERY_ENABLED = True + + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="async-imported-project") + client.login(user) + + url = reverse("importers-trello-import-project") + + with mock.patch('taiga.importers.trello.tasks.TrelloImporter') as TrelloImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + TrelloImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 202 + assert "task_id" in response.data + settings.CELERY_ENABLED = False + + +def test_import_trello_project_with_celery_disabled(client, settings): + user = f.UserFactory.create() + project = f.ProjectFactory.create(slug="imported-project") + client.login(user) + + url = reverse("importers-trello-import-project") + + with mock.patch('taiga.importers.trello.api.TrelloImporter') as TrelloImporterMock: + instance = mock.Mock() + instance.import_project.return_value = project + TrelloImporterMock.return_value = instance + response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1})) + + assert response.status_code == 200 + assert "slug" in response.data + assert response.data['slug'] == "imported-project" diff --git a/tests/integration/test_issues.py b/tests/integration/test_issues.py index 5fc58827..8148729f 100644 --- a/tests/integration/test_issues.py +++ b/tests/integration/test_issues.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_issues_tags.py b/tests/integration/test_issues_tags.py index cb355f8f..79f2f28f 100644 --- a/tests/integration/test_issues_tags.py +++ b/tests/integration/test_issues_tags.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_mdrender.py b/tests/integration/test_mdrender.py index 64a5182e..106f5c3e 100644 --- a/tests/integration/test_mdrender.py +++ b/tests/integration/test_mdrender.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_memberships.py b/tests/integration/test_memberships.py index beb9134b..ad2e70f3 100644 --- a/tests/integration/test_memberships.py +++ b/tests/integration/test_memberships.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -30,8 +30,8 @@ pytestmark = pytest.mark.django_db def test_get_members_from_bulk(): - data = [{"role_id": "1", "email": "member1@email.com"}, - {"role_id": "1", "email": "member2@email.com"}] + data = [{"role_id": "1", "username": "member1@email.com"}, + {"role_id": "1", "username": "member2@email.com"}] members = services.get_members_from_bulk(data, project_id=1) assert len(members) == 2 @@ -39,10 +39,65 @@ def test_get_members_from_bulk(): assert members[1].email == "member2@email.com" +def test_create_member_using_email(client): + project = f.ProjectFactory() + john = f.UserFactory.create() + joseph = f.UserFactory.create() + tester = f.RoleFactory(project=project, name="Tester") + f.MembershipFactory(project=project, user=john, is_admin=True) + url = reverse("memberships-list") + + data = {"project": project.id, "role": tester.pk, "username": joseph.email} + client.login(john) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 201 + assert response.data["email"] == joseph.email + + +def test_create_member_using_username_without_being_contacts(client): + project = f.ProjectFactory() + john = f.UserFactory.create() + joseph = f.UserFactory.create() + tester = f.RoleFactory(project=project, name="Tester") + f.MembershipFactory(project=project, user=john, is_admin=True) + url = reverse("memberships-list") + + data = {"project": project.id, "role": tester.pk, "username": joseph.username} + client.login(john) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 400 + assert "The user must be a valid contact" in response.data["username"][0] + + +def test_create_member_using_username_being_contacts(client): + project = f.ProjectFactory() + john = f.UserFactory.create() + joseph = f.UserFactory.create() + tester = f.RoleFactory(project=project, name="Tester", permissions=["view_project"]) + f.MembershipFactory(project=project, user=john, role=tester, is_admin=True) + + # They are members from another project + project2 = f.ProjectFactory() + gamer = f.RoleFactory(project=project2, name="Gamer", permissions=["view_project"]) + f.MembershipFactory(project=project2, user=john, role=gamer, is_admin=True) + f.MembershipFactory(project=project2, user=joseph, role=gamer) + + url = reverse("memberships-list") + + data = {"project": project.id, "role": tester.pk, "username": joseph.username} + client.login(john) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 201 + assert response.data["user"] == joseph.id + + def test_create_members_in_bulk(): with mock.patch("taiga.projects.services.members.db") as db: - data = [{"role_id": "1", "email": "member1@email.com"}, - {"role_id": "1", "email": "member2@email.com"}] + data = [{"role_id": "1", "username": "member1@email.com"}, + {"role_id": "1", "username": "member2@email.com"}] members = services.create_members_in_bulk(data, project_id=1) db.save_in_bulk.assert_called_once_with(members, None, None) @@ -51,25 +106,57 @@ def test_api_create_bulk_members(client): project = f.ProjectFactory() john = f.UserFactory.create() joseph = f.UserFactory.create() - tester = f.RoleFactory(project=project, name="Tester") - gamer = f.RoleFactory(project=project, name="Gamer") - f.MembershipFactory(project=project, user=project.owner, is_admin=True) + other = f.UserFactory.create() + tester = f.RoleFactory(project=project, name="Tester", permissions=["view_project"]) + gamer = f.RoleFactory(project=project, name="Gamer", permissions=["view_project"]) + f.MembershipFactory(project=project, user=john, role=tester, is_admin=True) + + # John and Other are members from another project + project2 = f.ProjectFactory() + f.MembershipFactory(project=project2, user=john, role=gamer, is_admin=True) + f.MembershipFactory(project=project2, user=other, role=gamer) url = reverse("memberships-bulk-create") data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.pk, "email": john.email}, - {"role_id": gamer.pk, "email": joseph.email}, + {"role_id": gamer.pk, "username": joseph.email}, + {"role_id": gamer.pk, "username": other.username}, ] } - client.login(project.owner) + client.login(john) response = client.json.post(url, json.dumps(data)) assert response.status_code == 200 - assert response.data[0]["email"] == john.email - assert response.data[1]["email"] == joseph.email + response_user_ids = set([u["user"] for u in response.data]) + user_ids = {other.id, joseph.id} + assert(user_ids.issubset(response_user_ids)) + + +def test_api_create_bulk_members_invalid_user_id(client): + project = f.ProjectFactory() + john = f.UserFactory.create() + joseph = f.UserFactory.create() + other = f.UserFactory.create() + tester = f.RoleFactory(project=project, name="Tester", permissions=["view_project"]) + gamer = f.RoleFactory(project=project, name="Gamer", permissions=["view_project"]) + f.MembershipFactory(project=project, user=john, role=tester, is_admin=True) + + url = reverse("memberships-bulk-create") + + data = { + "project_id": project.id, + "bulk_memberships": [ + {"role_id": gamer.pk, "username": joseph.email}, + {"role_id": gamer.pk, "username": other.username}, + ] + } + client.login(john) + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 400 + test_api_create_bulk_members_invalid_user_id def test_api_create_bulk_members_with_invalid_roles(client): @@ -85,8 +172,8 @@ def test_api_create_bulk_members_with_invalid_roles(client): data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.pk, "email": john.email}, - {"role_id": gamer.pk, "email": joseph.email}, + {"role_id": tester.pk, "username": john.email}, + {"role_id": gamer.pk, "username": joseph.email}, ] } client.login(project.owner) @@ -109,8 +196,8 @@ def test_api_create_bulk_members_with_allowed_domain(client): data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.pk, "email": "test1@email.com"}, - {"role_id": gamer.pk, "email": "test2@email.com"}, + {"role_id": tester.pk, "username": "test1@email.com"}, + {"role_id": gamer.pk, "username": "test2@email.com"}, ] } client.login(project.owner) @@ -133,16 +220,17 @@ def test_api_create_bulk_members_with_allowed_and_unallowed_domain(client, setti data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.pk, "email": "test@invalid-domain.com"}, - {"role_id": gamer.pk, "email": "test@email.com"}, + {"role_id": tester.pk, "username": "test@invalid-domain.com"}, + {"role_id": gamer.pk, "username": "test@email.com"}, ] } client.login(project.owner) response = client.json.post(url, json.dumps(data)) + print(response.data) assert response.status_code == 400 - assert "email" in response.data["bulk_memberships"][0] - assert "email" not in response.data["bulk_memberships"][1] + assert "username" in response.data["bulk_memberships"][0] + assert "username" not in response.data["bulk_memberships"][1] def test_api_create_bulk_members_with_unallowed_domains(client, settings): @@ -157,16 +245,16 @@ def test_api_create_bulk_members_with_unallowed_domains(client, settings): data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.pk, "email": "test1@invalid-domain.com"}, - {"role_id": gamer.pk, "email": "test2@invalid-domain.com"}, + {"role_id": tester.pk, "username": "test1@invalid-domain.com"}, + {"role_id": gamer.pk, "username": "test2@invalid-domain.com"}, ] } client.login(project.owner) response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - assert "email" in response.data["bulk_memberships"][0] - assert "email" in response.data["bulk_memberships"][1] + assert "username" in response.data["bulk_memberships"][0] + assert "username" in response.data["bulk_memberships"][1] def test_api_create_bulk_members_without_enough_memberships_private_project_slots_one_project(client): @@ -180,10 +268,10 @@ def test_api_create_bulk_members_without_enough_memberships_private_project_slot data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": role.pk, "email": "test1@test.com"}, - {"role_id": role.pk, "email": "test2@test.com"}, - {"role_id": role.pk, "email": "test3@test.com"}, - {"role_id": role.pk, "email": "test4@test.com"}, + {"role_id": role.pk, "username": "test1@test.com"}, + {"role_id": role.pk, "username": "test2@test.com"}, + {"role_id": role.pk, "username": "test3@test.com"}, + {"role_id": role.pk, "username": "test4@test.com"}, ] } client.login(user) @@ -206,10 +294,10 @@ def test_api_create_bulk_members_for_admin_without_enough_memberships_private_pr data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": role.pk, "email": "test1@test.com"}, - {"role_id": role.pk, "email": "test2@test.com"}, - {"role_id": role.pk, "email": "test3@test.com"}, - {"role_id": role.pk, "email": "test4@test.com"}, + {"role_id": role.pk, "username": "test1@test.com"}, + {"role_id": role.pk, "username": "test2@test.com"}, + {"role_id": role.pk, "username": "test3@test.com"}, + {"role_id": role.pk, "username": "test4@test.com"}, ] } client.login(user) @@ -237,10 +325,10 @@ def test_api_create_bulk_members_with_enough_memberships_private_project_slots_m data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": role.pk, "email": "test1@test.com"}, - {"role_id": role.pk, "email": "test2@test.com"}, - {"role_id": role.pk, "email": "test3@test.com"}, - {"role_id": role.pk, "email": "test4@test.com"}, + {"role_id": role.pk, "username": "test1@test.com"}, + {"role_id": role.pk, "username": "test2@test.com"}, + {"role_id": role.pk, "username": "test3@test.com"}, + {"role_id": role.pk, "username": "test4@test.com"}, ] } client.login(user) @@ -260,10 +348,10 @@ def test_api_create_bulk_members_without_enough_memberships_public_project_slots data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": role.pk, "email": "test1@test.com"}, - {"role_id": role.pk, "email": "test2@test.com"}, - {"role_id": role.pk, "email": "test3@test.com"}, - {"role_id": role.pk, "email": "test4@test.com"}, + {"role_id": role.pk, "username": "test1@test.com"}, + {"role_id": role.pk, "username": "test2@test.com"}, + {"role_id": role.pk, "username": "test3@test.com"}, + {"role_id": role.pk, "username": "test4@test.com"}, ] } client.login(user) @@ -290,10 +378,10 @@ def test_api_create_bulk_members_with_enough_memberships_public_project_slots_mu data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": role.pk, "email": "test1@test.com"}, - {"role_id": role.pk, "email": "test2@test.com"}, - {"role_id": role.pk, "email": "test3@test.com"}, - {"role_id": role.pk, "email": "test4@test.com"}, + {"role_id": role.pk, "username": "test1@test.com"}, + {"role_id": role.pk, "username": "test2@test.com"}, + {"role_id": role.pk, "username": "test3@test.com"}, + {"role_id": role.pk, "username": "test4@test.com"}, ] } client.login(user) @@ -312,7 +400,7 @@ def test_api_create_bulk_members_with_extra_text(client, outbox): data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.pk, "email": "john@email.com"}, + {"role_id": tester.pk, "username": "john@email.com"}, ], "invitation_extra_text": invitation_extra_text } @@ -350,7 +438,7 @@ def test_api_invite_existing_user(client, outbox): client.login(role.project.owner) url = reverse("memberships-list") - data = {"role": role.pk, "project": role.project.pk, "email": user.email} + data = {"role": role.pk, "project": role.project.pk, "username": user.email} response = client.json.post(url, json.dumps(data)) @@ -364,7 +452,7 @@ def test_api_invite_existing_user(client, outbox): assert "Added to the project" in message.subject -def test_api_create_invalid_membership_email_failing(client): +def test_api_create_invalid_membership_no_email_no_user(client): "Should not create the invitation linked to that user" user = f.UserFactory.create() role = f.RoleFactory.create() @@ -388,7 +476,7 @@ def test_api_create_invalid_membership_role_doesnt_exist_in_the_project(client): client.login(project.owner) url = reverse("memberships-list") - data = {"role": role.pk, "project": project.pk, "email": user.email} + data = {"role": role.pk, "project": project.pk, "username": user.email} response = client.json.post(url, json.dumps(data)) @@ -404,7 +492,7 @@ def test_api_create_membership(client): client.login(membership.user) url = reverse("memberships-list") - data = {"role": role.pk, "project": role.project.pk, "email": user.email} + data = {"role": role.pk, "project": role.project.pk, "username": user.email} response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 @@ -419,11 +507,11 @@ def test_api_create_membership_with_unallowed_domain(client, settings): client.login(membership.user) url = reverse("memberships-list") - data = {"role": role.pk, "project": role.project.pk, "email": "test@invalid-email.com"} + data = {"role": role.pk, "project": role.project.pk, "username": "test@invalid-email.com"} response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 - assert "email" in response.data + assert "username" in response.data def test_api_create_membership_with_allowed_domain(client, settings): @@ -434,7 +522,7 @@ def test_api_create_membership_with_allowed_domain(client, settings): client.login(membership.user) url = reverse("memberships-list") - data = {"role": role.pk, "project": role.project.pk, "email": "test@email.com"} + data = {"role": role.pk, "project": role.project.pk, "username": "test@email.com"} response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 @@ -449,7 +537,7 @@ def test_api_create_membership_without_enough_memberships_private_project_slots_ client.login(user) url = reverse("memberships-list") - data = {"role": role.pk, "project": project.pk, "email": "test@test.com"} + data = {"role": role.pk, "project": project.pk, "username": "test@test.com"} response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 @@ -470,7 +558,7 @@ def test_api_create_membership_with_enough_memberships_private_project_slots_mul client.login(user) url = reverse("memberships-list") - data = {"role": role.pk, "project": project.pk, "email": "test@test.com"} + data = {"role": role.pk, "project": project.pk, "username": "test@test.com"} response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 @@ -484,7 +572,7 @@ def test_api_create_membership_without_enough_memberships_public_project_slots_o client.login(user) url = reverse("memberships-list") - data = {"role": role.pk, "project": project.pk, "email": "test@test.com"} + data = {"role": role.pk, "project": project.pk, "username": "test@test.com"} response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 @@ -505,7 +593,7 @@ def test_api_create_membership_with_enough_memberships_public_project_slots_mult client.login(user) url = reverse("memberships-list") - data = {"role": role.pk, "project": project.pk, "email": "test@test.com"} + data = {"role": role.pk, "project": project.pk, "username": "test@test.com"} response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 @@ -515,11 +603,20 @@ def test_api_edit_membership(client): membership = f.MembershipFactory(is_admin=True) client.login(membership.user) url = reverse("memberships-detail", args=[membership.id]) - data = {"email": "new@email.com"} + data = {"username": "new@email.com"} response = client.json.patch(url, json.dumps(data)) - assert response.status_code == 200 + +def test_api_edit_membership(client): + membership = f.MembershipFactory(is_admin=True) + client.login(membership.user) + url = reverse("memberships-detail", args=[membership.id]) + data = {"username": "new@email.com"} + response = client.json.patch(url, json.dumps(data)) + assert response.status_code == 200 + + def test_api_change_owner_membership_to_no_admin_return_error(client): project = f.ProjectFactory() membership_owner = f.MembershipFactory(project=project, user=project.owner, is_admin=True) @@ -573,12 +670,11 @@ def test_api_create_member_max_pending_memberships(client, settings): f.MembershipFactory(project=project, user=None) url = reverse("memberships-list") - data = {"project": project.id, "role": tester.id, "email": joseph.email} + data = {"project": project.id, "role": tester.id, "username": joseph.email} client.login(john) response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 assert "limit of pending memberships" in response.data["_error_message"] - settings.MAX_PENDING_MEMBERSHIPS = None def test_api_create_bulk_members_max_pending_memberships(client, settings): @@ -595,14 +691,13 @@ def test_api_create_bulk_members_max_pending_memberships(client, settings): data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": tester.id, "email": "testing@taiga.io"}, + {"role_id": tester.id, "username": "testing@taiga.io"}, ] } client.login(john) response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 assert "limit of pending memberships" in response.data["_error_message"] - settings.MAX_PENDING_MEMBERSHIPS = None def test_create_memberhips_throttling(client, settings): @@ -615,13 +710,13 @@ def test_create_memberhips_throttling(client, settings): client.login(membership.user) url = reverse("memberships-list") - data = {"role": role.pk, "project": role.project.pk, "email": user.email} + data = {"role": role.pk, "project": role.project.pk, "username": user.email} response = client.json.post(url, json.dumps(data)) assert response.status_code == 201 assert response.data["user_email"] == user.email - data = {"role": role.pk, "project": role.project.pk, "email": user2.email} + data = {"role": role.pk, "project": role.project.pk, "username": user2.email} response = client.json.post(url, json.dumps(data)) assert response.status_code == 429 @@ -671,8 +766,8 @@ def test_api_create_bulk_members_throttling(client, settings): data = { "project_id": project.id, "bulk_memberships": [ - {"role_id": gamer.pk, "email": joseph.email}, - {"role_id": gamer.pk, "email": other.email}, + {"role_id": gamer.pk, "username": joseph.email}, + {"role_id": gamer.pk, "username": other.username}, ] } client.login(john) diff --git a/tests/integration/test_milestones.py b/tests/integration/test_milestones.py index 18562a8e..be3ec0ee 100644 --- a/tests/integration/test_milestones.py +++ b/tests/integration/test_milestones.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -18,6 +18,10 @@ # along with this program. If not, see . import pytest +import pytz + +from datetime import datetime, timedelta +from urllib.parse import quote from django.core.urlresolvers import reverse @@ -82,3 +86,97 @@ def test_list_milestones_taiga_info_headers(client): assert response2.has_header("Taiga-Info-Total-Opened-Milestones") == True assert response2["taiga-info-total-closed-milestones"] == "3" assert response2["taiga-info-total-opened-milestones"] == "1" + + +def test_api_filter_by_created_date__lte(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project) + f.MembershipFactory.create( + project=project, user=user, role=role, is_admin=True + ) + one_day_ago = datetime.now(pytz.utc) - timedelta(days=1) + + old_milestone = f.MilestoneFactory.create( + project=project, owner=user, created_date=one_day_ago + ) + milestone = f.MilestoneFactory.create(project=project, owner=user) + + url = reverse("milestones-list") + "?created_date__lte=%s" % ( + quote(milestone.created_date.isoformat()) + ) + + client.login(milestone.owner) + response = client.get(url) + number_of_milestones = len(response.data) + + assert response.status_code == 200 + assert number_of_milestones == 2 + + +def test_api_filter_by_modified_date__gte(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project) + f.MembershipFactory.create( + project=project, user=user, role=role, is_admin=True + ) + one_day_ago = datetime.now(pytz.utc) - timedelta(days=1) + + older_milestone = f.MilestoneFactory.create( + project=project, owner=user, created_date=one_day_ago + ) + milestone = f.MilestoneFactory.create(project=project, owner=user) + # we have to refresh as it slightly differs + milestone.refresh_from_db() + + assert older_milestone.modified_date < milestone.modified_date + + url = reverse("milestones-list") + "?modified_date__gte=%s" % ( + quote(milestone.modified_date.isoformat()) + ) + + client.login(milestone.owner) + response = client.get(url) + number_of_milestones = len(response.data) + + assert response.status_code == 200 + assert number_of_milestones == 1 + assert response.data[0]["slug"] == milestone.slug + + +@pytest.mark.parametrize("field_name", [ + "estimated_start", "estimated_finish" +]) +def test_api_filter_by_milestone__estimated_start_and_end(client, field_name): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project) + f.MembershipFactory.create( + project=project, user=user, role=role, is_admin=True + ) + milestone = f.MilestoneFactory.create(project=project, owner=user) + + assert hasattr(milestone, field_name) + date = getattr(milestone, field_name) + before = (date - timedelta(days=1)).isoformat() + after = (date + timedelta(days=1)).isoformat() + + client.login(milestone.owner) + + expections = { + field_name + "__gte=" + quote(before): 1, + field_name + "__gte=" + quote(after): 0, + field_name + "__lte=" + quote(before): 0, + field_name + "__lte=" + quote(after): 1 + } + + for param, expection in expections.items(): + url = reverse("milestones-list") + "?" + param + response = client.get(url) + number_of_milestones = len(response.data) + + assert response.status_code == 200 + assert number_of_milestones == expection, param + if number_of_milestones > 0: + assert response.data[0]["slug"] == milestone.slug diff --git a/tests/integration/test_models.py b/tests/integration/test_models.py index 6c0b7b01..79a973c4 100644 --- a/tests/integration/test_models.py +++ b/tests/integration/test_models.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_neighbors.py b/tests/integration/test_neighbors.py index 7f00a55b..7441e6d0 100644 --- a/tests/integration/test_neighbors.py +++ b/tests/integration/test_neighbors.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_notifications.py b/tests/integration/test_notifications.py index 66507959..a552cc4b 100644 --- a/tests/integration/test_notifications.py +++ b/tests/integration/test_notifications.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -25,10 +25,13 @@ import datetime import hashlib import binascii import struct +import pytz from unittest.mock import MagicMock, patch from django.core.urlresolvers import reverse +from django.utils import timezone + from django.apps import apps from .. import factories as f @@ -1041,7 +1044,7 @@ def test_retrieve_notify_policies_by_anonymous_user(client): def test_ms_thread_id(): id = '' - now = datetime.datetime.now() + now = timezone.now() index = services.make_ms_thread_index(id, now) parsed = parse_ms_thread_index(index) @@ -1063,7 +1066,7 @@ def parse_ms_thread_index(index): # guid = '%08X-%04X-%04X-%04X-%12X' % (guid[0], guid[1], guid[2], (guid[3] >> 48) & 0xFFFF, guid[3] & 0xFFFFFFFFFFFF) f = struct.unpack('>Q', s[:6] + b'\0\0')[0] - ts = [datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=f//10)] + ts = [datetime.datetime(1601, 1, 1, tzinfo=pytz.utc) + datetime.timedelta(microseconds=f//10)] # for the 5 byte appendixes that we won't use for n in range(22, len(s), 5): diff --git a/tests/integration/test_occ.py b/tests/integration/test_occ.py index ccb9013f..20e69aa1 100644 --- a/tests/integration/test_occ.py +++ b/tests/integration/test_occ.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_permissions.py b/tests/integration/test_permissions.py index bc39384f..ee114e80 100644 --- a/tests/integration/test_permissions.py +++ b/tests/integration/test_permissions.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py index bd4fbac8..8fbe6e03 100644 --- a/tests/integration/test_projects.py +++ b/tests/integration/test_projects.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -33,6 +33,7 @@ from taiga.projects.tasks.models import Task from taiga.projects.issues.models import Issue from taiga.projects.epics.models import Epic from taiga.projects.choices import BLOCKED_BY_DELETING +from taiga.timeline.service import get_project_timeline from .. import factories as f from ..utils import DUMMY_BMP_DATA @@ -1860,11 +1861,10 @@ def test_delete_project_with_celery_enabled(client, settings): assert project.memberships.count() == 0 assert project.blocked_code == BLOCKED_BY_DELETING delete_project_mock.delay.assert_called_once_with(project.id) + settings.CELERY_ENABLED = False def test_delete_project_with_celery_disabled(client, settings): - settings.CELERY_ENABLED = False - user = f.UserFactory.create() project = f.ProjectFactory.create(owner=user) role = f.RoleFactory.create(project=project, permissions=["view_project"]) @@ -2095,3 +2095,182 @@ def test_color_tags_project_fired_on_element_update_respecting_color(): user_story.save() project = Project.objects.get(id=user_story.project.id) assert ["tag", "#123123"] in project.tags_colors + + +def test_duplicate_project(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create( + owner=user, + is_looking_for_people=True, + looking_for_people_note="Looking lookin", + ) + project.tags = ["tag1", "tag2"] + project.tags_colors = [["t1", "#abcbca"], ["t2", "#aaabbb"]] + + project.default_epic_status = f.EpicStatusFactory.create(project=project) + project.default_us_status = f.UserStoryStatusFactory.create(project=project) + project.default_task_status = f.TaskStatusFactory.create(project=project) + project.default_issue_status = f.IssueStatusFactory.create(project=project) + project.default_points = f.PointsFactory.create(project=project) + project.default_issue_type = f.IssueTypeFactory.create(project=project) + project.default_priority = f.PriorityFactory.create(project=project) + project.default_severity = f.SeverityFactory.create(project=project) + + f.EpicCustomAttributeFactory(project=project) + f.UserStoryCustomAttributeFactory(project=project) + f.TaskCustomAttributeFactory(project=project) + f.IssueCustomAttributeFactory(project=project) + + project.save() + + role = f.RoleFactory.create(project=project, permissions=["view_project"]) + f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) + extra_membership = f.MembershipFactory.create(project=project, is_admin=True, role__project=project) + membership = f.MembershipFactory.create(project=project, role=role) + url = reverse("projects-duplicate", args=(project.id,)) + + data = { + "name": "test", + "description": "description", + "is_private": True, + "users": [{ + "id": extra_membership.user.id + }] + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201 + + new_project = Project.objects.get(id=response.data["id"]) + + assert new_project.owner_id == user.id + owner_membership = new_project.memberships.get(user_id=user.id) + assert owner_membership.user_id == user.id + assert owner_membership.is_admin == True + assert project.memberships.get(user_id=extra_membership.user.id).role.slug == extra_membership.role.slug + assert set(project.tags) == set(new_project.tags) + assert set(dict(project.tags_colors).keys()) == set(dict(new_project.tags_colors).keys()) + + attributes = [ + "is_epics_activated", "is_backlog_activated", "is_kanban_activated", "is_wiki_activated", + "is_issues_activated", "videoconferences", "videoconferences_extra_data", + "is_looking_for_people", "looking_for_people_note", "is_private" + ] + + for attr in attributes: + assert getattr(project, attr) == getattr(new_project, attr) + + fk_attributes = [ + "default_epic_status", "default_us_status", "default_task_status", "default_issue_status", + "default_issue_type", "default_points", "default_priority", "default_severity", + ] + + for attr in fk_attributes: + assert getattr(project, attr).name == getattr(new_project, attr).name + + related_attributes = [ + "epic_statuses", "us_statuses", "task_statuses","issue_statuses", + "issue_types", "points", "priorities", "severities", + "epiccustomattributes", "userstorycustomattributes", "taskcustomattributes", "issuecustomattributes", + "roles" + ] + for attr in related_attributes: + from_names = set(getattr(project, attr).all().values_list("name", flat=True)) + to_names = set(getattr(new_project, attr).all().values_list("name", flat=True)) + assert from_names == to_names + + timeline = list(get_project_timeline(new_project)) + assert len(timeline) == 2 + assert timeline[0].event_type == "projects.project.create" + assert timeline[1].event_type == "projects.membership.create" + + +def test_duplicate_private_project_without_enough_private_projects_slots(client): + user = f.UserFactory.create(max_private_projects=0) + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(user=user, project=project, is_admin=True) + + url = reverse("projects-duplicate", args=(project.id,)) + data = { + "name": "test", + "description": "description", + "is_private": True, + "users": [] + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert "can't have more private projects" in response.data["_error_message"] + assert response["Taiga-Info-Project-Memberships"] == "1" + assert response["Taiga-Info-Project-Is-Private"] == "True" + + +def test_duplicate_private_project_without_enough_memberships_slots(client): + user = f.UserFactory.create(max_memberships_private_projects=1) + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(user=user, project=project, is_admin=True, role__project=project) + extra_membership = f.MembershipFactory.create(project=project, is_admin=True, role__project=project) + + url = reverse("projects-duplicate", args=(project.id,)) + data = { + "name": "test", + "description": "description", + "is_private": True, + "users": [{ + "id": extra_membership.user_id + }] + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert "current limit of memberships" in response.data["_error_message"] + assert response["Taiga-Info-Project-Memberships"] == "2" + assert response["Taiga-Info-Project-Is-Private"] == "True" + + +def test_duplicate_public_project_without_enough_public_projects_slots(client): + user = f.UserFactory.create(max_public_projects=0) + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(user=user, project=project, is_admin=True) + + url = reverse("projects-duplicate", args=(project.id,)) + data = { + "name": "test", + "description": "description", + "is_private": False, + "users": [] + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert "can't have more public projects" in response.data["_error_message"] + assert response["Taiga-Info-Project-Memberships"] == "1" + assert response["Taiga-Info-Project-Is-Private"] == "False" + + +def test_duplicate_public_project_without_enough_memberships_slots(client): + user = f.UserFactory.create(max_memberships_public_projects=1) + project = f.ProjectFactory.create(owner=user) + f.MembershipFactory(user=user, project=project, is_admin=True, role__project=project) + extra_membership = f.MembershipFactory.create(project=project, is_admin=True, role__project=project) + + url = reverse("projects-duplicate", args=(project.id,)) + data = { + "name": "test", + "description": "description", + "is_private": False, + "users": [{ + "id": extra_membership.user_id + }] + } + + client.login(user) + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 400 + assert "current limit of memberships" in response.data["_error_message"] + assert response["Taiga-Info-Project-Memberships"] == "2" + assert response["Taiga-Info-Project-Is-Private"] == "False" diff --git a/tests/integration/test_references_sequences.py b/tests/integration/test_references_sequences.py index 2c38900e..7bb035de 100644 --- a/tests/integration/test_references_sequences.py +++ b/tests/integration/test_references_sequences.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -67,6 +67,8 @@ def test_sequences(seq): @pytest.mark.django_db def test_unique_reference_per_project(seq, refmodels): + refmodels.Reference.objects.all().delete() + project = factories.ProjectFactory.create() seqname = refmodels.make_sequence_name(project) @@ -82,6 +84,8 @@ def test_unique_reference_per_project(seq, refmodels): @pytest.mark.django_db def test_regenerate_us_reference_on_project_change(seq, refmodels): + refmodels.Reference.objects.all().delete() + project1 = factories.ProjectFactory.create() seqname1 = refmodels.make_sequence_name(project1) project2 = factories.ProjectFactory.create() @@ -104,6 +108,8 @@ def test_regenerate_us_reference_on_project_change(seq, refmodels): @pytest.mark.django_db def test_regenerate_task_reference_on_project_change(seq, refmodels): + refmodels.Reference.objects.all().delete() + project1 = factories.ProjectFactory.create() seqname1 = refmodels.make_sequence_name(project1) project2 = factories.ProjectFactory.create() @@ -126,6 +132,8 @@ def test_regenerate_task_reference_on_project_change(seq, refmodels): @pytest.mark.django_db def test_regenerate_issue_reference_on_project_change(seq, refmodels): + refmodels.Reference.objects.all().delete() + project1 = factories.ProjectFactory.create() seqname1 = refmodels.make_sequence_name(project1) project2 = factories.ProjectFactory.create() @@ -149,6 +157,8 @@ def test_regenerate_issue_reference_on_project_change(seq, refmodels): @pytest.mark.django_db def test_params_validation_in_api_request(client, refmodels): + refmodels.Reference.objects.all().delete() + user = factories.UserFactory.create() project = factories.ProjectFactory.create(owner=user) seqname1 = refmodels.make_sequence_name(project) diff --git a/tests/integration/test_roles.py b/tests/integration/test_roles.py index e3b47f7d..aef3fac4 100644 --- a/tests/integration/test_roles.py +++ b/tests/integration/test_roles.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -55,27 +55,3 @@ def test_destroy_role_and_reassign_members(client): qs = Membership.objects.filter(project=project, role_id=role1.pk) assert qs.count() == 2 - - -def test_destroy_role_and_reassign_members_with_deleted_project(client): - """ - Regression test, that fixes some 500 errors on production - """ - - user1 = f.UserFactory.create() - user2 = f.UserFactory.create() - project = f.ProjectFactory.create(owner=user1) - role1 = f.RoleFactory.create(project=project) - role2 = f.RoleFactory.create(project=project) - f.MembershipFactory.create(project=project, user=user1, role=role1) - f.MembershipFactory.create(project=project, user=user2, role=role2) - - Project.objects.filter(pk=project.id).delete() - - url = reverse("roles-detail", args=[role2.pk]) + "?moveTo={}".format(role1.pk) - client.login(user1) - - response = client.delete(url) - - # FIXME: really should return 403? I think it should be 404 - assert response.status_code == 403, response.content diff --git a/tests/integration/test_searches.py b/tests/integration/test_searches.py index bb5e681d..31cbaf6d 100644 --- a/tests/integration/test_searches.py +++ b/tests/integration/test_searches.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py index 790dd3e8..1138507c 100644 --- a/tests/integration/test_stats.py +++ b/tests/integration/test_stats.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_tasks.py b/tests/integration/test_tasks.py index 71a2521b..12252bf7 100644 --- a/tests/integration/test_tasks.py +++ b/tests/integration/test_tasks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -704,6 +704,38 @@ def test_api_filter_by_finished_date(client): assert response.data[0]["subject"] == finished_task.subject +@pytest.mark.parametrize("field_name", ["estimated_start", "estimated_finish"]) +def test_api_filter_by_milestone__estimated_start_and_end(client, field_name): + user = f.UserFactory(is_superuser=True) + task = f.create_task(owner=user) + + assert task.milestone + assert hasattr(task.milestone, field_name) + date = getattr(task.milestone, field_name) + before = (date - timedelta(days=1)).isoformat() + after = (date + timedelta(days=1)).isoformat() + + client.login(task.owner) + + full_field_name = "milestone__" + field_name + expections = { + full_field_name + "__gte=" + quote(before): 1, + full_field_name + "__gte=" + quote(after): 0, + full_field_name + "__lte=" + quote(before): 0, + full_field_name + "__lte=" + quote(after): 1 + } + + for param, expection in expections.items(): + url = reverse("tasks-list") + "?" + param + response = client.get(url) + number_of_tasks = len(response.data) + + assert response.status_code == 200 + assert number_of_tasks == expection, param + if number_of_tasks > 0: + assert response.data[0]["subject"] == task.subject + + def test_api_filters_data(client): project = f.ProjectFactory.create() user1 = f.UserFactory.create(is_superuser=True) diff --git a/tests/integration/test_tasks_tags.py b/tests/integration/test_tasks_tags.py index 60856688..968bb07b 100644 --- a/tests/integration/test_tasks_tags.py +++ b/tests/integration/test_tasks_tags.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_throwttling.py b/tests/integration/test_throwttling.py index ad2949f4..0f96bb10 100644 --- a/tests/integration/test_throwttling.py +++ b/tests/integration/test_throwttling.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -29,20 +29,15 @@ from .. import factories as f pytestmark = pytest.mark.django_db -anon_rate_path = "taiga.base.throttling.AnonRateThrottle.get_rate" -user_rate_path = "taiga.base.throttling.UserRateThrottle.get_rate" import_rate_path = "taiga.export_import.throttling.ImportModeRateThrottle.get_rate" def test_anonimous_throttling_policy(client, settings): f.create_project() url = reverse("projects-list") + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "2/min" - with mock.patch(anon_rate_path) as anon_rate, \ - mock.patch(user_rate_path) as user_rate, \ - mock.patch(import_rate_path) as import_rate: - anon_rate.return_value = "2/day" - user_rate.return_value = "4/day" + with mock.patch(import_rate_path) as import_rate: import_rate.return_value = "7/day" cache.clear() @@ -53,19 +48,19 @@ def test_anonimous_throttling_policy(client, settings): response = client.json.get(url) assert response.status_code == 429 + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + cache.clear() + def test_user_throttling_policy(client, settings): project = f.create_project() f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) url = reverse("projects-detail", kwargs={"pk": project.pk}) + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = "4/min" client.login(project.owner) - with mock.patch(anon_rate_path) as anon_rate, \ - mock.patch(user_rate_path) as user_rate, \ - mock.patch(import_rate_path) as import_rate: - anon_rate.return_value = "2/day" - user_rate.return_value = "4/day" + with mock.patch(import_rate_path) as import_rate: import_rate.return_value = "7/day" cache.clear() @@ -81,6 +76,8 @@ def test_user_throttling_policy(client, settings): assert response.status_code == 429 client.logout() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + cache.clear() def test_import_mode_throttling_policy(client, settings): @@ -95,14 +92,12 @@ def test_import_mode_throttling_policy(client, settings): data = { "subject": "Test" } + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "2/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = "4/min" client.login(project.owner) - with mock.patch(anon_rate_path) as anon_rate, \ - mock.patch(user_rate_path) as user_rate, \ - mock.patch(import_rate_path) as import_rate: - anon_rate.return_value = "2/day" - user_rate.return_value = "4/day" + with mock.patch(import_rate_path) as import_rate: import_rate.return_value = "7/day" cache.clear() @@ -124,3 +119,7 @@ def test_import_mode_throttling_policy(client, settings): assert response.status_code == 429 client.logout() + + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + cache.clear() diff --git a/tests/integration/test_timeline.py b/tests/integration/test_timeline.py index e0353d15..14d84a35 100644 --- a/tests/integration/test_timeline.py +++ b/tests/integration/test_timeline.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -35,7 +35,7 @@ def test_add_to_object_timeline(): user1 = factories.UserFactory() task = factories.TaskFactory() - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task, "test", task.created_date) @@ -54,7 +54,7 @@ def test_get_timeline(): task3= factories.TaskFactory() task4= factories.TaskFactory() - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) service._add_to_object_timeline(user1, task2, "test", task2.created_date) @@ -73,7 +73,7 @@ def test_filter_timeline_no_privileges(): user2 = factories.UserFactory() task1= factories.TaskFactory() - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) timeline = Timeline.objects.exclude(event_type="users.user.create") timeline = service.filter_timeline_for_user(timeline, user2) @@ -88,7 +88,7 @@ def test_filter_timeline_public_project(): task1= factories.TaskFactory() task2= factories.TaskFactory.create(project=project) - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) service._add_to_object_timeline(user1, task2, "test", task2.created_date) timeline = Timeline.objects.exclude(event_type="users.user.create") @@ -104,7 +104,7 @@ def test_filter_timeline_private_project_anon_permissions(): task1= factories.TaskFactory() task2= factories.TaskFactory.create(project=project) - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) service._add_to_object_timeline(user1, task2, "test", task2.created_date) timeline = Timeline.objects.exclude(event_type="users.user.create") @@ -123,7 +123,7 @@ def test_filter_timeline_private_project_member_permissions(): task1= factories.TaskFactory() task2= factories.TaskFactory.create(project=project) - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) service._add_to_object_timeline(user1, task2, "test", task2.created_date) timeline = Timeline.objects.exclude(event_type="users.user.create") @@ -140,7 +140,7 @@ def test_filter_timeline_private_project_member_admin(): task1= factories.TaskFactory() task2= factories.TaskFactory.create(project=project) - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) service._add_to_object_timeline(user1, task2, "test", task2.created_date) timeline = Timeline.objects.exclude(event_type="users.user.create") @@ -157,7 +157,7 @@ def test_filter_timeline_private_project_member_superuser(): task1= factories.TaskFactory() task2= factories.TaskFactory.create(project=project) - service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: str(id(x))) + service.register_timeline_implementation("tasks.task", "test", lambda x, extra_data=None: id(x)) service._add_to_object_timeline(user1, task1, "test", task1.created_date) service._add_to_object_timeline(user1, task2, "test", task2.created_date) timeline = Timeline.objects.exclude(event_type="users.user.create") diff --git a/tests/integration/test_totals_projects.py b/tests/integration/test_totals_projects.py index 8d29d950..1433750f 100644 --- a/tests/integration/test_totals_projects.py +++ b/tests/integration/test_totals_projects.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2016 Andrey Antukh -# Copyright (C) 2014-2016 Jesús Espino -# Copyright (C) 2014-2016 David Barragán -# Copyright (C) 2014-2016 Anler Hernández -# Copyright (C) 2014-2016 Alejandro Alonso -# Copyright (C) 2014-2016 Taiga Agile LLC +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Anler Hernández +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 Taiga Agile LLC # 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 @@ -27,13 +27,15 @@ from taiga.projects.history.choices import HistoryType from taiga.projects.models import Project from django.core.urlresolvers import reverse +from django.utils import timezone pytestmark = pytest.mark.django_db + def test_project_totals_updated_on_activity(client): project = f.create_project() totals_updated_datetime = project.totals_updated_datetime - now = datetime.datetime.now() + now = timezone.now() assert project.total_activity == 0 totals_updated_datetime = project.totals_updated_datetime @@ -120,10 +122,10 @@ def test_project_totals_updated_on_like(client): f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) totals_updated_datetime = project.totals_updated_datetime - now = datetime.datetime.now() + now = timezone.now() assert project.total_activity == 0 - now = datetime.datetime.now() + now = timezone.now() totals_updated_datetime = project.totals_updated_datetime us = f.UserStoryFactory.create(project=project, owner=project.owner) diff --git a/tests/integration/test_us_autoclosing.py b/tests/integration/test_us_autoclosing.py index 13ba78d2..a9b29a6c 100644 --- a/tests/integration/test_us_autoclosing.py +++ b/tests/integration/test_us_autoclosing.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_users.py b/tests/integration/test_users.py index cad6507c..7f0208a9 100644 --- a/tests/integration/test_users.py +++ b/tests/integration/test_users.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -24,6 +24,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.core.files import File +from django.core.cache import cache as default_cache from .. import factories as f from ..utils import DUMMY_BMP_DATA @@ -446,6 +447,42 @@ def test_list_contacts_public_projects(client): assert response_content[0]["id"] == user_2.id +def test_list_contacts_filter_exclude_project(client): + project1 = f.ProjectFactory.create() + project2 = f.ProjectFactory.create() + user_1 = f.UserFactory.create() + user_2 = f.UserFactory.create() + user_3 = f.UserFactory.create() + user_4 = f.UserFactory.create() + role1 = f.RoleFactory(project=project1, permissions=["view_project"]) + role2 = f.RoleFactory(project=project2, permissions=["view_project"]) + + membership_11 = f.MembershipFactory.create(project=project1, user=user_1, role=role1) + membership_12 = f.MembershipFactory.create(project=project1, user=user_2, role=role1) + + membership_21 = f.MembershipFactory.create(project=project2, user=user_1, role=role2) + membership_23 = f.MembershipFactory.create(project=project2, user=user_3, role=role2) + membership_24 = f.MembershipFactory.create(project=project2, user=user_4, role=role2) + + url = reverse('users-contacts', kwargs={"pk": user_1.pk}) + + client.login(user_1) + response = client.get(url, content_type="application/json") + assert response.status_code == 200 + response_content = response.data + assert len(response_content) == 3 + + response = client.get(url + "?exclude_project={}".format(project1.id), content_type="application/json") + assert response.status_code == 200 + response_content = response.data + assert len(response_content) == 2 + + response = client.get(url + "?exclude_project={}".format(project2.id), content_type="application/json") + assert response.status_code == 200 + response_content = response.data + assert len(response_content) == 1 + + ############################## ## Mail permissions ############################## @@ -980,3 +1017,39 @@ def test_get_voted_list_permissions(): project.anon_permissions = ["view_project", "view_epic", "view_us", "view_tasks", "view_issues"] project.save() assert len(get_voted_list(fav_user, viewer_unpriviliged_user)) == 4 + +############################## +## Retrieve user +############################## + +def test_users_retrieve_throttling_api(client): + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["user-detail"] = "1/minute" + + user = f.UserFactory.create() + + url = reverse('users-detail', kwargs={"pk": user.pk}) + data = {} + + response = client.get(url, content_type="application/json") + assert response.status_code == 200 + + response = client.get(url, content_type="application/json") + assert response.status_code == 429 + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["user-detail"] = None + default_cache.clear() + + +def test_users_by_username_throttling_api(client): + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["user-detail"] = "1/minute" + user = f.UserFactory.create(username="test-user-detail") + + url = reverse('users-by-username') + data = {} + + response = client.get(url, {"username": user.username}, content_type="application/json") + assert response.status_code == 200 + + response = client.get(url, {"username": user.username}, content_type="application/json") + assert response.status_code == 429 + settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["user-detail"] = None + default_cache.clear() diff --git a/tests/integration/test_userstorage_api.py b/tests/integration/test_userstorage_api.py index b1b9ec16..1b6af766 100644 --- a/tests/integration/test_userstorage_api.py +++ b/tests/integration/test_userstorage_api.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index 48f013eb..37c2682d 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -658,6 +658,37 @@ def test_api_filter_by_finish_date(client): assert number_of_userstories == 1 assert response.data[0]["subject"] == userstory_to_finish.subject +@pytest.mark.parametrize("field_name", ["estimated_start", "estimated_finish"]) +def test_api_filter_by_milestone__estimated_start_and_end(client, field_name): + user = f.UserFactory(is_superuser=True) + userstory = f.create_userstory(owner=user) + + assert userstory.milestone + assert hasattr(userstory.milestone, field_name) + date = getattr(userstory.milestone, field_name) + before = (date - timedelta(days=1)).isoformat() + after = (date + timedelta(days=1)).isoformat() + + client.login(userstory.owner) + + full_field_name = "milestone__" + field_name + expections = { + full_field_name + "__gte=" + quote(before): 1, + full_field_name + "__gte=" + quote(after): 0, + full_field_name + "__lte=" + quote(before): 0, + full_field_name + "__lte=" + quote(after): 1 + } + + for param, expection in expections.items(): + url = reverse("userstories-list") + "?" + param + response = client.get(url) + number_of_userstories = len(response.data) + + assert response.status_code == 200 + assert number_of_userstories == expection, param + if number_of_userstories > 0: + assert response.data[0]["subject"] == userstory.subject + def test_api_filters_data(client): project = f.ProjectFactory.create() diff --git a/tests/integration/test_userstories_tags.py b/tests/integration/test_userstories_tags.py index 0cc56868..4ed136ec 100644 --- a/tests/integration/test_userstories_tags.py +++ b/tests/integration/test_userstories_tags.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_vote_issues.py b/tests/integration/test_vote_issues.py index 6a4a1bba..94615616 100644 --- a/tests/integration/test_vote_issues.py +++ b/tests/integration/test_vote_issues.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_vote_tasks.py b/tests/integration/test_vote_tasks.py index c369e268..4ffac011 100644 --- a/tests/integration/test_vote_tasks.py +++ b/tests/integration/test_vote_tasks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_vote_userstories.py b/tests/integration/test_vote_userstories.py index 11ab7532..7d742c01 100644 --- a/tests/integration/test_vote_userstories.py +++ b/tests/integration/test_vote_userstories.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_votes.py b/tests/integration/test_votes.py index 68d8cc4d..0230463a 100644 --- a/tests/integration/test_votes.py +++ b/tests/integration/test_votes.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_watch_issues.py b/tests/integration/test_watch_issues.py index 6d353012..518d4c29 100644 --- a/tests/integration/test_watch_issues.py +++ b/tests/integration/test_watch_issues.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_watch_milestones.py b/tests/integration/test_watch_milestones.py index 76082db4..e458220a 100644 --- a/tests/integration/test_watch_milestones.py +++ b/tests/integration/test_watch_milestones.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_watch_projects.py b/tests/integration/test_watch_projects.py index f6caafb3..bfb7b37a 100644 --- a/tests/integration/test_watch_projects.py +++ b/tests/integration/test_watch_projects.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_watch_tasks.py b/tests/integration/test_watch_tasks.py index b8f12d23..00167afe 100644 --- a/tests/integration/test_watch_tasks.py +++ b/tests/integration/test_watch_tasks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_watch_userstories.py b/tests/integration/test_watch_userstories.py index 789f6fbe..e11bd93c 100644 --- a/tests/integration/test_watch_userstories.py +++ b/tests/integration/test_watch_userstories.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_watch_wikipages.py b/tests/integration/test_watch_wikipages.py index af59d63d..b84733c0 100644 --- a/tests/integration/test_watch_wikipages.py +++ b/tests/integration/test_watch_wikipages.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks.py b/tests/integration/test_webhooks.py index 071bf89f..d4da70c1 100644 --- a/tests/integration/test_webhooks.py +++ b/tests/integration/test_webhooks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_epics.py b/tests/integration/test_webhooks_epics.py index f77dc829..a7a4d49d 100644 --- a/tests/integration/test_webhooks_epics.py +++ b/tests/integration/test_webhooks_epics.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_issues.py b/tests/integration/test_webhooks_issues.py index 8789408d..f6b98c9c 100644 --- a/tests/integration/test_webhooks_issues.py +++ b/tests/integration/test_webhooks_issues.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_milestones.py b/tests/integration/test_webhooks_milestones.py index 7d779350..3fe92e27 100644 --- a/tests/integration/test_webhooks_milestones.py +++ b/tests/integration/test_webhooks_milestones.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_signals.py b/tests/integration/test_webhooks_signals.py index 2d6e5bc4..7c8fe6d9 100644 --- a/tests/integration/test_webhooks_signals.py +++ b/tests/integration/test_webhooks_signals.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_tasks.py b/tests/integration/test_webhooks_tasks.py index bb9b7d14..55c0a12f 100644 --- a/tests/integration/test_webhooks_tasks.py +++ b/tests/integration/test_webhooks_tasks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_userstories.py b/tests/integration/test_webhooks_userstories.py index 1580df57..0cbfef61 100644 --- a/tests/integration/test_webhooks_userstories.py +++ b/tests/integration/test_webhooks_userstories.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_webhooks_wikipages.py b/tests/integration/test_webhooks_wikipages.py index 0e46108c..26ecefab 100644 --- a/tests/integration/test_webhooks_wikipages.py +++ b/tests/integration/test_webhooks_wikipages.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/integration/test_wikilinks.py b/tests/integration/test_wikilinks.py index 20e185dc..1dc19384 100644 --- a/tests/integration/test_wikilinks.py +++ b/tests/integration/test_wikilinks.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index da0d7085..1a228a40 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_base_api_permissions.py b/tests/unit/test_base_api_permissions.py index a76d4359..f56cb05e 100644 --- a/tests/unit/test_base_api_permissions.py +++ b/tests/unit/test_base_api_permissions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_common_throttle.py b/tests/unit/test_common_throttle.py new file mode 100644 index 00000000..03138557 --- /dev/null +++ b/tests/unit/test_common_throttle.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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.test import RequestFactory +from django.core.cache import cache +from django.contrib.auth.models import AnonymousUser + +from taiga.base.throttling import CommonThrottle +from taiga.users.models import User + + +def test_user_no_write_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = None + request = rf.post("/test") + request.user = User(id=1) + throttling = CommonThrottle() + for x in range(100): + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = None + +def test_user_simple_write_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = "1/min" + request = rf.post("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = None + +def test_user_multi_write_first_small_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = ["1/min", "10/min"] + request = rf.post("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = None + +def test_user_multi_write_first_big_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = ["10/min", "1/min"] + request = rf.post("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-write'] = None + +def test_user_no_read_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + request = rf.get("/test") + request.user = User(id=1) + throttling = CommonThrottle() + for x in range(100): + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + +def test_user_simple_read_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = "1/min" + request = rf.get("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + +def test_user_multi_read_first_small_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = ["1/min", "10/min"] + request = rf.get("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + +def test_user_multi_read_first_big_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = ["10/min", "1/min"] + request = rf.get("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + +def test_whitelisted_user_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = "1/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [1] + request = rf.get("/test") + request.user = User(id=1) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [] + +def test_not_whitelisted_user_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = "1/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [1] + request = rf.get("/test") + request.user = User(id=2) + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [] + +def test_anon_no_write_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = None + request = rf.post("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + for x in range(100): + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = None + +def test_anon_simple_write_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = "1/min" + request = rf.post("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = None + +def test_anon_multi_write_first_small_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = ["1/min", "10/min"] + request = rf.post("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = None + +def test_anon_multi_write_first_big_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = ["10/min", "1/min"] + request = rf.post("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-write'] = None + +def test_anon_no_read_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + request = rf.get("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + for x in range(100): + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + +def test_anon_simple_read_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "1/min" + request = rf.get("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + +def test_anon_multi_read_first_small_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = ["1/min", "10/min"] + request = rf.get("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + +def test_anon_multi_read_first_big_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = ["10/min", "1/min"] + request = rf.get("/test") + request.user = AnonymousUser() + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + +def test_whitelisted_anon_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "1/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = ["127.0.0.1"] + request = rf.get("/test") + request.user = AnonymousUser() + request.META["REMOTE_ADDR"] = "127.0.0.1" + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) + for x in range(100): + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [] + +def test_not_whitelisted_anon_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "1/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = ["127.0.0.1"] + request = rf.get("/test") + request.user = AnonymousUser() + request.META["REMOTE_ADDR"] = "127.0.0.2" + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [] + +def test_whitelisted_subnet_anon_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "1/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = ["192.168.0.0/24"] + request = rf.get("/test") + request.user = AnonymousUser() + request.META["REMOTE_ADDR"] = "192.168.0.123" + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) + for x in range(100): + assert throttling.allow_request(request, None) + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [] + +def test_not_whitelisted_subnet_anon_throttling(settings, rf): + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = "1/min" + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = ["192.168.0.0/24"] + request = rf.get("/test") + request.user = AnonymousUser() + request.META["REMOTE_ADDR"] = "192.168.1.123" + throttling = CommonThrottle() + assert throttling.allow_request(request, None) + assert throttling.allow_request(request, None) is False + for x in range(100): + assert throttling.allow_request(request, None) is False + cache.clear() + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon-read'] = None + settings.REST_FRAMEWORK['DEFAULT_THROTTLE_WHITELIST'] = [] diff --git a/tests/unit/test_deferred.py b/tests/unit/test_deferred.py deleted file mode 100644 index 832c4b0b..00000000 --- a/tests/unit/test_deferred.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# 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 unittest import mock - -from taiga import celery -from taiga.deferred import defer, call_async, apply_async - - -def test_defer(): - # settings.CELERY_ALWAYS_EAGER = True - name = "task name" - args = (1, 2) - kwargs = {"kw": "keyword argument"} - - with mock.patch("taiga.deferred.app") as app: - defer(name, *args, **kwargs) - app.tasks[name].apply.assert_called_once_with(args, kwargs, - routing_key="transient.deferred") - - -def test_apply_async(): - name = "task name" - args = (1, 2) - kwargs = {"kw": "keyword argument"} - - with mock.patch("taiga.deferred.app") as app: - apply_async(name, args, kwargs) - app.tasks[name].apply.assert_called_once_with(args, kwargs) - - -def test_call_async(): - name = "task name" - args = (1, 2) - kwargs = {"kw": "keyword argument"} - - with mock.patch("taiga.deferred.app") as app: - call_async(name, *args, **kwargs) - app.tasks[name].apply.assert_called_once_with(args, kwargs) - - -def test_task_invocation(): - celery.app.task(name="_test_task")(lambda: 1) - assert defer("_test_task").get() == 1 diff --git a/tests/unit/test_export.py b/tests/unit/test_export.py index 1088550f..e40f80ee 100644 --- a/tests/unit/test_export.py +++ b/tests/unit/test_export.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 @@ -27,7 +27,7 @@ pytestmark = pytest.mark.django_db def test_export_issue_finish_date(client): - issue = f.IssueFactory.create(finished_date="2014-10-22") + issue = f.IssueFactory.create(finished_date="2014-10-22T00:00:00+0000") output = io.BytesIO() render_project(issue.project, output) project_data = json.loads(output.getvalue()) @@ -36,7 +36,7 @@ def test_export_issue_finish_date(client): def test_export_user_story_finish_date(client): - user_story = f.UserStoryFactory.create(finish_date="2014-10-22") + user_story = f.UserStoryFactory.create(finish_date="2014-10-22T00:00:00+0000") output = io.BytesIO() render_project(user_story.project, output) project_data = json.loads(output.getvalue()) diff --git a/tests/unit/test_import.py b/tests/unit/test_import.py index 58f9b9db..164fc816 100644 --- a/tests/unit/test_import.py +++ b/tests/unit/test_import.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_mdrender.py b/tests/unit/test_mdrender.py index 90bbe001..4c798d41 100644 --- a/tests/unit/test_mdrender.py +++ b/tests/unit/test_mdrender.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 @@ -23,6 +23,7 @@ from taiga.mdrender.extensions import emojify from taiga.mdrender.service import render, cache_by_sha, get_diff_of_htmls, render_and_extract from datetime import datetime +import pytz dummy_project = MagicMock() dummy_project.id = 1 @@ -206,19 +207,21 @@ def test_render_relative_image(): def test_render_triple_quote_code(): - expected_result = "
print(\"test\")\n
" + expected_result = '
print("test")\n
' + assert render(dummy_project, "```python\nprint(\"test\")\n```") == expected_result def test_render_triple_quote_and_lang_code(): - expected_result = "
print(\"test\")\n
" + expected_result = '
print("test")\n
' + assert render(dummy_project, "```python\nprint(\"test\")\n```") == expected_result def test_cache_by_sha(): @cache_by_sha def test_cache(project, text): - return datetime.now() + return datetime.now(pytz.utc) result1 = test_cache(dummy_project, "test") result2 = test_cache(dummy_project, "test2") diff --git a/tests/unit/test_order_updates.py b/tests/unit/test_order_updates.py index 3f9625aa..3609ff01 100644 --- a/tests/unit/test_order_updates.py +++ b/tests/unit/test_order_updates.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_serializer_mixins.py b/tests/unit/test_serializer_mixins.py index cc88552f..d7f8419e 100644 --- a/tests/unit/test_serializer_mixins.py +++ b/tests/unit/test_serializer_mixins.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_slug.py b/tests/unit/test_slug.py index 1b9de84d..f3dab268 100644 --- a/tests/unit/test_slug.py +++ b/tests/unit/test_slug.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_timeline.py b/tests/unit/test_timeline.py index c96d3ee8..91bc7127 100644 --- a/tests/unit/test_timeline.py +++ b/tests/unit/test_timeline.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_tokens.py b/tests/unit/test_tokens.py index 57955827..552cc7c8 100644 --- a/tests/unit/test_tokens.py +++ b/tests/unit/test_tokens.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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 diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 2264e970..0c11354a 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# 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-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 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 diff --git a/tests/utils.py b/tests/utils.py index 2a8a4855..bcc368cf 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# 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 +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# Copyright (C) 2014-2017 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
+ {% block body %}{% endblock %}
\n" +"

Thank you for registering in Taiga

\n" +"

We hope you enjoy it

\n" +"

We built Taiga because we wanted the project management tool " +"that sits open on our computers all day long, to serve as a continued " +"reminder of why we love to collaborate, code and design.

\n" +"

We built it to be beautiful, elegant, simple to use and fun - " +"without forsaking flexibility and power.

\n" +" The taiga Team\n" +"
\n" +"

Thank you for registering in Taiga

\n" +"

We hope you enjoy it

\n" +"

We built Taiga because we wanted the project management tool " +"that sits open on our computers all day long, to serve as a continued " +"reminder of why we love to collaborate, code and design.

\n" +"

We built it to be beautiful, elegant, simple to use and fun - " +"without forsaking flexibility and power.

\n" +" The taiga Team\n" +"
\n" +"

타이가에 가입해주셔서 감사합니다.

\n" +"

우리는 당신이 즐기길 원합니다.

\n" +"

우리는 타이가를 컴퓨터에 하루 종일 열려있는 프로젝트 관리 도구" +"로, 왜 우리가 협업과 코드와 디자인을 사랑하는지 계속해서 다시금 떠올릴 수 있" +"도록 만들었습니다.

\n" +"

우리는 아름답고, 우아하고, 간결하며 재미있게 사용할 수 있도록 " +"만들었습니다. - 유연성과 강력함을 버리지 않고 말이죠.

\n" +" 타이가 팀\n" +"
\n" +"

Thank you for registering in Taiga

\n" +"

We hope you enjoy it

\n" +"

We built Taiga because we wanted the project management tool " +"that sits open on our computers all day long, to serve as a continued " +"reminder of why we love to collaborate, code and design.

\n" +"

We built it to be beautiful, elegant, simple to use and fun - " +"without forsaking flexibility and power.

\n" +" The taiga Team\n" +"
\n" +"

Thank you for registering in Taiga

\n" +"

We hope you enjoy it

\n" +"

We built Taiga because we wanted the project management tool " +"that sits open on our computers all day long, to serve as a continued " +"reminder of why we love to collaborate, code and design.

\n" +"

We built it to be beautiful, elegant, simple to use and fun - " +"without forsaking flexibility and power.

\n" +" The taiga Team\n" +"
-

{{ attr.name }}

-
- {{ _("to") }}
- {{ attr.value|linebreaksbr }} -
+

{{ attr.name }}

+

{{ mdrender(project, attr.value_diff) }}

+

{{ attr.name }}

- {{ _("from") }}
- {{ attr.changes.value.0|linebreaksbr }} -
{{ _("to") }}
- {{ attr.changes.value.1|linebreaksbr }} + {{ attr.value|linebreaksbr }}
+

{{ attr.name }}

+

{{ mdrender(project, attr.value_diff) }}

+
+

{{ attr.name }}

+
+ {{ _("from") }}
+ {{ attr.changes.value.0|linebreaksbr }} +
+ {{ _("to") }}
+ {{ attr.changes.value.1|linebreaksbr }} +