Merge remote-tracking branch 'upstream/master'

remotes/origin/enhancement/email-actions
Chris Wilson 2015-06-09 14:52:48 +01:00
commit 77591b8ad6
52 changed files with 1310 additions and 436 deletions

View File

@ -17,7 +17,6 @@ notifications:
email: email:
recipients: recipients:
- jespinog@gmail.com - jespinog@gmail.com
- andrei.antoukh@gmail.com
- bameda@dbarragan.com - bameda@dbarragan.com
on_success: change on_success: change
on_failure: change on_failure: change

View File

@ -4,6 +4,8 @@
## 1.8.0 ??? (unreleased) ## 1.8.0 ??? (unreleased)
### Features ### Features
- Improve timeline resource.
- Add sitemap of taiga-front (the web client).
- Search by reference (thanks to [@artlepool](https://github.com/artlepool)) - Search by reference (thanks to [@artlepool](https://github.com/artlepool))
- Add call 'by_username' to the API resource User - Add call 'by_username' to the API resource User

View File

@ -257,6 +257,7 @@ INSTALLED_APPS = [
"django.contrib.messages", "django.contrib.messages",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.contrib.sitemaps",
"taiga.base", "taiga.base",
"taiga.base.api", "taiga.base.api",
@ -449,9 +450,16 @@ BITBUCKET_VALID_ORIGIN_IPS = ["131.103.20.165", "131.103.20.166"]
GITLAB_VALID_ORIGIN_IPS = [] GITLAB_VALID_ORIGIN_IPS = []
EXPORTS_TTL = 60 * 60 * 24 # 24 hours EXPORTS_TTL = 60 * 60 * 24 # 24 hours
CELERY_ENABLED = False CELERY_ENABLED = False
WEBHOOKS_ENABLED = False WEBHOOKS_ENABLED = False
# If is True /front/sitemap.xml show a valid sitemap of taiga-front client
FRONT_SITEMAP_ENABLED = False
FRONT_SITEMAP_CACHE_TIMEOUT = 24*60*60 # In second
from .sr import * from .sr import *

View File

@ -62,3 +62,8 @@ DATABASES = {
#GITHUB_API_URL = "https://api.github.com/" #GITHUB_API_URL = "https://api.github.com/"
#GITHUB_API_CLIENT_ID = "yourgithubclientid" #GITHUB_API_CLIENT_ID = "yourgithubclientid"
#GITHUB_API_CLIENT_SECRET = "yourgithubclientsecret" #GITHUB_API_CLIENT_SECRET = "yourgithubclientsecret"
# SITEMAP
# If is True /front/sitemap.xml show a valid sitemap of taiga-front client
#FRONT_SITEMAP_ENABLED = False
#FRONT_SITEMAP_CACHE_TIMEOUT = 24*60*60 # In second

View File

@ -49,6 +49,9 @@ class I18NJsonField(JsonField):
def translate_values(self, d): def translate_values(self, d):
i18n_d = {} i18n_d = {}
if d is None:
return d
for key, value in d.items(): for key, value in d.items():
if isinstance(value, dict): if isinstance(value, dict):
i18n_d[key] = self.translate_values(value) i18n_d[key] = self.translate_values(value)

View File

@ -364,6 +364,26 @@ class TagsFilter(FilterBackend):
return super().filter_queryset(request, queryset, view) return super().filter_queryset(request, queryset, view)
class StatusFilter(FilterBackend):
def __init__(self, filter_name='status'):
self.filter_name = filter_name
def _get_status_queryparams(self, params):
status = params.get(self.filter_name, None)
if status is not None:
status = set([x.strip() for x in status.split(",")])
return list(status)
return None
def filter_queryset(self, request, queryset, view):
query_status = self._get_status_queryparams(request.QUERY_PARAMS)
if query_status:
queryset = queryset.filter(status__in=query_status)
return super().filter_queryset(request, queryset, view)
class QFilter(FilterBackend): class QFilter(FilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
q = request.QUERY_PARAMS.get('q', None) q = request.QUERY_PARAMS.get('q', None)

View File

@ -121,7 +121,7 @@
.headerContent { .headerContent {
text-align: center; text-align: center;
color:#b8b8b8 !important; color:#8D8D8D !important;
font-family: 'Open Sans', Arial, Helvetica; font-family: 'Open Sans', Arial, Helvetica;
font-size:14px; font-size:14px;
margin-bottom:16px; margin-bottom:16px;

View File

@ -60,7 +60,7 @@
* @style heading 2 * @style heading 2
*/ */
h2{ h2{
color: #b8b8b8 !important; color: #8D8D8D !important;
display:block; display:block;
font-family: 'Open Sans', Arial; font-family: 'Open Sans', Arial;
font-size:20px; font-size:20px;

View File

@ -121,7 +121,7 @@
.headerContent { .headerContent {
text-align: center; text-align: center;
color:#b8b8b8 !important; color:#8D8D8D !important;
font-family: 'Open Sans', Arial, Helvetica; font-family: 'Open Sans', Arial, Helvetica;
font-size:14px; font-size:14px;
margin-bottom:16px; margin-bottom:16px;
@ -418,14 +418,14 @@
</tr> </tr>
{% for entry in history_entries%} {% for entry in history_entries%}
{% if entry.comment %} {% if entry.comment %}
<tr> <tr>
<td colspan="2"> <td colspan="2">
{% trans comment=mdrender(project, entry.comment) %} {% trans comment=mdrender(project, entry.comment) %}
<h3>comment:</h3> <h3>comment:</h3>
<p>{{ comment }}</p> <p>{{ comment }}</p>
{% endtrans %} {% endtrans %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% set changed_fields = entry.values_diff %} {% set changed_fields = entry.values_diff %}
{% if changed_fields %} {% if changed_fields %}

View File

@ -1,52 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django_jinja import library
from django_sites import get_by_id as get_site_by_id
urls = {
"home": "/",
"login": "/login",
"change-password": "/change-password/{0}",
"change-email": "/change-email/{0}",
"cancel-account": "/cancel-account/{0}",
"invitation": "/invitation/{0}",
"project": "/project/{0}",
"backlog": "/project/{0}/backlog/",
"taskboard": "/project/{0}/taskboard/{1}",
"kanban": "/project/{0}/kanban/",
"userstory": "/project/{0}/us/{1}",
"task": "/project/{0}/task/{1}",
"issues": "/project/{0}/issues",
"issue": "/project/{0}/issue/{1}",
"wiki": "/project/{0}/wiki/{1}",
"project-admin": "/project/{0}/admin/project-profile/details",
}
@library.global_function(name="resolve_front_url")
def resolve(type, *args):
site = get_site_by_id("front")
url_tmpl = "{scheme}//{domain}{url}"
scheme = site.scheme and "{0}:".format(site.scheme) or ""
url = urls[type].format(*args)
return url_tmpl.format(scheme=scheme, domain=site.domain, url=url)

View File

@ -0,0 +1,60 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import OrderedDict
from .generics import GenericSitemap
from .projects import ProjectsSitemap
from .projects import ProjectBacklogsSitemap
from .projects import ProjectKanbansSitemap
from .projects import ProjectIssuesSitemap
from .projects import ProjectTeamsSitemap
from .milestones import MilestonesSitemap
from .userstories import UserStoriesSitemap
from .tasks import TasksSitemap
from .issues import IssuesSitemap
from .wiki import WikiPagesSitemap
from .users import UsersSitemap
sitemaps = OrderedDict([
("generics", GenericSitemap),
("projects", ProjectsSitemap),
("project-backlogs", ProjectBacklogsSitemap),
("project-kanbans", ProjectKanbansSitemap),
("project-issues-list", ProjectIssuesSitemap),
("project-teams", ProjectTeamsSitemap),
("milestones", MilestonesSitemap),
("userstories", UserStoriesSitemap),
("tasks", TasksSitemap),
("issues", IssuesSitemap),
("wikipages", WikiPagesSitemap),
("users", UsersSitemap)
])

View File

@ -0,0 +1,45 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.sitemaps import Sitemap as DjangoSitemap
class Sitemap(DjangoSitemap):
def get_urls(self, page=1, site=None, protocol=None):
urls = []
latest_lastmod = None
all_items_lastmod = True # track if all items have a lastmod
for item in self.paginator.page(page).object_list:
loc = self.__get('location', item)
priority = self.__get('priority', item, None)
lastmod = self.__get('lastmod', item, None)
if all_items_lastmod:
all_items_lastmod = lastmod is not None
if (all_items_lastmod and
(latest_lastmod is None or lastmod > latest_lastmod)):
latest_lastmod = lastmod
url_info = {
'item': item,
'location': loc,
'lastmod': lastmod,
'changefreq': self.__get('changefreq', item, None),
'priority': str(priority if priority is not None else ''),
}
urls.append(url_info)
if all_items_lastmod and latest_lastmod:
self.latest_lastmod = latest_lastmod
return urls

View File

@ -0,0 +1,42 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class GenericSitemap(Sitemap):
def items(self):
return [
{"url_key": "home", "changefreq": "monthly", "priority": 0.6},
{"url_key": "login", "changefreq": "monthly", "priority": 0.6},
{"url_key": "register", "changefreq": "monthly", "priority": 0.6},
{"url_key": "forgot-password", "changefreq": "monthly", "priority": 0.6}
]
def location(self, obj):
return resolve(obj["url_key"])
def changefreq(self, obj):
return obj.get("changefreq", None)
def priority(self, obj):
return obj.get("priority", None)

View File

@ -0,0 +1,49 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class IssuesSitemap(Sitemap):
def items(self):
issue_model = apps.get_model("issues", "Issue")
# Get issues of public projects OR private projects if anon user can view them
queryset = issue_model.objects.filter(Q(project__is_private=False) |
Q(project__is_private=True,
project__anon_permissions__contains=["view_issues"]))
# Project data is needed
queryset = queryset.select_related("project")
return queryset
def location(self, obj):
return resolve("issue", obj.project.slug, obj.ref)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6

View File

@ -0,0 +1,51 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class MilestonesSitemap(Sitemap):
def items(self):
milestone_model = apps.get_model("milestones", "Milestone")
# Get US of public projects OR private projects if anon user can view them and us and tasks
queryset = milestone_model.objects.filter(Q(project__is_private=False) |
Q(project__is_private=True,
project__anon_permissions__contains=["view_milestones",
"view_us",
"view_tasks"]))
# Project data is needed
queryset = queryset.select_related("project")
return queryset
def location(self, obj):
return resolve("taskboard", obj.project.slug, obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6

View File

@ -0,0 +1,154 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class ProjectsSitemap(Sitemap):
def items(self):
project_model = apps.get_model("projects", "Project")
# Get public projects OR private projects if anon user can view them
queryset = project_model.objects.filter(Q(is_private=False) |
Q(is_private=True,
anon_permissions__contains=["view_project"]))
return queryset
def location(self, obj):
return resolve("project", obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6
class ProjectBacklogsSitemap(Sitemap):
def items(self):
project_model = apps.get_model("projects", "Project")
# Get public projects OR private projects if anon user can view them and user stories
queryset = project_model.objects.filter(Q(is_private=False) |
Q(is_private=True,
anon_permissions__contains=["view_project",
"view_us"]))
# Exclude projects without backlog enabled
queryset = queryset.exclude(is_backlog_activated=False)
return queryset
def location(self, obj):
return resolve("backlog", obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6
class ProjectKanbansSitemap(Sitemap):
def items(self):
project_model = apps.get_model("projects", "Project")
# Get public projects OR private projects if anon user can view them and user stories
queryset = project_model.objects.filter(Q(is_private=False) |
Q(is_private=True,
anon_permissions__contains=["view_project",
"view_us"]))
# Exclude projects without kanban enabled
queryset = queryset.exclude(is_kanban_activated=False)
return queryset
def location(self, obj):
return resolve("kanban", obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6
class ProjectIssuesSitemap(Sitemap):
def items(self):
project_model = apps.get_model("projects", "Project")
# Get public projects OR private projects if anon user can view them and issues
queryset = project_model.objects.filter(Q(is_private=False) |
Q(is_private=True,
anon_permissions__contains=["view_project",
"view_issues"]))
# Exclude projects without issues enabled
queryset = queryset.exclude(is_issues_activated=False)
return queryset
def location(self, obj):
return resolve("issues", obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6
class ProjectTeamsSitemap(Sitemap):
def items(self):
project_model = apps.get_model("projects", "Project")
# Get public projects OR private projects if anon user can view them
queryset = project_model.objects.filter(Q(is_private=False) |
Q(is_private=True,
anon_permissions__contains=["view_project"]))
return queryset
def location(self, obj):
return resolve("team", obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6

View File

@ -0,0 +1,49 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class TasksSitemap(Sitemap):
def items(self):
task_model = apps.get_model("tasks", "Task")
# Get tasks of public projects OR private projects if anon user can view them
queryset = task_model.objects.filter(Q(project__is_private=False) |
Q(project__is_private=True,
project__anon_permissions__contains=["view_tasks"]))
# Project data is needed
queryset = queryset.select_related("project")
return queryset
def location(self, obj):
return resolve("task", obj.project.slug, obj.ref)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.4

View File

@ -0,0 +1,44 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class UsersSitemap(Sitemap):
def items(self):
user_model = apps.get_model("users", "User")
# Only active users and not system users
queryset = user_model.objects.filter(is_active=True,
is_system=False)
return queryset
def location(self, obj):
return resolve("user", obj.username)
def lastmod(self, obj):
return None
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6

View File

@ -0,0 +1,49 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class UserStoriesSitemap(Sitemap):
def items(self):
us_model = apps.get_model("userstories", "UserStory")
# Get US of public projects OR private projects if anon user can view them
queryset = us_model.objects.filter(Q(project__is_private=False) |
Q(project__is_private=True,
project__anon_permissions__contains=["view_us"]))
# Project data is needed
queryset = queryset.select_related("project")
return queryset
def location(self, obj):
return resolve("userstory", obj.project.slug, obj.ref)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6

View File

@ -0,0 +1,52 @@
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Taiga Agile LLC <support@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import Q
from django.apps import apps
from taiga.front.templatetags.functions import resolve
from .base import Sitemap
class WikiPagesSitemap(Sitemap):
def items(self):
wiki_page_model = apps.get_model("wiki", "WikiPage")
# Get wiki pages of public projects OR private projects if anon user can view them
queryset = wiki_page_model.objects.filter(Q(project__is_private=False) |
Q(project__is_private=True,
project__anon_permissions__contains=["view_wiki_pages"]))
# Exclude wiki pages from projects without wiki section enabled
queryset = queryset.exclude(project__is_wiki_activated=False)
# Project data is needed
queryset = queryset.select_related("project")
return queryset
def location(self, obj):
return resolve("wiki", obj.project.slug, obj.slug)
def lastmod(self, obj):
return obj.modified_date
def changefreq(self, obj):
return "daily"
def priority(self, obj):
return 0.6

View File

View File

@ -0,0 +1,34 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django_jinja import library
from django_sites import get_by_id as get_site_by_id
from taiga.front.urls import urls
register = library.Library()
@register.global_function(name="resolve_front_url")
def resolve(type, *args):
site = get_site_by_id("front")
url_tmpl = "{scheme}//{domain}{url}"
scheme = site.scheme and "{0}:".format(site.scheme) or ""
url = urls[type].format(*args)
return url_tmpl.format(scheme=scheme, domain=site.domain, url=url)

49
taiga/front/urls.py Normal file
View File

@ -0,0 +1,49 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
urls = {
"home": "/",
"login": "/login",
"register": "/register",
"forgot-password": "/forgot-password",
"change-password": "/change-password/{0}", # user.token
"change-email": "/change-email/{0}", # user.email_token
"cancel-account": "/cancel-account/{0}", # auth.token.get_token_for_user(user)
"invitation": "/invitation/{0}", # membership.token
"user": "/profile/{0}", # user.username
"project": "/project/{0}", # project.slug
"backlog": "/project/{0}/backlog/", # project.slug
"taskboard": "/project/{0}/taskboard/{1}", # project.slug, milestone.slug
"kanban": "/project/{0}/kanban/", # project.slug
"userstory": "/project/{0}/us/{1}", # project.slug, us.ref
"task": "/project/{0}/task/{1}", # project.slug, task.ref
"issues": "/project/{0}/issues", # project.slug
"issue": "/project/{0}/issue/{1}", # project.slug, issue.ref
"wiki": "/project/{0}/wiki/{1}", # project.slug, wikipage.slug
"team": "/project/{0}/team/", # project.slug
"project-admin": "/project/{0}/admin/project-profile/details", # project.slug
}

View File

@ -9,8 +9,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: taiga-back\n" "Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-25 17:31+0200\n" "POT-Creation-Date: 2015-06-09 09:47+0200\n"
"PO-Revision-Date: 2015-05-25 13:58+0000\n" "PO-Revision-Date: 2015-06-09 07:47+0000\n"
"Last-Translator: Taiga Dev Team <support@taiga.io>\n" "Last-Translator: Taiga Dev Team <support@taiga.io>\n"
"Language-Team: Catalan (http://www.transifex.com/projects/p/taiga-back/" "Language-Team: Catalan (http://www.transifex.com/projects/p/taiga-back/"
"language/ca/)\n" "language/ca/)\n"
@ -435,16 +435,12 @@ msgstr "Actualitzacions"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" <h3>comment:" " <h3>comment:"
"</h3>\n" "</h3>\n"
" <p>" " <p>"
"%(comment)s</p>\n" "%(comment)s</p>\n"
" " " "
msgstr "" msgstr ""
"\n"
"<h3>Comentari:</h3>\n"
" <p>%(comment)s</p>\n"
" "
#: taiga/base/templates/emails/updates-body-text.jinja:6 #: taiga/base/templates/emails/updates-body-text.jinja:6
#, python-format #, python-format
@ -1082,7 +1078,7 @@ msgstr "Amo"
#: taiga/projects/models.py:507 taiga/projects/models.py:538 #: taiga/projects/models.py:507 taiga/projects/models.py:538
#: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41
#: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28 #: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28
#: taiga/projects/wiki/models.py:66 taiga/users/models.py:193 #: taiga/projects/wiki/models.py:66 taiga/users/models.py:196
msgid "project" msgid "project"
msgstr "Projecte" msgstr "Projecte"
@ -1126,7 +1122,7 @@ msgstr "Descripció"
#: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:391 taiga/projects/models.py:418
#: taiga/projects/models.py:453 taiga/projects/models.py:476 #: taiga/projects/models.py:453 taiga/projects/models.py:476
#: taiga/projects/models.py:501 taiga/projects/models.py:534 #: taiga/projects/models.py:501 taiga/projects/models.py:534
#: taiga/projects/wiki/models.py:71 taiga/users/models.py:188 #: taiga/projects/wiki/models.py:71 taiga/users/models.py:191
msgid "order" msgid "order"
msgstr "Ordre" msgstr "Ordre"
@ -1148,7 +1144,7 @@ msgstr ""
#: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:414 taiga/projects/models.py:451
#: taiga/projects/models.py:474 taiga/projects/models.py:497 #: taiga/projects/models.py:474 taiga/projects/models.py:497
#: taiga/projects/models.py:532 taiga/projects/models.py:555 #: taiga/projects/models.py:532 taiga/projects/models.py:555
#: taiga/users/models.py:180 taiga/webhooks/models.py:27 #: taiga/users/models.py:183 taiga/webhooks/models.py:27
msgid "name" msgid "name"
msgstr "Nom" msgstr "Nom"
@ -1371,7 +1367,7 @@ msgstr "referència externa"
#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125 #: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125
#: taiga/projects/models.py:352 taiga/projects/models.py:416 #: taiga/projects/models.py:352 taiga/projects/models.py:416
#: taiga/projects/models.py:499 taiga/projects/models.py:557 #: taiga/projects/models.py:499 taiga/projects/models.py:557
#: taiga/projects/wiki/models.py:30 taiga/users/models.py:182 #: taiga/projects/wiki/models.py:30 taiga/users/models.py:185
msgid "slug" msgid "slug"
msgstr "slug" msgstr "slug"
@ -1421,7 +1417,7 @@ msgstr "email"
msgid "create at" msgid "create at"
msgstr "" msgstr ""
#: taiga/projects/models.py:63 taiga/users/models.py:126 #: taiga/projects/models.py:63 taiga/users/models.py:128
msgid "token" msgid "token"
msgstr "token" msgstr "token"
@ -2107,15 +2103,15 @@ msgstr ""
msgid "The version must be an integer" msgid "The version must be an integer"
msgstr "" msgstr ""
#: taiga/projects/occ/mixins.py:56 #: taiga/projects/occ/mixins.py:58
msgid "The version is not valid" msgid "The version parameter is not valid"
msgstr "" msgstr ""
#: taiga/projects/occ/mixins.py:72 #: taiga/projects/occ/mixins.py:74
msgid "The version doesn't match with the current one" msgid "The version doesn't match with the current one"
msgstr "" msgstr ""
#: taiga/projects/occ/mixins.py:92 #: taiga/projects/occ/mixins.py:93
msgid "version" msgid "version"
msgstr "Versió" msgstr "Versió"
@ -2131,43 +2127,43 @@ msgstr "Aquest e-mail ja està en ús"
msgid "Invalid role for the project" msgid "Invalid role for the project"
msgstr "Rol invàlid per al projecte" msgstr "Rol invàlid per al projecte"
#: taiga/projects/serializers.py:343 #: taiga/projects/serializers.py:340
msgid "Total milestones must be major or equal to zero" msgid "Total milestones must be major or equal to zero"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:400 #: taiga/projects/serializers.py:402
msgid "Default options" msgid "Default options"
msgstr "Opcions per defecte" msgstr "Opcions per defecte"
#: taiga/projects/serializers.py:401 #: taiga/projects/serializers.py:403
msgid "User story's statuses" msgid "User story's statuses"
msgstr "Estatus d'històries d'usuari" msgstr "Estatus d'històries d'usuari"
#: taiga/projects/serializers.py:402 #: taiga/projects/serializers.py:404
msgid "Points" msgid "Points"
msgstr "Punts" msgstr "Punts"
#: taiga/projects/serializers.py:403 #: taiga/projects/serializers.py:405
msgid "Task's statuses" msgid "Task's statuses"
msgstr "Estatus de tasques" msgstr "Estatus de tasques"
#: taiga/projects/serializers.py:404 #: taiga/projects/serializers.py:406
msgid "Issue's statuses" msgid "Issue's statuses"
msgstr "Estatus d'incidéncies" msgstr "Estatus d'incidéncies"
#: taiga/projects/serializers.py:405 #: taiga/projects/serializers.py:407
msgid "Issue's types" msgid "Issue's types"
msgstr "Tipus d'incidéncies" msgstr "Tipus d'incidéncies"
#: taiga/projects/serializers.py:406 #: taiga/projects/serializers.py:408
msgid "Priorities" msgid "Priorities"
msgstr "Prioritats" msgstr "Prioritats"
#: taiga/projects/serializers.py:407 #: taiga/projects/serializers.py:409
msgid "Severities" msgid "Severities"
msgstr "Severitats" msgstr "Severitats"
#: taiga/projects/serializers.py:408 #: taiga/projects/serializers.py:410
msgid "Roles" msgid "Roles"
msgstr "Rols" msgstr "Rols"
@ -2653,57 +2649,57 @@ msgstr "Permissos"
msgid "Important dates" msgid "Important dates"
msgstr "Dates importants" msgstr "Dates importants"
#: taiga/users/api.py:112 taiga/users/api.py:119 #: taiga/users/api.py:124 taiga/users/api.py:131
msgid "Invalid username or email" msgid "Invalid username or email"
msgstr "Nom d'usuari o email invàlid" msgstr "Nom d'usuari o email invàlid"
#: taiga/users/api.py:128 #: taiga/users/api.py:140
msgid "Mail sended successful!" msgid "Mail sended successful!"
msgstr "Correu enviat satisfactòriament" msgstr "Correu enviat satisfactòriament"
#: taiga/users/api.py:140 taiga/users/api.py:145 #: taiga/users/api.py:152 taiga/users/api.py:157
msgid "Token is invalid" msgid "Token is invalid"
msgstr "Token invàlid" msgstr "Token invàlid"
#: taiga/users/api.py:166 #: taiga/users/api.py:178
msgid "Current password parameter needed" msgid "Current password parameter needed"
msgstr "Paràmetre de password actual requerit" msgstr "Paràmetre de password actual requerit"
#: taiga/users/api.py:169 #: taiga/users/api.py:181
msgid "New password parameter needed" msgid "New password parameter needed"
msgstr "Paràmetre de password requerit" msgstr "Paràmetre de password requerit"
#: taiga/users/api.py:172 #: taiga/users/api.py:184
msgid "Invalid password length at least 6 charaters needed" msgid "Invalid password length at least 6 charaters needed"
msgstr "Password invàlid, al menys 6 caràcters requerits" msgstr "Password invàlid, al menys 6 caràcters requerits"
#: taiga/users/api.py:175 #: taiga/users/api.py:187
msgid "Invalid current password" msgid "Invalid current password"
msgstr "Password actual invàlid" msgstr "Password actual invàlid"
#: taiga/users/api.py:191 #: taiga/users/api.py:203
msgid "Incomplete arguments" msgid "Incomplete arguments"
msgstr "Arguments incomplets." msgstr "Arguments incomplets."
#: taiga/users/api.py:196 #: taiga/users/api.py:208
msgid "Invalid image format" msgid "Invalid image format"
msgstr "Format d'image invàlid" msgstr "Format d'image invàlid"
#: taiga/users/api.py:249 #: taiga/users/api.py:261
msgid "Duplicated email" msgid "Duplicated email"
msgstr "Email duplicat" msgstr "Email duplicat"
#: taiga/users/api.py:251 #: taiga/users/api.py:263
msgid "Not valid email" msgid "Not valid email"
msgstr "Email no vàlid" msgstr "Email no vàlid"
#: taiga/users/api.py:271 taiga/users/api.py:277 #: taiga/users/api.py:283 taiga/users/api.py:289
msgid "" msgid ""
"Invalid, are you sure the token is correct and you didn't use it before?" "Invalid, are you sure the token is correct and you didn't use it before?"
msgstr "" msgstr ""
"Invàlid. Estás segur que el token es correcte i que no l'has usat abans?" "Invàlid. Estás segur que el token es correcte i que no l'has usat abans?"
#: taiga/users/api.py:304 taiga/users/api.py:312 taiga/users/api.py:315 #: taiga/users/api.py:316 taiga/users/api.py:324 taiga/users/api.py:327
msgid "Invalid, are you sure the token is correct?" msgid "Invalid, are you sure the token is correct?"
msgstr "Invàlid. Estás segur que el token es correcte?" msgstr "Invàlid. Estás segur que el token es correcte?"
@ -2761,22 +2757,26 @@ msgid "default language"
msgstr "llenguatge per defecte" msgstr "llenguatge per defecte"
#: taiga/users/models.py:122 #: taiga/users/models.py:122
msgid "default theme"
msgstr ""
#: taiga/users/models.py:124
msgid "default timezone" msgid "default timezone"
msgstr "zona horaria per defecte" msgstr "zona horaria per defecte"
#: taiga/users/models.py:124 #: taiga/users/models.py:126
msgid "colorize tags" msgid "colorize tags"
msgstr "coloritza tags" msgstr "coloritza tags"
#: taiga/users/models.py:129 #: taiga/users/models.py:131
msgid "email token" msgid "email token"
msgstr "token de correu" msgstr "token de correu"
#: taiga/users/models.py:131 #: taiga/users/models.py:133
msgid "new email address" msgid "new email address"
msgstr "nova adreça de correu" msgstr "nova adreça de correu"
#: taiga/users/models.py:185 #: taiga/users/models.py:188
msgid "permissions" msgid "permissions"
msgstr "permissos" msgstr "permissos"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: taiga-back\n" "Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-25 17:31+0200\n" "POT-Creation-Date: 2015-06-09 09:47+0200\n"
"PO-Revision-Date: 2015-03-25 20:09+0100\n" "PO-Revision-Date: 2015-03-25 20:09+0100\n"
"Last-Translator: Taiga Dev Team <support@taiga.io>\n" "Last-Translator: Taiga Dev Team <support@taiga.io>\n"
"Language-Team: Taiga Dev Team <support@taiga.io>\n" "Language-Team: Taiga Dev Team <support@taiga.io>\n"
@ -427,11 +427,11 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" <h3>comment:" " <h3>comment:"
"</h3>\n" "</h3>\n"
" <p>" " <p>"
"%(comment)s</p>\n" "%(comment)s</p>\n"
" " " "
msgstr "" msgstr ""
#: taiga/base/templates/emails/updates-body-text.jinja:6 #: taiga/base/templates/emails/updates-body-text.jinja:6
@ -1051,7 +1051,7 @@ msgstr ""
#: taiga/projects/models.py:507 taiga/projects/models.py:538 #: taiga/projects/models.py:507 taiga/projects/models.py:538
#: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41
#: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28 #: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28
#: taiga/projects/wiki/models.py:66 taiga/users/models.py:193 #: taiga/projects/wiki/models.py:66 taiga/users/models.py:196
msgid "project" msgid "project"
msgstr "" msgstr ""
@ -1095,7 +1095,7 @@ msgstr ""
#: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:391 taiga/projects/models.py:418
#: taiga/projects/models.py:453 taiga/projects/models.py:476 #: taiga/projects/models.py:453 taiga/projects/models.py:476
#: taiga/projects/models.py:501 taiga/projects/models.py:534 #: taiga/projects/models.py:501 taiga/projects/models.py:534
#: taiga/projects/wiki/models.py:71 taiga/users/models.py:188 #: taiga/projects/wiki/models.py:71 taiga/users/models.py:191
msgid "order" msgid "order"
msgstr "" msgstr ""
@ -1117,7 +1117,7 @@ msgstr ""
#: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:414 taiga/projects/models.py:451
#: taiga/projects/models.py:474 taiga/projects/models.py:497 #: taiga/projects/models.py:474 taiga/projects/models.py:497
#: taiga/projects/models.py:532 taiga/projects/models.py:555 #: taiga/projects/models.py:532 taiga/projects/models.py:555
#: taiga/users/models.py:180 taiga/webhooks/models.py:27 #: taiga/users/models.py:183 taiga/webhooks/models.py:27
msgid "name" msgid "name"
msgstr "" msgstr ""
@ -1340,7 +1340,7 @@ msgstr ""
#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125 #: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125
#: taiga/projects/models.py:352 taiga/projects/models.py:416 #: taiga/projects/models.py:352 taiga/projects/models.py:416
#: taiga/projects/models.py:499 taiga/projects/models.py:557 #: taiga/projects/models.py:499 taiga/projects/models.py:557
#: taiga/projects/wiki/models.py:30 taiga/users/models.py:182 #: taiga/projects/wiki/models.py:30 taiga/users/models.py:185
msgid "slug" msgid "slug"
msgstr "" msgstr ""
@ -1390,7 +1390,7 @@ msgstr ""
msgid "create at" msgid "create at"
msgstr "" msgstr ""
#: taiga/projects/models.py:63 taiga/users/models.py:126 #: taiga/projects/models.py:63 taiga/users/models.py:128
msgid "token" msgid "token"
msgstr "" msgstr ""
@ -2070,15 +2070,15 @@ msgstr ""
msgid "The version must be an integer" msgid "The version must be an integer"
msgstr "" msgstr ""
#: taiga/projects/occ/mixins.py:56 #: taiga/projects/occ/mixins.py:58
msgid "The version is not valid" msgid "The version parameter is not valid"
msgstr "" msgstr ""
#: taiga/projects/occ/mixins.py:72 #: taiga/projects/occ/mixins.py:74
msgid "The version doesn't match with the current one" msgid "The version doesn't match with the current one"
msgstr "" msgstr ""
#: taiga/projects/occ/mixins.py:92 #: taiga/projects/occ/mixins.py:93
msgid "version" msgid "version"
msgstr "" msgstr ""
@ -2094,43 +2094,43 @@ msgstr ""
msgid "Invalid role for the project" msgid "Invalid role for the project"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:343 #: taiga/projects/serializers.py:340
msgid "Total milestones must be major or equal to zero" msgid "Total milestones must be major or equal to zero"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:400 #: taiga/projects/serializers.py:402
msgid "Default options" msgid "Default options"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:401 #: taiga/projects/serializers.py:403
msgid "User story's statuses" msgid "User story's statuses"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:402 #: taiga/projects/serializers.py:404
msgid "Points" msgid "Points"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:403 #: taiga/projects/serializers.py:405
msgid "Task's statuses" msgid "Task's statuses"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:404 #: taiga/projects/serializers.py:406
msgid "Issue's statuses" msgid "Issue's statuses"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:405 #: taiga/projects/serializers.py:407
msgid "Issue's types" msgid "Issue's types"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:406 #: taiga/projects/serializers.py:408
msgid "Priorities" msgid "Priorities"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:407 #: taiga/projects/serializers.py:409
msgid "Severities" msgid "Severities"
msgstr "" msgstr ""
#: taiga/projects/serializers.py:408 #: taiga/projects/serializers.py:410
msgid "Roles" msgid "Roles"
msgstr "" msgstr ""
@ -2596,56 +2596,56 @@ msgstr ""
msgid "Important dates" msgid "Important dates"
msgstr "" msgstr ""
#: taiga/users/api.py:112 taiga/users/api.py:119 #: taiga/users/api.py:124 taiga/users/api.py:131
msgid "Invalid username or email" msgid "Invalid username or email"
msgstr "" msgstr ""
#: taiga/users/api.py:128 #: taiga/users/api.py:140
msgid "Mail sended successful!" msgid "Mail sended successful!"
msgstr "" msgstr ""
#: taiga/users/api.py:140 taiga/users/api.py:145 #: taiga/users/api.py:152 taiga/users/api.py:157
msgid "Token is invalid" msgid "Token is invalid"
msgstr "" msgstr ""
#: taiga/users/api.py:166 #: taiga/users/api.py:178
msgid "Current password parameter needed" msgid "Current password parameter needed"
msgstr "" msgstr ""
#: taiga/users/api.py:169 #: taiga/users/api.py:181
msgid "New password parameter needed" msgid "New password parameter needed"
msgstr "" msgstr ""
#: taiga/users/api.py:172 #: taiga/users/api.py:184
msgid "Invalid password length at least 6 charaters needed" msgid "Invalid password length at least 6 charaters needed"
msgstr "" msgstr ""
#: taiga/users/api.py:175 #: taiga/users/api.py:187
msgid "Invalid current password" msgid "Invalid current password"
msgstr "" msgstr ""
#: taiga/users/api.py:191 #: taiga/users/api.py:203
msgid "Incomplete arguments" msgid "Incomplete arguments"
msgstr "" msgstr ""
#: taiga/users/api.py:196 #: taiga/users/api.py:208
msgid "Invalid image format" msgid "Invalid image format"
msgstr "" msgstr ""
#: taiga/users/api.py:249 #: taiga/users/api.py:261
msgid "Duplicated email" msgid "Duplicated email"
msgstr "" msgstr ""
#: taiga/users/api.py:251 #: taiga/users/api.py:263
msgid "Not valid email" msgid "Not valid email"
msgstr "" msgstr ""
#: taiga/users/api.py:271 taiga/users/api.py:277 #: taiga/users/api.py:283 taiga/users/api.py:289
msgid "" msgid ""
"Invalid, are you sure the token is correct and you didn't use it before?" "Invalid, are you sure the token is correct and you didn't use it before?"
msgstr "" msgstr ""
#: taiga/users/api.py:304 taiga/users/api.py:312 taiga/users/api.py:315 #: taiga/users/api.py:316 taiga/users/api.py:324 taiga/users/api.py:327
msgid "Invalid, are you sure the token is correct?" msgid "Invalid, are you sure the token is correct?"
msgstr "" msgstr ""
@ -2699,22 +2699,26 @@ msgid "default language"
msgstr "" msgstr ""
#: taiga/users/models.py:122 #: taiga/users/models.py:122
msgid "default timezone" msgid "default theme"
msgstr "" msgstr ""
#: taiga/users/models.py:124 #: taiga/users/models.py:124
msgid "default timezone"
msgstr ""
#: taiga/users/models.py:126
msgid "colorize tags" msgid "colorize tags"
msgstr "" msgstr ""
#: taiga/users/models.py:129 #: taiga/users/models.py:131
msgid "email token" msgid "email token"
msgstr "" msgstr ""
#: taiga/users/models.py:131 #: taiga/users/models.py:133
msgid "new email address" msgid "new email address"
msgstr "" msgstr ""
#: taiga/users/models.py:185 #: taiga/users/models.py:188
msgid "permissions" msgid "permissions"
msgstr "" msgstr ""

View File

@ -12,9 +12,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: taiga-back\n" "Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-25 17:31+0200\n" "POT-Creation-Date: 2015-06-09 09:47+0200\n"
"PO-Revision-Date: 2015-05-25 15:50+0000\n" "PO-Revision-Date: 2015-06-09 07:47+0000\n"
"Last-Translator: David Barragán <bameda@gmail.com>\n" "Last-Translator: Taiga Dev Team <support@taiga.io>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/taiga-back/" "Language-Team: Spanish (http://www.transifex.com/projects/p/taiga-back/"
"language/es/)\n" "language/es/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -471,15 +471,12 @@ msgstr "Actualizaciones"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" <h3>comment:" " <h3>comment:"
"</h3>\n" "</h3>\n"
" <p>" " <p>"
"%(comment)s</p>\n" "%(comment)s</p>\n"
" " " "
msgstr "" msgstr ""
"\n"
"<h3>comentario:</h3>\n"
"<p>%(comment)s</p>"
#: taiga/base/templates/emails/updates-body-text.jinja:6 #: taiga/base/templates/emails/updates-body-text.jinja:6
#, python-format #, python-format
@ -1220,7 +1217,7 @@ msgstr "Dueño"
#: taiga/projects/models.py:507 taiga/projects/models.py:538 #: taiga/projects/models.py:507 taiga/projects/models.py:538
#: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41
#: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28 #: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28
#: taiga/projects/wiki/models.py:66 taiga/users/models.py:193 #: taiga/projects/wiki/models.py:66 taiga/users/models.py:196
msgid "project" msgid "project"
msgstr "Proyecto" msgstr "Proyecto"
@ -1264,7 +1261,7 @@ msgstr "descripción"
#: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:391 taiga/projects/models.py:418
#: taiga/projects/models.py:453 taiga/projects/models.py:476 #: taiga/projects/models.py:453 taiga/projects/models.py:476
#: taiga/projects/models.py:501 taiga/projects/models.py:534 #: taiga/projects/models.py:501 taiga/projects/models.py:534
#: taiga/projects/wiki/models.py:71 taiga/users/models.py:188 #: taiga/projects/wiki/models.py:71 taiga/users/models.py:191
msgid "order" msgid "order"
msgstr "orden" msgstr "orden"
@ -1286,7 +1283,7 @@ msgstr "Talky"
#: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:414 taiga/projects/models.py:451
#: taiga/projects/models.py:474 taiga/projects/models.py:497 #: taiga/projects/models.py:474 taiga/projects/models.py:497
#: taiga/projects/models.py:532 taiga/projects/models.py:555 #: taiga/projects/models.py:532 taiga/projects/models.py:555
#: taiga/users/models.py:180 taiga/webhooks/models.py:27 #: taiga/users/models.py:183 taiga/webhooks/models.py:27
msgid "name" msgid "name"
msgstr "nombre" msgstr "nombre"
@ -1509,7 +1506,7 @@ msgstr "referencia externa"
#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125 #: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125
#: taiga/projects/models.py:352 taiga/projects/models.py:416 #: taiga/projects/models.py:352 taiga/projects/models.py:416
#: taiga/projects/models.py:499 taiga/projects/models.py:557 #: taiga/projects/models.py:499 taiga/projects/models.py:557
#: taiga/projects/wiki/models.py:30 taiga/users/models.py:182 #: taiga/projects/wiki/models.py:30 taiga/users/models.py:185
msgid "slug" msgid "slug"
msgstr "slug" msgstr "slug"
@ -1561,7 +1558,7 @@ msgstr "email"
msgid "create at" msgid "create at"
msgstr "creado el" msgstr "creado el"
#: taiga/projects/models.py:63 taiga/users/models.py:126 #: taiga/projects/models.py:63 taiga/users/models.py:128
msgid "token" msgid "token"
msgstr "token" msgstr "token"
@ -2478,15 +2475,15 @@ msgstr "Los observadores tienen usuarios invalidos"
msgid "The version must be an integer" msgid "The version must be an integer"
msgstr "La versión debe ser un número entero" msgstr "La versión debe ser un número entero"
#: taiga/projects/occ/mixins.py:56 #: taiga/projects/occ/mixins.py:58
msgid "The version is not valid" msgid "The version parameter is not valid"
msgstr "La versión no es válida" msgstr ""
#: taiga/projects/occ/mixins.py:72 #: taiga/projects/occ/mixins.py:74
msgid "The version doesn't match with the current one" msgid "The version doesn't match with the current one"
msgstr "Las version difiere de la actual" msgstr "Las version difiere de la actual"
#: taiga/projects/occ/mixins.py:92 #: taiga/projects/occ/mixins.py:93
msgid "version" msgid "version"
msgstr "versión" msgstr "versión"
@ -2503,43 +2500,43 @@ msgstr "La dirección de email ya está en uso."
msgid "Invalid role for the project" msgid "Invalid role for the project"
msgstr "Rol inválido para el proyecto" msgstr "Rol inválido para el proyecto"
#: taiga/projects/serializers.py:343 #: taiga/projects/serializers.py:340
msgid "Total milestones must be major or equal to zero" msgid "Total milestones must be major or equal to zero"
msgstr "El número total de sprints debe ser mayor o igual a cero" msgstr "El número total de sprints debe ser mayor o igual a cero"
#: taiga/projects/serializers.py:400 #: taiga/projects/serializers.py:402
msgid "Default options" msgid "Default options"
msgstr "Opciones por defecto" msgstr "Opciones por defecto"
#: taiga/projects/serializers.py:401 #: taiga/projects/serializers.py:403
msgid "User story's statuses" msgid "User story's statuses"
msgstr "Estados de historia de usuario" msgstr "Estados de historia de usuario"
#: taiga/projects/serializers.py:402 #: taiga/projects/serializers.py:404
msgid "Points" msgid "Points"
msgstr "Puntos" msgstr "Puntos"
#: taiga/projects/serializers.py:403 #: taiga/projects/serializers.py:405
msgid "Task's statuses" msgid "Task's statuses"
msgstr "Estado de tareas" msgstr "Estado de tareas"
#: taiga/projects/serializers.py:404 #: taiga/projects/serializers.py:406
msgid "Issue's statuses" msgid "Issue's statuses"
msgstr "Estados de peticion" msgstr "Estados de peticion"
#: taiga/projects/serializers.py:405 #: taiga/projects/serializers.py:407
msgid "Issue's types" msgid "Issue's types"
msgstr "Tipos de petición" msgstr "Tipos de petición"
#: taiga/projects/serializers.py:406 #: taiga/projects/serializers.py:408
msgid "Priorities" msgid "Priorities"
msgstr "Prioridades" msgstr "Prioridades"
#: taiga/projects/serializers.py:407 #: taiga/projects/serializers.py:409
msgid "Severities" msgid "Severities"
msgstr "Gravedades" msgstr "Gravedades"
#: taiga/projects/serializers.py:408 #: taiga/projects/serializers.py:410
msgid "Roles" msgid "Roles"
msgstr "Roles" msgstr "Roles"
@ -3059,57 +3056,57 @@ msgstr "Permisos"
msgid "Important dates" msgid "Important dates"
msgstr "datos importántes" msgstr "datos importántes"
#: taiga/users/api.py:112 taiga/users/api.py:119 #: taiga/users/api.py:124 taiga/users/api.py:131
msgid "Invalid username or email" msgid "Invalid username or email"
msgstr "Nombre de usuario o email no válidos" msgstr "Nombre de usuario o email no válidos"
#: taiga/users/api.py:128 #: taiga/users/api.py:140
msgid "Mail sended successful!" msgid "Mail sended successful!"
msgstr "¡Correo enviado con éxito!" msgstr "¡Correo enviado con éxito!"
#: taiga/users/api.py:140 taiga/users/api.py:145 #: taiga/users/api.py:152 taiga/users/api.py:157
msgid "Token is invalid" msgid "Token is invalid"
msgstr "token inválido" msgstr "token inválido"
#: taiga/users/api.py:166 #: taiga/users/api.py:178
msgid "Current password parameter needed" msgid "Current password parameter needed"
msgstr "La contraseña actual es obligatoria." msgstr "La contraseña actual es obligatoria."
#: taiga/users/api.py:169 #: taiga/users/api.py:181
msgid "New password parameter needed" msgid "New password parameter needed"
msgstr "La nueva contraseña es obligatoria" msgstr "La nueva contraseña es obligatoria"
#: taiga/users/api.py:172 #: taiga/users/api.py:184
msgid "Invalid password length at least 6 charaters needed" msgid "Invalid password length at least 6 charaters needed"
msgstr "La longitud de la contraseña debe de ser de al menos 6 caracteres" msgstr "La longitud de la contraseña debe de ser de al menos 6 caracteres"
#: taiga/users/api.py:175 #: taiga/users/api.py:187
msgid "Invalid current password" msgid "Invalid current password"
msgstr "Contraseña actual inválida" msgstr "Contraseña actual inválida"
#: taiga/users/api.py:191 #: taiga/users/api.py:203
msgid "Incomplete arguments" msgid "Incomplete arguments"
msgstr "Argumentos incompletos" msgstr "Argumentos incompletos"
#: taiga/users/api.py:196 #: taiga/users/api.py:208
msgid "Invalid image format" msgid "Invalid image format"
msgstr "Formato de imagen no válido" msgstr "Formato de imagen no válido"
#: taiga/users/api.py:249 #: taiga/users/api.py:261
msgid "Duplicated email" msgid "Duplicated email"
msgstr "Email duplicado" msgstr "Email duplicado"
#: taiga/users/api.py:251 #: taiga/users/api.py:263
msgid "Not valid email" msgid "Not valid email"
msgstr "Email no válido" msgstr "Email no válido"
#: taiga/users/api.py:271 taiga/users/api.py:277 #: taiga/users/api.py:283 taiga/users/api.py:289
msgid "" msgid ""
"Invalid, are you sure the token is correct and you didn't use it before?" "Invalid, are you sure the token is correct and you didn't use it before?"
msgstr "" msgstr ""
"Invalido, ¿estás seguro de que el token es correcto y no se ha usado antes?" "Invalido, ¿estás seguro de que el token es correcto y no se ha usado antes?"
#: taiga/users/api.py:304 taiga/users/api.py:312 taiga/users/api.py:315 #: taiga/users/api.py:316 taiga/users/api.py:324 taiga/users/api.py:327
msgid "Invalid, are you sure the token is correct?" msgid "Invalid, are you sure the token is correct?"
msgstr "Inválido, ¿estás seguro de que el token es correcto?" msgstr "Inválido, ¿estás seguro de que el token es correcto?"
@ -3167,22 +3164,26 @@ msgid "default language"
msgstr "idioma por defecto" msgstr "idioma por defecto"
#: taiga/users/models.py:122 #: taiga/users/models.py:122
msgid "default theme"
msgstr ""
#: taiga/users/models.py:124
msgid "default timezone" msgid "default timezone"
msgstr "zona horaria por defecto" msgstr "zona horaria por defecto"
#: taiga/users/models.py:124 #: taiga/users/models.py:126
msgid "colorize tags" msgid "colorize tags"
msgstr "añade color a las etiquetas" msgstr "añade color a las etiquetas"
#: taiga/users/models.py:129 #: taiga/users/models.py:131
msgid "email token" msgid "email token"
msgstr "token de email" msgstr "token de email"
#: taiga/users/models.py:131 #: taiga/users/models.py:133
msgid "new email address" msgid "new email address"
msgstr "nueva dirección de email" msgstr "nueva dirección de email"
#: taiga/users/models.py:185 #: taiga/users/models.py:188
msgid "permissions" msgid "permissions"
msgstr "permisos" msgstr "permisos"

View File

@ -9,9 +9,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: taiga-back\n" "Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-25 17:31+0200\n" "POT-Creation-Date: 2015-06-09 09:47+0200\n"
"PO-Revision-Date: 2015-05-25 15:44+0000\n" "PO-Revision-Date: 2015-06-09 07:47+0000\n"
"Last-Translator: David Barragán <bameda@gmail.com>\n" "Last-Translator: Taiga Dev Team <support@taiga.io>\n"
"Language-Team: Finnish (http://www.transifex.com/projects/p/taiga-back/" "Language-Team: Finnish (http://www.transifex.com/projects/p/taiga-back/"
"language/fi/)\n" "language/fi/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -461,15 +461,12 @@ msgstr "Päivityksiä"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" <h3>comment:" " <h3>comment:"
"</h3>\n" "</h3>\n"
" <p>" " <p>"
"%(comment)s</p>\n" "%(comment)s</p>\n"
" " " "
msgstr "" msgstr ""
"\n"
"<h3>kommentti:</h3>\n"
"<p>%(comment)s</p>"
#: taiga/base/templates/emails/updates-body-text.jinja:6 #: taiga/base/templates/emails/updates-body-text.jinja:6
#, python-format #, python-format
@ -1210,7 +1207,7 @@ msgstr "omistaja"
#: taiga/projects/models.py:507 taiga/projects/models.py:538 #: taiga/projects/models.py:507 taiga/projects/models.py:538
#: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41
#: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28 #: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28
#: taiga/projects/wiki/models.py:66 taiga/users/models.py:193 #: taiga/projects/wiki/models.py:66 taiga/users/models.py:196
msgid "project" msgid "project"
msgstr "projekti" msgstr "projekti"
@ -1254,7 +1251,7 @@ msgstr "kuvaus"
#: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:391 taiga/projects/models.py:418
#: taiga/projects/models.py:453 taiga/projects/models.py:476 #: taiga/projects/models.py:453 taiga/projects/models.py:476
#: taiga/projects/models.py:501 taiga/projects/models.py:534 #: taiga/projects/models.py:501 taiga/projects/models.py:534
#: taiga/projects/wiki/models.py:71 taiga/users/models.py:188 #: taiga/projects/wiki/models.py:71 taiga/users/models.py:191
msgid "order" msgid "order"
msgstr "order" msgstr "order"
@ -1276,7 +1273,7 @@ msgstr "Talky"
#: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:414 taiga/projects/models.py:451
#: taiga/projects/models.py:474 taiga/projects/models.py:497 #: taiga/projects/models.py:474 taiga/projects/models.py:497
#: taiga/projects/models.py:532 taiga/projects/models.py:555 #: taiga/projects/models.py:532 taiga/projects/models.py:555
#: taiga/users/models.py:180 taiga/webhooks/models.py:27 #: taiga/users/models.py:183 taiga/webhooks/models.py:27
msgid "name" msgid "name"
msgstr "nimi" msgstr "nimi"
@ -1499,7 +1496,7 @@ msgstr "ulkoinen viittaus"
#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125 #: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125
#: taiga/projects/models.py:352 taiga/projects/models.py:416 #: taiga/projects/models.py:352 taiga/projects/models.py:416
#: taiga/projects/models.py:499 taiga/projects/models.py:557 #: taiga/projects/models.py:499 taiga/projects/models.py:557
#: taiga/projects/wiki/models.py:30 taiga/users/models.py:182 #: taiga/projects/wiki/models.py:30 taiga/users/models.py:185
msgid "slug" msgid "slug"
msgstr "hukka-aika" msgstr "hukka-aika"
@ -1549,7 +1546,7 @@ msgstr "sähköposti"
msgid "create at" msgid "create at"
msgstr "luo täällä" msgstr "luo täällä"
#: taiga/projects/models.py:63 taiga/users/models.py:126 #: taiga/projects/models.py:63 taiga/users/models.py:128
msgid "token" msgid "token"
msgstr "tunniste" msgstr "tunniste"
@ -2475,15 +2472,15 @@ msgstr "Vahdit sisältävät virheellisiä käyttäjiä"
msgid "The version must be an integer" msgid "The version must be an integer"
msgstr "Versio pitää olla kokonaisluku" msgstr "Versio pitää olla kokonaisluku"
#: taiga/projects/occ/mixins.py:56 #: taiga/projects/occ/mixins.py:58
msgid "The version is not valid" msgid "The version parameter is not valid"
msgstr "Versio on virheellinen" msgstr ""
#: taiga/projects/occ/mixins.py:72 #: taiga/projects/occ/mixins.py:74
msgid "The version doesn't match with the current one" msgid "The version doesn't match with the current one"
msgstr "Versio ei ole sama kuin nykyinen" msgstr "Versio ei ole sama kuin nykyinen"
#: taiga/projects/occ/mixins.py:92 #: taiga/projects/occ/mixins.py:93
msgid "version" msgid "version"
msgstr "versio" msgstr "versio"
@ -2499,43 +2496,43 @@ msgstr "Sähköpostiosoite on jo käytössä"
msgid "Invalid role for the project" msgid "Invalid role for the project"
msgstr "Virheellinen rooli projektille" msgstr "Virheellinen rooli projektille"
#: taiga/projects/serializers.py:343 #: taiga/projects/serializers.py:340
msgid "Total milestones must be major or equal to zero" msgid "Total milestones must be major or equal to zero"
msgstr "Virstapylväitä yhteensä pitää olla vähintään 0." msgstr "Virstapylväitä yhteensä pitää olla vähintään 0."
#: taiga/projects/serializers.py:400 #: taiga/projects/serializers.py:402
msgid "Default options" msgid "Default options"
msgstr "Oletusoptiot" msgstr "Oletusoptiot"
#: taiga/projects/serializers.py:401 #: taiga/projects/serializers.py:403
msgid "User story's statuses" msgid "User story's statuses"
msgstr "Käyttäjätarinatilat" msgstr "Käyttäjätarinatilat"
#: taiga/projects/serializers.py:402 #: taiga/projects/serializers.py:404
msgid "Points" msgid "Points"
msgstr "Pisteet" msgstr "Pisteet"
#: taiga/projects/serializers.py:403 #: taiga/projects/serializers.py:405
msgid "Task's statuses" msgid "Task's statuses"
msgstr "Tehtävien tilat" msgstr "Tehtävien tilat"
#: taiga/projects/serializers.py:404 #: taiga/projects/serializers.py:406
msgid "Issue's statuses" msgid "Issue's statuses"
msgstr "Pyyntöjen tilat" msgstr "Pyyntöjen tilat"
#: taiga/projects/serializers.py:405 #: taiga/projects/serializers.py:407
msgid "Issue's types" msgid "Issue's types"
msgstr "pyyntötyypit" msgstr "pyyntötyypit"
#: taiga/projects/serializers.py:406 #: taiga/projects/serializers.py:408
msgid "Priorities" msgid "Priorities"
msgstr "Kiireellisyydet" msgstr "Kiireellisyydet"
#: taiga/projects/serializers.py:407 #: taiga/projects/serializers.py:409
msgid "Severities" msgid "Severities"
msgstr "Vakavuudet" msgstr "Vakavuudet"
#: taiga/projects/serializers.py:408 #: taiga/projects/serializers.py:410
msgid "Roles" msgid "Roles"
msgstr "Roolit" msgstr "Roolit"
@ -3052,58 +3049,58 @@ msgstr "Oikeudet"
msgid "Important dates" msgid "Important dates"
msgstr "Tärkeät päivämäärät" msgstr "Tärkeät päivämäärät"
#: taiga/users/api.py:112 taiga/users/api.py:119 #: taiga/users/api.py:124 taiga/users/api.py:131
msgid "Invalid username or email" msgid "Invalid username or email"
msgstr "Tuntematon käyttäjänimi tai sähköposti" msgstr "Tuntematon käyttäjänimi tai sähköposti"
#: taiga/users/api.py:128 #: taiga/users/api.py:140
msgid "Mail sended successful!" msgid "Mail sended successful!"
msgstr "Sähköposti lähetetty." msgstr "Sähköposti lähetetty."
#: taiga/users/api.py:140 taiga/users/api.py:145 #: taiga/users/api.py:152 taiga/users/api.py:157
msgid "Token is invalid" msgid "Token is invalid"
msgstr "Tunniste on virheellinen" msgstr "Tunniste on virheellinen"
#: taiga/users/api.py:166 #: taiga/users/api.py:178
msgid "Current password parameter needed" msgid "Current password parameter needed"
msgstr "Nykyinen salasanaparametri tarvitaan" msgstr "Nykyinen salasanaparametri tarvitaan"
#: taiga/users/api.py:169 #: taiga/users/api.py:181
msgid "New password parameter needed" msgid "New password parameter needed"
msgstr "Uusi salasanaparametri tarvitaan" msgstr "Uusi salasanaparametri tarvitaan"
#: taiga/users/api.py:172 #: taiga/users/api.py:184
msgid "Invalid password length at least 6 charaters needed" msgid "Invalid password length at least 6 charaters needed"
msgstr "Salasanan pitää olla vähintään 6 merkkiä pitkä" msgstr "Salasanan pitää olla vähintään 6 merkkiä pitkä"
#: taiga/users/api.py:175 #: taiga/users/api.py:187
msgid "Invalid current password" msgid "Invalid current password"
msgstr "Virheellinen nykyinen salasana" msgstr "Virheellinen nykyinen salasana"
#: taiga/users/api.py:191 #: taiga/users/api.py:203
msgid "Incomplete arguments" msgid "Incomplete arguments"
msgstr "Puutteelliset argumentit" msgstr "Puutteelliset argumentit"
#: taiga/users/api.py:196 #: taiga/users/api.py:208
msgid "Invalid image format" msgid "Invalid image format"
msgstr "Väärä kuvaformaatti" msgstr "Väärä kuvaformaatti"
#: taiga/users/api.py:249 #: taiga/users/api.py:261
msgid "Duplicated email" msgid "Duplicated email"
msgstr "Sähköposti on jo olemassa" msgstr "Sähköposti on jo olemassa"
#: taiga/users/api.py:251 #: taiga/users/api.py:263
msgid "Not valid email" msgid "Not valid email"
msgstr "Virheellinen sähköposti" msgstr "Virheellinen sähköposti"
#: taiga/users/api.py:271 taiga/users/api.py:277 #: taiga/users/api.py:283 taiga/users/api.py:289
msgid "" msgid ""
"Invalid, are you sure the token is correct and you didn't use it before?" "Invalid, are you sure the token is correct and you didn't use it before?"
msgstr "" msgstr ""
"Virheellinen. Oletko varma, että tunniste on oikea ja et ole jo käyttänyt " "Virheellinen. Oletko varma, että tunniste on oikea ja et ole jo käyttänyt "
"sitä?" "sitä?"
#: taiga/users/api.py:304 taiga/users/api.py:312 taiga/users/api.py:315 #: taiga/users/api.py:316 taiga/users/api.py:324 taiga/users/api.py:327
msgid "Invalid, are you sure the token is correct?" msgid "Invalid, are you sure the token is correct?"
msgstr "Virheellinen, oletko varma että tunniste on oikea?" msgstr "Virheellinen, oletko varma että tunniste on oikea?"
@ -3161,22 +3158,26 @@ msgid "default language"
msgstr "oletuskieli" msgstr "oletuskieli"
#: taiga/users/models.py:122 #: taiga/users/models.py:122
msgid "default theme"
msgstr ""
#: taiga/users/models.py:124
msgid "default timezone" msgid "default timezone"
msgstr "oletus aikavyöhyke" msgstr "oletus aikavyöhyke"
#: taiga/users/models.py:124 #: taiga/users/models.py:126
msgid "colorize tags" msgid "colorize tags"
msgstr "väritä avainsanat" msgstr "väritä avainsanat"
#: taiga/users/models.py:129 #: taiga/users/models.py:131
msgid "email token" msgid "email token"
msgstr "sähköpostitunniste" msgstr "sähköpostitunniste"
#: taiga/users/models.py:131 #: taiga/users/models.py:133
msgid "new email address" msgid "new email address"
msgstr "uusi sähköpostiosoite" msgstr "uusi sähköpostiosoite"
#: taiga/users/models.py:185 #: taiga/users/models.py:188
msgid "permissions" msgid "permissions"
msgstr "oikeudet" msgstr "oikeudet"

View File

@ -5,15 +5,17 @@
# Translators: # Translators:
# Alain Poirier <alain.poirier@net-ng.com>, 2015 # Alain Poirier <alain.poirier@net-ng.com>, 2015
# David Barragán <bameda@gmail.com>, 2015 # David Barragán <bameda@gmail.com>, 2015
# Florent B. <me@kxrz.me>, 2015
# Louis-Michel Couture <louim_1@hotmail.com>, 2015 # Louis-Michel Couture <louim_1@hotmail.com>, 2015
# Matthieu Durocher <matthieu@technocyclope.com>, 2015
# Stéphane Mor <stephanemor@gmail.com>, 2015 # Stéphane Mor <stephanemor@gmail.com>, 2015
# William Godin <williamgodin@gmail.com>, 2015 # William Godin <williamgodin@gmail.com>, 2015
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: taiga-back\n" "Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-25 17:31+0200\n" "POT-Creation-Date: 2015-06-09 09:47+0200\n"
"PO-Revision-Date: 2015-05-25 13:58+0000\n" "PO-Revision-Date: 2015-06-09 07:47+0000\n"
"Last-Translator: Taiga Dev Team <support@taiga.io>\n" "Last-Translator: Taiga Dev Team <support@taiga.io>\n"
"Language-Team: French (http://www.transifex.com/projects/p/taiga-back/" "Language-Team: French (http://www.transifex.com/projects/p/taiga-back/"
"language/fr/)\n" "language/fr/)\n"
@ -142,7 +144,7 @@ msgstr ""
#: taiga/base/api/fields.py:860 #: taiga/base/api/fields.py:860
#, python-format #, python-format
msgid "\"%s\" value must be a float." msgid "\"%s\" value must be a float."
msgstr "La valeur de \"%s\" doit être un nombre flottant." msgstr "La valeur de \"%s\" doit être un nombre en virgule flottante."
#: taiga/base/api/fields.py:881 #: taiga/base/api/fields.py:881
msgid "Enter a number." msgid "Enter a number."
@ -225,7 +227,7 @@ msgstr "Type incorrect. Valeur pk attendue, %s reçu."
#: taiga/base/api/relations.py:310 #: taiga/base/api/relations.py:310
#, python-format #, python-format
msgid "Object with %s=%s does not exist." msgid "Object with %s=%s does not exist."
msgstr "L'object pour lequel %s=%s n'existe pas." msgstr "L'objet pour lequel %s=%s n'existe pas."
#: taiga/base/api/relations.py:346 #: taiga/base/api/relations.py:346
msgid "Invalid hyperlink - No URL match" msgid "Invalid hyperlink - No URL match"
@ -461,15 +463,12 @@ msgstr "Updates"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" <h3>comment:" " <h3>comment:"
"</h3>\n" "</h3>\n"
" <p>" " <p>"
"%(comment)s</p>\n" "%(comment)s</p>\n"
" " " "
msgstr "" msgstr ""
"\n"
"<h3>commentaire:</h3>\n"
"<p>%(comment)s</p>"
#: taiga/base/templates/emails/updates-body-text.jinja:6 #: taiga/base/templates/emails/updates-body-text.jinja:6
#, python-format #, python-format
@ -484,7 +483,7 @@ msgstr ""
#: taiga/export_import/api.py:103 #: taiga/export_import/api.py:103
msgid "We needed at least one role" msgid "We needed at least one role"
msgstr "" msgstr "Veuillez sélectionner au moins un rôle."
#: taiga/export_import/api.py:197 #: taiga/export_import/api.py:197
msgid "Needed dump file" msgid "Needed dump file"
@ -496,15 +495,16 @@ msgstr "Format de dump invalide"
#: taiga/export_import/dump_service.py:96 #: taiga/export_import/dump_service.py:96
msgid "error importing project data" msgid "error importing project data"
msgstr "" msgstr "Erreur lors de l'importation de données"
#: taiga/export_import/dump_service.py:109 #: taiga/export_import/dump_service.py:109
msgid "error importing lists of project attributes" msgid "error importing lists of project attributes"
msgstr "" msgstr "erreur lors de l'importation des listes des attributs de projet"
#: taiga/export_import/dump_service.py:114 #: taiga/export_import/dump_service.py:114
msgid "error importing default project attributes values" msgid "error importing default project attributes values"
msgstr "" msgstr ""
"erreur lors de l'importation des valeurs par défaut des attributs de projet"
#: taiga/export_import/dump_service.py:124 #: taiga/export_import/dump_service.py:124
msgid "error importing custom attributes" msgid "error importing custom attributes"
@ -520,7 +520,7 @@ msgstr "Erreur à l'importation des groupes d'utilisateurs"
#: taiga/export_import/dump_service.py:149 #: taiga/export_import/dump_service.py:149
msgid "error importing sprints" msgid "error importing sprints"
msgstr "" msgstr "Erreur lors de l'importation des sprints."
#: taiga/export_import/dump_service.py:154 #: taiga/export_import/dump_service.py:154
msgid "error importing wiki pages" msgid "error importing wiki pages"
@ -540,11 +540,11 @@ msgstr "erreur à l'importation des histoires utilisateur"
#: taiga/export_import/dump_service.py:174 #: taiga/export_import/dump_service.py:174
msgid "error importing tasks" msgid "error importing tasks"
msgstr "" msgstr "Erreur lors de l'importation des tâches."
#: taiga/export_import/dump_service.py:179 #: taiga/export_import/dump_service.py:179
msgid "error importing tags" msgid "error importing tags"
msgstr "" msgstr "erreur lors de l'importation des mots-clés"
#: taiga/export_import/dump_service.py:183 #: taiga/export_import/dump_service.py:183
msgid "error importing timelines" msgid "error importing timelines"
@ -870,7 +870,7 @@ msgstr ""
#: taiga/hooks/github/event_hooks.py:169 #: taiga/hooks/github/event_hooks.py:169
msgid "Issue created from GitHub." msgid "Issue created from GitHub."
msgstr "" msgstr "Suivi de problème créé à partir de GitHub."
#: taiga/hooks/github/event_hooks.py:178 taiga/hooks/github/event_hooks.py:193 #: taiga/hooks/github/event_hooks.py:178 taiga/hooks/github/event_hooks.py:193
msgid "Invalid issue comment information" msgid "Invalid issue comment information"
@ -1106,7 +1106,7 @@ msgstr "propriétaire"
#: taiga/projects/models.py:507 taiga/projects/models.py:538 #: taiga/projects/models.py:507 taiga/projects/models.py:538
#: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41
#: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28 #: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28
#: taiga/projects/wiki/models.py:66 taiga/users/models.py:193 #: taiga/projects/wiki/models.py:66 taiga/users/models.py:196
msgid "project" msgid "project"
msgstr "projet" msgstr "projet"
@ -1150,7 +1150,7 @@ msgstr "description"
#: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:391 taiga/projects/models.py:418
#: taiga/projects/models.py:453 taiga/projects/models.py:476 #: taiga/projects/models.py:453 taiga/projects/models.py:476
#: taiga/projects/models.py:501 taiga/projects/models.py:534 #: taiga/projects/models.py:501 taiga/projects/models.py:534
#: taiga/projects/wiki/models.py:71 taiga/users/models.py:188 #: taiga/projects/wiki/models.py:71 taiga/users/models.py:191
msgid "order" msgid "order"
msgstr "ordre" msgstr "ordre"
@ -1172,7 +1172,7 @@ msgstr "Talky"
#: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:414 taiga/projects/models.py:451
#: taiga/projects/models.py:474 taiga/projects/models.py:497 #: taiga/projects/models.py:474 taiga/projects/models.py:497
#: taiga/projects/models.py:532 taiga/projects/models.py:555 #: taiga/projects/models.py:532 taiga/projects/models.py:555
#: taiga/users/models.py:180 taiga/webhooks/models.py:27 #: taiga/users/models.py:183 taiga/webhooks/models.py:27
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
@ -1395,7 +1395,7 @@ msgstr "référence externe"
#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125 #: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125
#: taiga/projects/models.py:352 taiga/projects/models.py:416 #: taiga/projects/models.py:352 taiga/projects/models.py:416
#: taiga/projects/models.py:499 taiga/projects/models.py:557 #: taiga/projects/models.py:499 taiga/projects/models.py:557
#: taiga/projects/wiki/models.py:30 taiga/users/models.py:182 #: taiga/projects/wiki/models.py:30 taiga/users/models.py:185
msgid "slug" msgid "slug"
msgstr "slug" msgstr "slug"
@ -1443,9 +1443,9 @@ msgstr "email"
#: taiga/projects/models.py:61 #: taiga/projects/models.py:61
msgid "create at" msgid "create at"
msgstr "" msgstr "Créé le"
#: taiga/projects/models.py:63 taiga/users/models.py:126 #: taiga/projects/models.py:63 taiga/users/models.py:128
msgid "token" msgid "token"
msgstr "jeton" msgstr "jeton"
@ -2131,15 +2131,15 @@ msgstr "La liste des observateurs contient des utilisateurs invalides"
msgid "The version must be an integer" msgid "The version must be an integer"
msgstr "La version doit être un nombre entier" msgstr "La version doit être un nombre entier"
#: taiga/projects/occ/mixins.py:56 #: taiga/projects/occ/mixins.py:58
msgid "The version is not valid" msgid "The version parameter is not valid"
msgstr "La version n'est pas valide" msgstr ""
#: taiga/projects/occ/mixins.py:72 #: taiga/projects/occ/mixins.py:74
msgid "The version doesn't match with the current one" msgid "The version doesn't match with the current one"
msgstr "La version ne correspond pas à la version courante" msgstr "La version ne correspond pas à la version courante"
#: taiga/projects/occ/mixins.py:92 #: taiga/projects/occ/mixins.py:93
msgid "version" msgid "version"
msgstr "version" msgstr "version"
@ -2156,43 +2156,43 @@ msgstr "Adresse email déjà existante"
msgid "Invalid role for the project" msgid "Invalid role for the project"
msgstr "Rôle non valide pour le projet" msgstr "Rôle non valide pour le projet"
#: taiga/projects/serializers.py:343 #: taiga/projects/serializers.py:340
msgid "Total milestones must be major or equal to zero" msgid "Total milestones must be major or equal to zero"
msgstr "Le nombre de jalons doit être supérieur ou égal à zéro" msgstr "Le nombre de jalons doit être supérieur ou égal à zéro"
#: taiga/projects/serializers.py:400 #: taiga/projects/serializers.py:402
msgid "Default options" msgid "Default options"
msgstr "Options par défaut" msgstr "Options par défaut"
#: taiga/projects/serializers.py:401 #: taiga/projects/serializers.py:403
msgid "User story's statuses" msgid "User story's statuses"
msgstr "Etats de la User Story" msgstr "Etats de la User Story"
#: taiga/projects/serializers.py:402 #: taiga/projects/serializers.py:404
msgid "Points" msgid "Points"
msgstr "Points" msgstr "Points"
#: taiga/projects/serializers.py:403 #: taiga/projects/serializers.py:405
msgid "Task's statuses" msgid "Task's statuses"
msgstr "Etats des tâches" msgstr "Etats des tâches"
#: taiga/projects/serializers.py:404 #: taiga/projects/serializers.py:406
msgid "Issue's statuses" msgid "Issue's statuses"
msgstr "Statuts des problèmes" msgstr "Statuts des problèmes"
#: taiga/projects/serializers.py:405 #: taiga/projects/serializers.py:407
msgid "Issue's types" msgid "Issue's types"
msgstr "Types de problèmes" msgstr "Types de problèmes"
#: taiga/projects/serializers.py:406 #: taiga/projects/serializers.py:408
msgid "Priorities" msgid "Priorities"
msgstr "Priorités" msgstr "Priorités"
#: taiga/projects/serializers.py:407 #: taiga/projects/serializers.py:409
msgid "Severities" msgid "Severities"
msgstr "Sévérités" msgstr "Sévérités"
#: taiga/projects/serializers.py:408 #: taiga/projects/serializers.py:410
msgid "Roles" msgid "Roles"
msgstr "Rôles" msgstr "Rôles"
@ -2689,58 +2689,58 @@ msgstr "Permissions"
msgid "Important dates" msgid "Important dates"
msgstr "Dates importantes" msgstr "Dates importantes"
#: taiga/users/api.py:112 taiga/users/api.py:119 #: taiga/users/api.py:124 taiga/users/api.py:131
msgid "Invalid username or email" msgid "Invalid username or email"
msgstr "Nom d'utilisateur ou email non valide" msgstr "Nom d'utilisateur ou email non valide"
#: taiga/users/api.py:128 #: taiga/users/api.py:140
msgid "Mail sended successful!" msgid "Mail sended successful!"
msgstr "Mail envoyé avec succès!" msgstr "Mail envoyé avec succès!"
#: taiga/users/api.py:140 taiga/users/api.py:145 #: taiga/users/api.py:152 taiga/users/api.py:157
msgid "Token is invalid" msgid "Token is invalid"
msgstr "Jeton invalide" msgstr "Jeton invalide"
#: taiga/users/api.py:166 #: taiga/users/api.py:178
msgid "Current password parameter needed" msgid "Current password parameter needed"
msgstr "Paramètre 'mot de passe actuel' requis" msgstr "Paramètre 'mot de passe actuel' requis"
#: taiga/users/api.py:169 #: taiga/users/api.py:181
msgid "New password parameter needed" msgid "New password parameter needed"
msgstr "Paramètre 'nouveau mot de passe' requis" msgstr "Paramètre 'nouveau mot de passe' requis"
#: taiga/users/api.py:172 #: taiga/users/api.py:184
msgid "Invalid password length at least 6 charaters needed" msgid "Invalid password length at least 6 charaters needed"
msgstr "Le mot de passe doit être d'au moins 6 caractères" msgstr "Le mot de passe doit être d'au moins 6 caractères"
#: taiga/users/api.py:175 #: taiga/users/api.py:187
msgid "Invalid current password" msgid "Invalid current password"
msgstr "Mot de passe actuel incorrect" msgstr "Mot de passe actuel incorrect"
#: taiga/users/api.py:191 #: taiga/users/api.py:203
msgid "Incomplete arguments" msgid "Incomplete arguments"
msgstr "arguments manquants" msgstr "arguments manquants"
#: taiga/users/api.py:196 #: taiga/users/api.py:208
msgid "Invalid image format" msgid "Invalid image format"
msgstr "format de l'image non valide" msgstr "format de l'image non valide"
#: taiga/users/api.py:249 #: taiga/users/api.py:261
msgid "Duplicated email" msgid "Duplicated email"
msgstr "Email dupliquée" msgstr "Email dupliquée"
#: taiga/users/api.py:251 #: taiga/users/api.py:263
msgid "Not valid email" msgid "Not valid email"
msgstr "Email non valide" msgstr "Email non valide"
#: taiga/users/api.py:271 taiga/users/api.py:277 #: taiga/users/api.py:283 taiga/users/api.py:289
msgid "" msgid ""
"Invalid, are you sure the token is correct and you didn't use it before?" "Invalid, are you sure the token is correct and you didn't use it before?"
msgstr "" msgstr ""
"Invalide, êtes-vous sûre que le jeton est correct et qu'il n'a pas déjà été " "Invalide, êtes-vous sûre que le jeton est correct et qu'il n'a pas déjà été "
"utilisé ?" "utilisé ?"
#: taiga/users/api.py:304 taiga/users/api.py:312 taiga/users/api.py:315 #: taiga/users/api.py:316 taiga/users/api.py:324 taiga/users/api.py:327
msgid "Invalid, are you sure the token is correct?" msgid "Invalid, are you sure the token is correct?"
msgstr "Invalide, êtes-vous sûre que le jeton est correct ?" msgstr "Invalide, êtes-vous sûre que le jeton est correct ?"
@ -2799,22 +2799,26 @@ msgid "default language"
msgstr "langage par défaut" msgstr "langage par défaut"
#: taiga/users/models.py:122 #: taiga/users/models.py:122
msgid "default theme"
msgstr ""
#: taiga/users/models.py:124
msgid "default timezone" msgid "default timezone"
msgstr "Fuseau horaire par défaut" msgstr "Fuseau horaire par défaut"
#: taiga/users/models.py:124 #: taiga/users/models.py:126
msgid "colorize tags" msgid "colorize tags"
msgstr "changer la couleur des tags" msgstr "changer la couleur des tags"
#: taiga/users/models.py:129 #: taiga/users/models.py:131
msgid "email token" msgid "email token"
msgstr "jeton email" msgstr "jeton email"
#: taiga/users/models.py:131 #: taiga/users/models.py:133
msgid "new email address" msgid "new email address"
msgstr "nouvelle adresse email" msgstr "nouvelle adresse email"
#: taiga/users/models.py:185 #: taiga/users/models.py:188
msgid "permissions" msgid "permissions"
msgstr "permissions" msgstr "permissions"

View File

@ -11,9 +11,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: taiga-back\n" "Project-Id-Version: taiga-back\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-25 17:31+0200\n" "POT-Creation-Date: 2015-06-09 09:47+0200\n"
"PO-Revision-Date: 2015-05-25 15:44+0000\n" "PO-Revision-Date: 2015-06-09 07:47+0000\n"
"Last-Translator: David Barragán <bameda@gmail.com>\n" "Last-Translator: Taiga Dev Team <support@taiga.io>\n"
"Language-Team: Chinese Traditional (http://www.transifex.com/projects/p/" "Language-Team: Chinese Traditional (http://www.transifex.com/projects/p/"
"taiga-back/language/zh-Hant/)\n" "taiga-back/language/zh-Hant/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -463,15 +463,12 @@ msgstr "更新"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" <h3>comment:" " <h3>comment:"
"</h3>\n" "</h3>\n"
" <p>" " <p>"
"%(comment)s</p>\n" "%(comment)s</p>\n"
" " " "
msgstr "" msgstr ""
"\n"
"<h3>評論:</h3>\n"
"<p>%(comment)s</p>"
#: taiga/base/templates/emails/updates-body-text.jinja:6 #: taiga/base/templates/emails/updates-body-text.jinja:6
#, python-format #, python-format
@ -1205,7 +1202,7 @@ msgstr "所有者"
#: taiga/projects/models.py:507 taiga/projects/models.py:538 #: taiga/projects/models.py:507 taiga/projects/models.py:538
#: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: taiga/projects/notifications/models.py:69 taiga/projects/tasks/models.py:41
#: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28 #: taiga/projects/userstories/models.py:62 taiga/projects/wiki/models.py:28
#: taiga/projects/wiki/models.py:66 taiga/users/models.py:193 #: taiga/projects/wiki/models.py:66 taiga/users/models.py:196
msgid "project" msgid "project"
msgstr "專案" msgstr "專案"
@ -1249,7 +1246,7 @@ msgstr "描述"
#: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:391 taiga/projects/models.py:418
#: taiga/projects/models.py:453 taiga/projects/models.py:476 #: taiga/projects/models.py:453 taiga/projects/models.py:476
#: taiga/projects/models.py:501 taiga/projects/models.py:534 #: taiga/projects/models.py:501 taiga/projects/models.py:534
#: taiga/projects/wiki/models.py:71 taiga/users/models.py:188 #: taiga/projects/wiki/models.py:71 taiga/users/models.py:191
msgid "order" msgid "order"
msgstr "次序" msgstr "次序"
@ -1271,7 +1268,7 @@ msgstr "Talky"
#: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:414 taiga/projects/models.py:451
#: taiga/projects/models.py:474 taiga/projects/models.py:497 #: taiga/projects/models.py:474 taiga/projects/models.py:497
#: taiga/projects/models.py:532 taiga/projects/models.py:555 #: taiga/projects/models.py:532 taiga/projects/models.py:555
#: taiga/users/models.py:180 taiga/webhooks/models.py:27 #: taiga/users/models.py:183 taiga/webhooks/models.py:27
msgid "name" msgid "name"
msgstr "姓名" msgstr "姓名"
@ -1494,7 +1491,7 @@ msgstr "外部參考"
#: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125 #: taiga/projects/milestones/models.py:37 taiga/projects/models.py:125
#: taiga/projects/models.py:352 taiga/projects/models.py:416 #: taiga/projects/models.py:352 taiga/projects/models.py:416
#: taiga/projects/models.py:499 taiga/projects/models.py:557 #: taiga/projects/models.py:499 taiga/projects/models.py:557
#: taiga/projects/wiki/models.py:30 taiga/users/models.py:182 #: taiga/projects/wiki/models.py:30 taiga/users/models.py:185
msgid "slug" msgid "slug"
msgstr "代稱" msgstr "代稱"
@ -1544,7 +1541,7 @@ msgstr "電子郵件"
msgid "create at" msgid "create at"
msgstr "創建於" msgstr "創建於"
#: taiga/projects/models.py:63 taiga/users/models.py:126 #: taiga/projects/models.py:63 taiga/users/models.py:128
msgid "token" msgid "token"
msgstr "代號" msgstr "代號"
@ -2474,15 +2471,15 @@ msgstr "監督者包含無效使用者"
msgid "The version must be an integer" msgid "The version must be an integer"
msgstr "版本須為整數值 " msgstr "版本須為整數值 "
#: taiga/projects/occ/mixins.py:56 #: taiga/projects/occ/mixins.py:58
msgid "The version is not valid" msgid "The version parameter is not valid"
msgstr "版本無效" msgstr ""
#: taiga/projects/occ/mixins.py:72 #: taiga/projects/occ/mixins.py:74
msgid "The version doesn't match with the current one" msgid "The version doesn't match with the current one"
msgstr "版本與目前使用不相符" msgstr "版本與目前使用不相符"
#: taiga/projects/occ/mixins.py:92 #: taiga/projects/occ/mixins.py:93
msgid "version" msgid "version"
msgstr "版本" msgstr "版本"
@ -2498,43 +2495,43 @@ msgstr "電子郵件已使用"
msgid "Invalid role for the project" msgid "Invalid role for the project"
msgstr "專案無效的角色" msgstr "專案無效的角色"
#: taiga/projects/serializers.py:343 #: taiga/projects/serializers.py:340
msgid "Total milestones must be major or equal to zero" msgid "Total milestones must be major or equal to zero"
msgstr "Kanban" msgstr "Kanban"
#: taiga/projects/serializers.py:400 #: taiga/projects/serializers.py:402
msgid "Default options" msgid "Default options"
msgstr "預設選項" msgstr "預設選項"
#: taiga/projects/serializers.py:401 #: taiga/projects/serializers.py:403
msgid "User story's statuses" msgid "User story's statuses"
msgstr "使用者故事狀態" msgstr "使用者故事狀態"
#: taiga/projects/serializers.py:402 #: taiga/projects/serializers.py:404
msgid "Points" msgid "Points"
msgstr "點數" msgstr "點數"
#: taiga/projects/serializers.py:403 #: taiga/projects/serializers.py:405
msgid "Task's statuses" msgid "Task's statuses"
msgstr "任務狀態" msgstr "任務狀態"
#: taiga/projects/serializers.py:404 #: taiga/projects/serializers.py:406
msgid "Issue's statuses" msgid "Issue's statuses"
msgstr "問題狀態" msgstr "問題狀態"
#: taiga/projects/serializers.py:405 #: taiga/projects/serializers.py:407
msgid "Issue's types" msgid "Issue's types"
msgstr "問題類型" msgstr "問題類型"
#: taiga/projects/serializers.py:406 #: taiga/projects/serializers.py:408
msgid "Priorities" msgid "Priorities"
msgstr "優先性" msgstr "優先性"
#: taiga/projects/serializers.py:407 #: taiga/projects/serializers.py:409
msgid "Severities" msgid "Severities"
msgstr "嚴重性" msgstr "嚴重性"
#: taiga/projects/serializers.py:408 #: taiga/projects/serializers.py:410
msgid "Roles" msgid "Roles"
msgstr "角色" msgstr "角色"
@ -3043,56 +3040,56 @@ msgstr "許可"
msgid "Important dates" msgid "Important dates"
msgstr "重要日期" msgstr "重要日期"
#: taiga/users/api.py:112 taiga/users/api.py:119 #: taiga/users/api.py:124 taiga/users/api.py:131
msgid "Invalid username or email" msgid "Invalid username or email"
msgstr "無效使用者或郵件" msgstr "無效使用者或郵件"
#: taiga/users/api.py:128 #: taiga/users/api.py:140
msgid "Mail sended successful!" msgid "Mail sended successful!"
msgstr "成功送出郵件" msgstr "成功送出郵件"
#: taiga/users/api.py:140 taiga/users/api.py:145 #: taiga/users/api.py:152 taiga/users/api.py:157
msgid "Token is invalid" msgid "Token is invalid"
msgstr "代號無效" msgstr "代號無效"
#: taiga/users/api.py:166 #: taiga/users/api.py:178
msgid "Current password parameter needed" msgid "Current password parameter needed"
msgstr "需要目前密碼之參數" msgstr "需要目前密碼之參數"
#: taiga/users/api.py:169 #: taiga/users/api.py:181
msgid "New password parameter needed" msgid "New password parameter needed"
msgstr "需要新密碼參數" msgstr "需要新密碼參數"
#: taiga/users/api.py:172 #: taiga/users/api.py:184
msgid "Invalid password length at least 6 charaters needed" msgid "Invalid password length at least 6 charaters needed"
msgstr "無效密碼長度,至少需6個字元" msgstr "無效密碼長度,至少需6個字元"
#: taiga/users/api.py:175 #: taiga/users/api.py:187
msgid "Invalid current password" msgid "Invalid current password"
msgstr "無效密碼" msgstr "無效密碼"
#: taiga/users/api.py:191 #: taiga/users/api.py:203
msgid "Incomplete arguments" msgid "Incomplete arguments"
msgstr "不完整參數" msgstr "不完整參數"
#: taiga/users/api.py:196 #: taiga/users/api.py:208
msgid "Invalid image format" msgid "Invalid image format"
msgstr "無效的圖片檔案" msgstr "無效的圖片檔案"
#: taiga/users/api.py:249 #: taiga/users/api.py:261
msgid "Duplicated email" msgid "Duplicated email"
msgstr "複製電子郵件" msgstr "複製電子郵件"
#: taiga/users/api.py:251 #: taiga/users/api.py:263
msgid "Not valid email" msgid "Not valid email"
msgstr "非有效電子郵性" msgstr "非有效電子郵性"
#: taiga/users/api.py:271 taiga/users/api.py:277 #: taiga/users/api.py:283 taiga/users/api.py:289
msgid "" msgid ""
"Invalid, are you sure the token is correct and you didn't use it before?" "Invalid, are you sure the token is correct and you didn't use it before?"
msgstr "無效,請確認代號正確,之前是否曾使用過?" msgstr "無效,請確認代號正確,之前是否曾使用過?"
#: taiga/users/api.py:304 taiga/users/api.py:312 taiga/users/api.py:315 #: taiga/users/api.py:316 taiga/users/api.py:324 taiga/users/api.py:327
msgid "Invalid, are you sure the token is correct?" msgid "Invalid, are you sure the token is correct?"
msgstr "無效,請確認代號是否正確?" msgstr "無效,請確認代號是否正確?"
@ -3146,22 +3143,26 @@ msgid "default language"
msgstr "預設語言 " msgstr "預設語言 "
#: taiga/users/models.py:122 #: taiga/users/models.py:122
msgid "default theme"
msgstr ""
#: taiga/users/models.py:124
msgid "default timezone" msgid "default timezone"
msgstr "預設時區" msgstr "預設時區"
#: taiga/users/models.py:124 #: taiga/users/models.py:126
msgid "colorize tags" msgid "colorize tags"
msgstr "顏色標籤" msgstr "顏色標籤"
#: taiga/users/models.py:129 #: taiga/users/models.py:131
msgid "email token" msgid "email token"
msgstr "電子郵件符號 " msgstr "電子郵件符號 "
#: taiga/users/models.py:131 #: taiga/users/models.py:133
msgid "new email address" msgid "new email address"
msgstr "新電子郵件地址" msgstr "新電子郵件地址"
#: taiga/users/models.py:185 #: taiga/users/models.py:188
msgid "permissions" msgid "permissions"
msgstr "許可" msgstr "許可"

View File

@ -25,22 +25,22 @@
from markdown.extensions import Extension from markdown.extensions import Extension
from markdown.inlinepatterns import Pattern from markdown.inlinepatterns import Pattern
from markdown.util import etree from markdown.util import etree, AtomicString
from taiga.users.models import User from taiga.users.models import User
class MentionsExtension(Extension): class MentionsExtension(Extension):
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
MENTION_RE = r'(?<=^|(?<=[^a-zA-Z0-9-_\.]))@([A-Za-z]+[A-Za-z0-9-]+)' MENTION_RE = r'(@)([a-z0-9.-\.]+)'
mentionsPattern = MentionsPattern(MENTION_RE) mentionsPattern = MentionsPattern(MENTION_RE)
mentionsPattern.md = md mentionsPattern.md = md
md.inlinePatterns.add('mentions', mentionsPattern, '_begin') md.inlinePatterns.add('mentions', mentionsPattern, '_end')
class MentionsPattern(Pattern): class MentionsPattern(Pattern):
def handleMatch(self, m): def handleMatch(self, m):
username = m.group(2) username = m.group(3)
try: try:
user = User.objects.get(username=username) user = User.objects.get(username=username)
@ -49,10 +49,11 @@ class MentionsPattern(Pattern):
url = "/profile/{}".format(username) url = "/profile/{}".format(username)
link_text = "&commat;{}".format(username) link_text = "@{}".format(username)
a = etree.Element('a') a = etree.Element('a')
a.text = link_text a.text = AtomicString(link_text)
a.set('href', url) a.set('href', url)
a.set('title', user.get_full_name()) a.set('title', user.get_full_name())
a.set('class', "mention") a.set('class', "mention")

View File

@ -28,7 +28,7 @@ from markdown.inlinepatterns import Pattern
from markdown.util import etree from markdown.util import etree
from taiga.projects.references.services import get_instance_by_ref from taiga.projects.references.services import get_instance_by_ref
from taiga.front import resolve from taiga.front.templatetags.functions import resolve
class TaigaReferencesExtension(Extension): class TaigaReferencesExtension(Extension):

View File

@ -21,7 +21,7 @@ import markdown
from markdown.treeprocessors import Treeprocessor from markdown.treeprocessors import Treeprocessor
from taiga.front import resolve from taiga.front.templatetags.functions import resolve
class TargetBlankLinkExtension(markdown.Extension): class TargetBlankLinkExtension(markdown.Extension):

View File

@ -21,7 +21,7 @@ from markdown.treeprocessors import Treeprocessor
from markdown.util import etree from markdown.util import etree
from taiga.front import resolve from taiga.front.templatetags.functions import resolve
from taiga.base.utils.slug import slugify from taiga.base.utils.slug import slugify
import re import re

View File

@ -96,7 +96,7 @@
{% set values_removed = lists_diff(values_from, values_to) %} {% set values_removed = lists_diff(values_from, values_to) %}
<tr> <tr>
<td valign="middle" rowspan="2" class="update-row-name"> <td valign="middle" class="update-row-name">
<h3>{{ verbose_name(obj_class, field_name) }}</h3> <h3>{{ verbose_name(obj_class, field_name) }}</h3>
</td> </td>
<td valign="top" class="update-row-from"> <td valign="top" class="update-row-from">

View File

@ -110,7 +110,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
IssuesFilter, IssuesOrdering,) IssuesFilter, IssuesOrdering,)
retrieve_exclude_filters = (IssuesFilter,) retrieve_exclude_filters = (IssuesFilter,)
filter_fields = ("project", "assigned_to", "status__is_closed", "watchers") filter_fields = ("project", "status__is_closed", "watchers")
order_by_fields = ("type", order_by_fields = ("type",
"severity", "severity",
"status", "status",

View File

@ -37,7 +37,9 @@ class OCCResourceMixin(object):
return param_version return param_version
def _validate_param_version(self, param_version, current_version): def _validate_param_version(self, param_version, current_version):
if param_version is not None: if param_version is None:
return False
else:
if param_version < 0: if param_version < 0:
return False return False
if current_version is not None and param_version > current_version: if current_version is not None and param_version > current_version:
@ -50,28 +52,27 @@ class OCCResourceMixin(object):
if obj.id: if obj.id:
current_version = type(obj).objects.model.objects.get(id=obj.id).version current_version = type(obj).objects.model.objects.get(id=obj.id).version
# Extract param version # Extract param version
param_version = self._extract_param_version() param_version = self._extract_param_version()
if not self._validate_param_version(param_version, current_version): if not self._validate_param_version(param_version, current_version):
raise exc.WrongArguments({"version": _("The version is not valid")}) raise exc.WrongArguments({"version": _("The version parameter is not valid")})
if current_version != param_version: if current_version != param_version:
diff_versions = current_version - param_version diff_versions = current_version - param_version
modifying_fields = set(self.request.DATA.keys()) modifying_fields = set(self.request.DATA.keys())
if "version" in modifying_fields: if "version" in modifying_fields:
modifying_fields.remove("version") modifying_fields.remove("version")
modified_fields = set(get_modified_fields(obj, diff_versions)) modified_fields = set(get_modified_fields(obj, diff_versions))
if "version" in modifying_fields: if "version" in modifying_fields:
modified_fields.remove("version") modified_fields.remove("version")
both_modified = modifying_fields & modified_fields both_modified = modifying_fields & modified_fields
if both_modified: if both_modified:
raise exc.WrongArguments({"version": _("The version doesn't match with the current one")}) raise exc.WrongArguments({"version": _("The version doesn't match with the current one")})
if obj.id:
obj.version = models.F('version') + 1 obj.version = models.F('version') + 1
def pre_save(self, obj): def pre_save(self, obj):

View File

@ -305,7 +305,6 @@ class ProjectSerializer(serializers.ModelSerializer):
my_permissions = serializers.SerializerMethodField("get_my_permissions") my_permissions = serializers.SerializerMethodField("get_my_permissions")
i_am_owner = serializers.SerializerMethodField("get_i_am_owner") i_am_owner = serializers.SerializerMethodField("get_i_am_owner")
tags_colors = TagsColorsField(required=False) tags_colors = TagsColorsField(required=False)
users = serializers.SerializerMethodField("get_users")
total_closed_milestones = serializers.SerializerMethodField("get_total_closed_milestones") total_closed_milestones = serializers.SerializerMethodField("get_total_closed_milestones")
class Meta: class Meta:
@ -328,9 +327,6 @@ class ProjectSerializer(serializers.ModelSerializer):
return is_project_owner(self.context["request"].user, obj) return is_project_owner(self.context["request"].user, obj)
return False return False
def get_users(self, obj):
return UserSerializer(obj.members.all(), many=True).data
def get_total_closed_milestones(self, obj): def get_total_closed_milestones(self, obj):
return obj.milestones.filter(closed=True).count() return obj.milestones.filter(closed=True).count()
@ -355,18 +351,20 @@ class ProjectDetailSerializer(ProjectSerializer):
issue_types = IssueTypeSerializer(many=True, required=False) issue_types = IssueTypeSerializer(many=True, required=False)
priorities = PrioritySerializer(many=True, required=False) # Issues priorities = PrioritySerializer(many=True, required=False) # Issues
severities = SeveritySerializer(many=True, required=False) severities = SeveritySerializer(many=True, required=False)
userstory_custom_attributes = UserStoryCustomAttributeSerializer(source="userstorycustomattributes", userstory_custom_attributes = UserStoryCustomAttributeSerializer(source="userstorycustomattributes",
many=True, required=False) many=True, required=False)
task_custom_attributes = TaskCustomAttributeSerializer(source="taskcustomattributes", task_custom_attributes = TaskCustomAttributeSerializer(source="taskcustomattributes",
many=True, required=False) many=True, required=False)
issue_custom_attributes = IssueCustomAttributeSerializer(source="issuecustomattributes", issue_custom_attributes = IssueCustomAttributeSerializer(source="issuecustomattributes",
many=True, required=False) many=True, required=False)
users = serializers.SerializerMethodField("get_users")
def get_memberships(self, obj): def get_memberships(self, obj):
qs = obj.memberships.filter(user__isnull=False) qs = obj.memberships.filter(user__isnull=False)
qs = qs.order_by('user__full_name', 'user__username') qs = qs.extra(select={"complete_user_name":"concat(full_name, username)"})
qs = qs.order_by("complete_user_name")
qs = qs.select_related("role", "user") qs = qs.select_related("role", "user")
serializer = ProjectMembershipSerializer(qs, many=True) serializer = ProjectMembershipSerializer(qs, many=True)
return serializer.data return serializer.data
@ -374,6 +372,9 @@ class ProjectDetailSerializer(ProjectSerializer):
serializer = ProjectRoleSerializer(obj.roles.all(), many=True) serializer = ProjectRoleSerializer(obj.roles.all(), many=True)
return serializer.data return serializer.data
def get_users(self, obj):
return UserSerializer(obj.members.all(), many=True).data
class ProjectDetailAdminSerializer(ProjectDetailSerializer): class ProjectDetailAdminSerializer(ProjectDetailSerializer):
class Meta: class Meta:

View File

@ -50,12 +50,13 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
list_serializer_class = serializers.UserStorySerializer list_serializer_class = serializers.UserStorySerializer
permission_classes = (permissions.UserStoryPermission,) permission_classes = (permissions.UserStoryPermission,)
filter_backends = (filters.CanViewUsFilterBackend, filters.TagsFilter, filter_backends = (filters.StatusFilter, filters.CanViewUsFilterBackend, filters.TagsFilter,
filters.QFilter, filters.OrderByFilterMixin) filters.QFilter, filters.OrderByFilterMixin)
retrieve_exclude_filters = (filters.TagsFilter,)
filter_fields = ["project", "milestone", "milestone__isnull", "status", retrieve_exclude_filters = (filters.StatusFilter, filters.TagsFilter,)
filter_fields = ["project", "milestone", "milestone__isnull",
"is_archived", "status__is_archived", "assigned_to", "is_archived", "status__is_archived", "assigned_to",
"status__is_closed", "watchers"] "status__is_closed", "watchers", "is_closed"]
order_by_fields = ["backlog_order", "sprint_order", "kanban_order"] order_by_fields = ["backlog_order", "sprint_order", "kanban_order"]
# Specific filter used for filtering neighbor user stories # Specific filter used for filtering neighbor user stories

View File

@ -0,0 +1,86 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
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 Prefetch, F
from taiga.timeline.models import Timeline
from taiga.timeline.timeline_implementations import userstory_timeline
from optparse import make_option
from taiga.projects.tasks.models import Task
from taiga.projects.userstories.models import UserStory
def update_timeline(initial_date, final_date):
timelines = Timeline.objects.all()
if initial_date:
timelines = timelines.filter(created__gte=initial_date)
if final_date:
timelines = timelines.filter(created__lt=final_date)
timelines = timelines.filter(event_type="tasks.task.change")
print("Generating tasks indexed by id dict")
task_ids = timelines.values_list("object_id", flat=True)
tasks_per_id = {task.id: task for task in Task.objects.filter(id__in=task_ids).select_related("user_story").iterator()}
del task_ids
counter = 1
total = timelines.count()
print("Updating timelines")
for timeline in timelines.iterator():
print("%s/%s"%(counter, total))
task_id = timeline.object_id
task = tasks_per_id.get(task_id, None)
if not task:
counter += 1
continue
user_story = tasks_per_id[task_id].user_story
if not user_story:
counter += 1
continue
timeline.data["task"]["userstory"] = userstory_timeline(user_story)
timeline.save(update_fields=["data"])
counter += 1
class Command(BaseCommand):
help = 'Regenerate project timeline'
option_list = BaseCommand.option_list + (
make_option('--initial_date',
action='store',
dest='initial_date',
default=None,
help='Initial date for timeline update'),
) + (
make_option('--final_date',
action='store',
dest='final_date',
default=None,
help='Final date for timeline update'),
)
def handle(self, *args, **options):
debug_enabled = settings.DEBUG
if debug_enabled:
print("Please, execute this script only with DEBUG mode disabled (DEBUG=False)")
return
update_timeline(options["initial_date"], options["final_date"])

View File

@ -36,11 +36,6 @@ class Timeline(models.Model):
data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") data_content_type = models.ForeignKey(ContentType, related_name="data_timelines")
created = models.DateTimeField(default=timezone.now) created = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
if self.id:
raise ValidationError("Not modify allowed for timeline entries")
return super().save(*args, **kwargs)
class Meta: class Meta:
index_together = [('content_type', 'object_id', 'namespace'), ] index_together = [('content_type', 'object_id', 'namespace'), ]

View File

@ -76,10 +76,13 @@ def task_timeline(instance, extra_data={}):
"task": service.extract_task_info(instance), "task": service.extract_task_info(instance),
"project": service.extract_project_info(instance.project), "project": service.extract_project_info(instance.project),
} }
if instance.user_story:
result["task"]["userstory"] = service.extract_userstory_info(instance.user_story)
result.update(extra_data) result.update(extra_data)
return result return result
@register_timeline_implementation("wiki.wikipage", "create") @register_timeline_implementation("wiki.wikipage", "create")
@register_timeline_implementation("wiki.wikipage", "change") @register_timeline_implementation("wiki.wikipage", "change")
@register_timeline_implementation("wiki.wikipage", "delete") @register_timeline_implementation("wiki.wikipage", "delete")

View File

@ -16,12 +16,16 @@
from django.conf import settings from django.conf import settings
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin from django.contrib import admin
from .routers import router from .routers import router
from .contrib_routers import router as contrib_router from .contrib_routers import router as contrib_router
##############################################
# Default
##############################################
urlpatterns = [ urlpatterns = [
url(r'^api/v1/', include(router.urls)), url(r'^api/v1/', include(router.urls)),
url(r'^api/v1/', include(contrib_router.urls)), url(r'^api/v1/', include(contrib_router.urls)),
@ -29,21 +33,51 @@ urlpatterns = [
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
] ]
def mediafiles_urlpatterns(prefix): handler500 = "taiga.base.api.views.api_server_error"
"""
Method for serve media files with runserver.
"""
import re
from django.views.static import serve
return [
url(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), serve, ##############################################
{'document_root': settings.MEDIA_ROOT}) # Front sitemap
##############################################
if settings.FRONT_SITEMAP_ENABLED:
from django.contrib.sitemaps.views import index
from django.contrib.sitemaps.views import sitemap
from django.views.decorators.cache import cache_page
from taiga.front.sitemaps import sitemaps
urlpatterns += [
url(r"^front/sitemap\.xml$",
cache_page(settings.FRONT_SITEMAP_CACHE_TIMEOUT)(index),
{"sitemaps": sitemaps, 'sitemap_url_name': 'front-sitemap'},
name="front-sitemap-index"),
url(r"^front/sitemap-(?P<section>.+)\.xml$",
cache_page(settings.FRONT_SITEMAP_CACHE_TIMEOUT)(sitemap),
{"sitemaps": sitemaps},
name="front-sitemap")
] ]
##############################################
# Static and media files in debug mode
##############################################
if settings.DEBUG: if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
def mediafiles_urlpatterns(prefix):
"""
Method for serve media files with runserver.
"""
import re
from django.views.static import serve
return [
url(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), serve,
{'document_root': settings.MEDIA_ROOT})
]
# Hardcoded only for development server # Hardcoded only for development server
urlpatterns += staticfiles_urlpatterns(prefix="/static/") urlpatterns += staticfiles_urlpatterns(prefix="/static/")
urlpatterns += mediafiles_urlpatterns(prefix="/media/") urlpatterns += mediafiles_urlpatterns(prefix="/media/")
handler500 = "taiga.base.api.views.api_server_error"

View File

@ -17,7 +17,7 @@
import uuid import uuid
from django.apps import apps from django.apps import apps
from django.db.models import Q from django.db.models import Q, F
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.validators import validate_email from django.core.validators import validate_email
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -97,7 +97,8 @@ class UsersViewSet(ModelCrudViewSet):
self.check_permissions(request, 'contacts', user) self.check_permissions(request, 'contacts', user)
self.object_list = user_filters.ContactsFilterBackend().filter_queryset( self.object_list = user_filters.ContactsFilterBackend().filter_queryset(
user, request, self.get_queryset(), self) user, request, self.get_queryset(), self).extra(
select={"complete_user_name":"concat(full_name, username)"}).order_by("complete_user_name")
page = self.paginate_queryset(self.object_list) page = self.paginate_queryset(self.object_list)
if page is not None: if page is not None:
@ -111,7 +112,7 @@ class UsersViewSet(ModelCrudViewSet):
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
user = get_object_or_404(models.User, **kwargs) user = get_object_or_404(models.User, **kwargs)
self.check_permissions(request, "stats", user) self.check_permissions(request, "stats", user)
return response.Ok(services.get_stats_for_user(user)) return response.Ok(services.get_stats_for_user(user, request.user))
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def password_recovery(self, request, pk=None): def password_recovery(self, request, pk=None):

View File

@ -15,30 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps from django.apps import apps
from django.db.models import Q
from taiga.base.filters import PermissionBasedFilterBackend from taiga.base.filters import PermissionBasedFilterBackend
from . import services
class ContactsFilterBackend(PermissionBasedFilterBackend): class ContactsFilterBackend(PermissionBasedFilterBackend):
permission = "view_project"
def filter_queryset(self, user, request, queryset, view): def filter_queryset(self, user, request, queryset, view):
qs = queryset.filter(is_active=True) qs = queryset.filter(is_active=True)
Membership = apps.get_model('projects', 'Membership') project_ids = services.get_visible_project_ids(user, request.user)
memberships_qs = Membership.objects.filter(user=user) qs = qs.filter(memberships__project_id__in=project_ids)
# Authenticated
if request.user.is_authenticated():
# if super user we don't need to filter anything
if not request.user.is_superuser:
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) |
Q(is_owner=True))
# Anonymous
else:
memberships_qs = memberships_qs.filter(project__anon_permissions__contains=[self.permission])
projects_list = [membership.project_id for membership in memberships_qs]
qs = qs.filter(memberships__project_id__in=projects_list)
qs = qs.exclude(id=user.id) qs = qs.exclude(id=user.id)
return qs.distinct() return qs.distinct()

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0010_auto_20150414_0936'),
]
operations = [
migrations.AddField(
model_name='user',
name='theme',
field=models.CharField(null=True, blank=True, max_length=100, default='', verbose_name='default theme'),
preserve_default=True,
),
]

View File

@ -118,6 +118,8 @@ class User(AbstractBaseUser, PermissionsMixin):
date_joined = models.DateTimeField(_('date joined'), default=timezone.now) date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
lang = models.CharField(max_length=20, null=True, blank=True, default="", lang = models.CharField(max_length=20, null=True, blank=True, default="",
verbose_name=_("default language")) verbose_name=_("default language"))
theme = models.CharField(max_length=100, null=True, blank=True, default="",
verbose_name=_("default theme"))
timezone = models.CharField(max_length=20, null=True, blank=True, default="", timezone = models.CharField(max_length=20, null=True, blank=True, default="",
verbose_name=_("default timezone")) verbose_name=_("default timezone"))
colorize_tags = models.BooleanField(null=False, blank=True, default=False, colorize_tags = models.BooleanField(null=False, blank=True, default=False,
@ -167,6 +169,7 @@ class User(AbstractBaseUser, PermissionsMixin):
self.color = "" self.color = ""
self.bio = "" self.bio = ""
self.lang = "" self.lang = ""
self.theme = ""
self.timezone = "" self.timezone = ""
self.colorize_tags = True self.colorize_tags = True
self.token = None self.token = None

View File

@ -49,7 +49,7 @@ class UserSerializer(serializers.ModelSerializer):
# IMPORTANT: Maintain the UserAdminSerializer Meta up to date # IMPORTANT: Maintain the UserAdminSerializer Meta up to date
# with this info (including there the email) # with this info (including there the email)
fields = ("id", "username", "full_name", "full_name_display", fields = ("id", "username", "full_name", "full_name_display",
"color", "bio", "lang", "timezone", "is_active", "color", "bio", "lang", "theme", "timezone", "is_active",
"photo", "big_photo", "roles", "projects_with_me") "photo", "big_photo", "roles", "projects_with_me")
read_only_fields = ("id",) read_only_fields = ("id",)
@ -103,7 +103,7 @@ class UserAdminSerializer(UserSerializer):
# IMPORTANT: Maintain the UserSerializer Meta up to date # IMPORTANT: Maintain the UserSerializer Meta up to date
# with this info (including here the email) # with this info (including here the email)
fields = ("id", "username", "full_name", "full_name_display", "email", fields = ("id", "username", "full_name", "full_name_display", "email",
"color", "bio", "lang", "timezone", "is_active", "photo", "color", "bio", "lang", "theme", "timezone", "is_active", "photo",
"big_photo") "big_photo")
read_only_fields = ("id", "email") read_only_fields = ("id", "email")

View File

@ -89,21 +89,51 @@ def get_big_photo_or_gravatar_url(user):
else: else:
return get_gravatar_url(user.email, size=settings.DEFAULT_BIG_AVATAR_SIZE) return get_gravatar_url(user.email, size=settings.DEFAULT_BIG_AVATAR_SIZE)
def get_stats_for_user(user):
"""Get the user stats"""
project_ids = user.memberships.values_list("project__id", flat=True).distinct() def get_visible_project_ids(from_user, by_user):
total_num_projects = project_ids.count() """Calculate the project_ids from one user visible by another"""
roles = [_(r) for r in user.memberships.values_list("role__name", flat=True)] required_permissions = ["view_project"]
#Or condition for membership filtering, the basic one is the access to projects allowing anonymous visualization
member_perm_conditions = Q(project__anon_permissions__contains=required_permissions)
# Authenticated
if by_user.is_authenticated():
#Calculating the projects wich from_user user is member
by_user_project_ids = by_user.memberships.values_list("project__id", flat=True)
#Adding to the condition two OR situations:
#- The from user has a role that allows access to the project
#- The to user is the owner
member_perm_conditions |= \
Q(project__id__in=by_user_project_ids, role__permissions__contains=required_permissions) |\
Q(project__id__in=by_user_project_ids, is_owner=True)
Membership = apps.get_model('projects', 'Membership')
#Calculating the user memberships adding the permission filter for the by user
memberships_qs = Membership.objects.filter(member_perm_conditions, user=from_user)
project_ids = memberships_qs.values_list("project__id", flat=True)
return project_ids
def get_stats_for_user(from_user, by_user):
"""Get the user stats"""
project_ids = get_visible_project_ids(from_user, by_user)
total_num_projects = len(project_ids)
roles = [_(r) for r in from_user.memberships.filter(project__id__in=project_ids).values_list("role__name", flat=True)]
roles = list(set(roles)) roles = list(set(roles))
User = apps.get_model('users', 'User') User = apps.get_model('users', 'User')
total_num_contacts = User.objects.filter(memberships__project__id__in=project_ids)\ total_num_contacts = User.objects.filter(memberships__project__id__in=project_ids)\
.exclude(id=user.id)\ .exclude(id=from_user.id)\
.distinct()\ .distinct()\
.count() .count()
UserStory = apps.get_model('userstories', 'UserStory') UserStory = apps.get_model('userstories', 'UserStory')
total_num_closed_userstories = UserStory.objects.filter(is_closed=True, assigned_to=user).count() total_num_closed_userstories = UserStory.objects.filter(
is_closed=True,
project__id__in=project_ids,
assigned_to=from_user).count()
project_stats = { project_stats = {
'total_num_projects': total_num_projects, 'total_num_projects': total_num_projects,

View File

@ -46,3 +46,8 @@ def test_render_and_extract_mentions():
user = factories.UserFactory(username="user1", full_name="test") user = factories.UserFactory(username="user1", full_name="test")
(_, extracted) = render_and_extract(dummy_project, "**@user1**") (_, extracted) = render_and_extract(dummy_project, "**@user1**")
assert extracted['mentions'] == [user] assert extracted['mentions'] == [user]
def test_proccessor_valid_email():
result = render(dummy_project, "**beta.tester@taiga.io**")
expected_result = "<p><strong><a href=\"mailto:beta.tester@taiga.io\" target=\"_blank\">beta.tester@taiga.io</a></strong></p>"
assert result == expected_result

View File

@ -333,3 +333,26 @@ def test_valid_concurrent_save_for_task_different_fields(client):
data = {"version": 1, "description": "test 2"} data = {"version": 1, "description": "test 2"}
response = client.patch(url, json.dumps(data), content_type="application/json") response = client.patch(url, json.dumps(data), content_type="application/json")
assert response.status_code == 200 assert response.status_code == 200
def test_invalid_save_without_version_parameter(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
client.login(user)
mock_path = "taiga.projects.tasks.api.TaskViewSet.pre_conditions_on_save"
with patch(mock_path):
url = reverse("tasks-list")
data = {"subject": "test",
"project": project.id,
"status": f.TaskStatusFactory.create(project=project).id}
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
task_id = json.loads(response.content)["id"]
url = reverse("tasks-detail", args=(task_id,))
data = {"subject": "test 1"}
response = client.patch(url, json.dumps(data), content_type="application/json")
assert response.status_code == 400

View File

@ -214,6 +214,25 @@ def test_archived_filter(client):
assert len(json.loads(response.content)) == 1 assert len(json.loads(response.content)) == 1
def test_filter_by_multiple_status(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
f.UserStoryFactory.create(project=project)
us1 = f.UserStoryFactory.create(project=project)
us2 = f.UserStoryFactory.create(project=project)
client.login(user)
url = reverse("userstories-list")
url = "{}?status={},{}".format(reverse("userstories-list"), us1.status.id, us2.status.id)
data = {}
response = client.get(url, data)
assert len(json.loads(response.content)) == 2
def test_get_total_points(client): def test_get_total_points(client):
project = f.ProjectFactory.create() project = f.ProjectFactory.create()

View File

@ -57,13 +57,6 @@ def test_add_to_objects_timeline():
service.push_to_timeline(None, project, "test") service.push_to_timeline(None, project, "test")
def test_modify_created_timeline_entry():
timeline = Timeline()
timeline.pk = 3
with pytest.raises(ValidationError):
timeline.save()
def test_get_impl_key_from_model(): def test_get_impl_key_from_model():
assert service._get_impl_key_from_model(Timeline, "test") == "timeline.timeline.test" assert service._get_impl_key_from_model(Timeline, "test") == "timeline.timeline.test"
with pytest.raises(Exception): with pytest.raises(Exception):