diff --git a/.travis.yml b/.travis.yml index ebb26f00..6e37362c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ notifications: email: recipients: - jespinog@gmail.com - - andrei.antoukh@gmail.com - bameda@dbarragan.com on_success: change on_failure: change diff --git a/CHANGELOG.md b/CHANGELOG.md index 923eff1e..52393fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## 1.8.0 ??? (unreleased) ### Features +- Improve timeline resource. +- Add sitemap of taiga-front (the web client). - Search by reference (thanks to [@artlepool](https://github.com/artlepool)) - Add call 'by_username' to the API resource User diff --git a/settings/common.py b/settings/common.py index ee33a4ba..ba0d1254 100644 --- a/settings/common.py +++ b/settings/common.py @@ -257,6 +257,7 @@ INSTALLED_APPS = [ "django.contrib.messages", "django.contrib.admin", "django.contrib.staticfiles", + "django.contrib.sitemaps", "taiga.base", "taiga.base.api", @@ -449,9 +450,16 @@ BITBUCKET_VALID_ORIGIN_IPS = ["131.103.20.165", "131.103.20.166"] GITLAB_VALID_ORIGIN_IPS = [] EXPORTS_TTL = 60 * 60 * 24 # 24 hours + CELERY_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 * diff --git a/settings/local.py.example b/settings/local.py.example index 2bc48b45..a74baa27 100644 --- a/settings/local.py.example +++ b/settings/local.py.example @@ -62,3 +62,8 @@ DATABASES = { #GITHUB_API_URL = "https://api.github.com/" #GITHUB_API_CLIENT_ID = "yourgithubclientid" #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 diff --git a/taiga/base/fields.py b/taiga/base/fields.py index 63c7739a..07898ea2 100644 --- a/taiga/base/fields.py +++ b/taiga/base/fields.py @@ -49,6 +49,9 @@ class I18NJsonField(JsonField): def translate_values(self, d): i18n_d = {} + if d is None: + return d + for key, value in d.items(): if isinstance(value, dict): i18n_d[key] = self.translate_values(value) diff --git a/taiga/base/filters.py b/taiga/base/filters.py index 333b8125..208be999 100644 --- a/taiga/base/filters.py +++ b/taiga/base/filters.py @@ -364,6 +364,26 @@ class TagsFilter(FilterBackend): 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): def filter_queryset(self, request, queryset, view): q = request.QUERY_PARAMS.get('q', None) diff --git a/taiga/base/templates/emails/base-body-html.jinja b/taiga/base/templates/emails/base-body-html.jinja index 52a263f5..602bde96 100644 --- a/taiga/base/templates/emails/base-body-html.jinja +++ b/taiga/base/templates/emails/base-body-html.jinja @@ -121,7 +121,7 @@ .headerContent { text-align: center; - color:#b8b8b8 !important; + color:#8D8D8D !important; font-family: 'Open Sans', Arial, Helvetica; font-size:14px; margin-bottom:16px; diff --git a/taiga/base/templates/emails/hero-body-html.jinja b/taiga/base/templates/emails/hero-body-html.jinja index 349c3f2e..0439dcc8 100644 --- a/taiga/base/templates/emails/hero-body-html.jinja +++ b/taiga/base/templates/emails/hero-body-html.jinja @@ -60,7 +60,7 @@ * @style heading 2 */ h2{ - color: #b8b8b8 !important; + color: #8D8D8D !important; display:block; font-family: 'Open Sans', Arial; font-size:20px; diff --git a/taiga/base/templates/emails/updates-body-html.jinja b/taiga/base/templates/emails/updates-body-html.jinja index 4675b88a..86be9c92 100644 --- a/taiga/base/templates/emails/updates-body-html.jinja +++ b/taiga/base/templates/emails/updates-body-html.jinja @@ -121,7 +121,7 @@ .headerContent { text-align: center; - color:#b8b8b8 !important; + color:#8D8D8D !important; font-family: 'Open Sans', Arial, Helvetica; font-size:14px; margin-bottom:16px; @@ -418,14 +418,14 @@ {% for entry in history_entries%} {% if entry.comment %} - - - {% trans comment=mdrender(project, entry.comment) %} -

comment:

-

{{ comment }}

- {% endtrans %} - - + + + {% trans comment=mdrender(project, entry.comment) %} +

comment:

+

{{ comment }}

+ {% endtrans %} + + {% endif %} {% set changed_fields = entry.values_diff %} {% if changed_fields %} diff --git a/taiga/front/__init__.py b/taiga/front/__init__.py index 1b2d3535..e69de29b 100644 --- a/taiga/front/__init__.py +++ b/taiga/front/__init__.py @@ -1,52 +0,0 @@ -# Copyright (C) 2014 Andrey Antukh -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -from django_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) diff --git a/taiga/front/sitemaps/__init__.py b/taiga/front/sitemaps/__init__.py new file mode 100644 index 00000000..ba5da7bd --- /dev/null +++ b/taiga/front/sitemaps/__init__.py @@ -0,0 +1,60 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from 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) +]) diff --git a/taiga/front/sitemaps/base.py b/taiga/front/sitemaps/base.py new file mode 100644 index 00000000..83967f4d --- /dev/null +++ b/taiga/front/sitemaps/base.py @@ -0,0 +1,45 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/generics.py b/taiga/front/sitemaps/generics.py new file mode 100644 index 00000000..180c6eb0 --- /dev/null +++ b/taiga/front/sitemaps/generics.py @@ -0,0 +1,42 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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) + diff --git a/taiga/front/sitemaps/issues.py b/taiga/front/sitemaps/issues.py new file mode 100644 index 00000000..e4404138 --- /dev/null +++ b/taiga/front/sitemaps/issues.py @@ -0,0 +1,49 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/milestones.py b/taiga/front/sitemaps/milestones.py new file mode 100644 index 00000000..7dde324b --- /dev/null +++ b/taiga/front/sitemaps/milestones.py @@ -0,0 +1,51 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/projects.py b/taiga/front/sitemaps/projects.py new file mode 100644 index 00000000..bbbbfbb8 --- /dev/null +++ b/taiga/front/sitemaps/projects.py @@ -0,0 +1,154 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/tasks.py b/taiga/front/sitemaps/tasks.py new file mode 100644 index 00000000..264be4de --- /dev/null +++ b/taiga/front/sitemaps/tasks.py @@ -0,0 +1,49 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/users.py b/taiga/front/sitemaps/users.py new file mode 100644 index 00000000..c29420e0 --- /dev/null +++ b/taiga/front/sitemaps/users.py @@ -0,0 +1,44 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/userstories.py b/taiga/front/sitemaps/userstories.py new file mode 100644 index 00000000..da16d19b --- /dev/null +++ b/taiga/front/sitemaps/userstories.py @@ -0,0 +1,49 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/sitemaps/wiki.py b/taiga/front/sitemaps/wiki.py new file mode 100644 index 00000000..eeb96a58 --- /dev/null +++ b/taiga/front/sitemaps/wiki.py @@ -0,0 +1,52 @@ +# Copyright (C) 2015 David Barragán +# Copyright (C) 2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.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 diff --git a/taiga/front/templatetags/__init__.py b/taiga/front/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/front/templatetags/functions.py b/taiga/front/templatetags/functions.py new file mode 100644 index 00000000..9a510d50 --- /dev/null +++ b/taiga/front/templatetags/functions.py @@ -0,0 +1,34 @@ +# Copyright (C) 2015 Andrey Antukh +# Copyright (C) 2015 Jesús Espino +# Copyright (C) 2015 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from django_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) diff --git a/taiga/front/urls.py b/taiga/front/urls.py new file mode 100644 index 00000000..b377f655 --- /dev/null +++ b/taiga/front/urls.py @@ -0,0 +1,49 @@ +# Copyright (C) 2015 Andrey Antukh +# Copyright (C) 2015 Jesús Espino +# Copyright (C) 2015 David Barragán +# This program is free software: you can redistribute it and/or 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 . + + +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 +} + diff --git a/taiga/locale/ca/LC_MESSAGES/django.po b/taiga/locale/ca/LC_MESSAGES/django.po index 4765d917..e8a1a632 100644 --- a/taiga/locale/ca/LC_MESSAGES/django.po +++ b/taiga/locale/ca/LC_MESSAGES/django.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-25 17:31+0200\n" -"PO-Revision-Date: 2015-05-25 13:58+0000\n" +"POT-Creation-Date: 2015-06-09 09:47+0200\n" +"PO-Revision-Date: 2015-06-09 07:47+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: Catalan (http://www.transifex.com/projects/p/taiga-back/" "language/ca/)\n" @@ -435,16 +435,12 @@ msgstr "Actualitzacions" #, python-format msgid "" "\n" -"

comment:" +"

comment:" "

\n" -"

" +"

" "%(comment)s

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

Comentari:

\n" -"

%(comment)s

\n" -" " #: taiga/base/templates/emails/updates-body-text.jinja:6 #, python-format @@ -1082,7 +1078,7 @@ msgstr "Amo" #: taiga/projects/models.py:507 taiga/projects/models.py:538 #: 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/wiki/models.py:66 taiga/users/models.py:193 +#: taiga/projects/wiki/models.py:66 taiga/users/models.py:196 msgid "project" msgstr "Projecte" @@ -1126,7 +1122,7 @@ msgstr "Descripció" #: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:453 taiga/projects/models.py:476 #: 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" msgstr "Ordre" @@ -1148,7 +1144,7 @@ msgstr "" #: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:474 taiga/projects/models.py:497 #: 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" msgstr "Nom" @@ -1371,7 +1367,7 @@ msgstr "referència externa" #: 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: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" msgstr "slug" @@ -1421,7 +1417,7 @@ msgstr "email" msgid "create at" msgstr "" -#: taiga/projects/models.py:63 taiga/users/models.py:126 +#: taiga/projects/models.py:63 taiga/users/models.py:128 msgid "token" msgstr "token" @@ -2107,15 +2103,15 @@ msgstr "" msgid "The version must be an integer" msgstr "" -#: taiga/projects/occ/mixins.py:56 -msgid "The version is not valid" +#: taiga/projects/occ/mixins.py:58 +msgid "The version parameter is not valid" msgstr "" -#: taiga/projects/occ/mixins.py:72 +#: taiga/projects/occ/mixins.py:74 msgid "The version doesn't match with the current one" msgstr "" -#: taiga/projects/occ/mixins.py:92 +#: taiga/projects/occ/mixins.py:93 msgid "version" msgstr "Versió" @@ -2131,43 +2127,43 @@ msgstr "Aquest e-mail ja està en ús" msgid "Invalid role for the project" 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" msgstr "" -#: taiga/projects/serializers.py:400 +#: taiga/projects/serializers.py:402 msgid "Default options" msgstr "Opcions per defecte" -#: taiga/projects/serializers.py:401 +#: taiga/projects/serializers.py:403 msgid "User story's statuses" msgstr "Estatus d'històries d'usuari" -#: taiga/projects/serializers.py:402 +#: taiga/projects/serializers.py:404 msgid "Points" msgstr "Punts" -#: taiga/projects/serializers.py:403 +#: taiga/projects/serializers.py:405 msgid "Task's statuses" msgstr "Estatus de tasques" -#: taiga/projects/serializers.py:404 +#: taiga/projects/serializers.py:406 msgid "Issue's statuses" msgstr "Estatus d'incidéncies" -#: taiga/projects/serializers.py:405 +#: taiga/projects/serializers.py:407 msgid "Issue's types" msgstr "Tipus d'incidéncies" -#: taiga/projects/serializers.py:406 +#: taiga/projects/serializers.py:408 msgid "Priorities" msgstr "Prioritats" -#: taiga/projects/serializers.py:407 +#: taiga/projects/serializers.py:409 msgid "Severities" msgstr "Severitats" -#: taiga/projects/serializers.py:408 +#: taiga/projects/serializers.py:410 msgid "Roles" msgstr "Rols" @@ -2653,57 +2649,57 @@ msgstr "Permissos" msgid "Important dates" 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" msgstr "Nom d'usuari o email invàlid" -#: taiga/users/api.py:128 +#: taiga/users/api.py:140 msgid "Mail sended successful!" 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" msgstr "Token invàlid" -#: taiga/users/api.py:166 +#: taiga/users/api.py:178 msgid "Current password parameter needed" msgstr "Paràmetre de password actual requerit" -#: taiga/users/api.py:169 +#: taiga/users/api.py:181 msgid "New password parameter needed" 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" msgstr "Password invàlid, al menys 6 caràcters requerits" -#: taiga/users/api.py:175 +#: taiga/users/api.py:187 msgid "Invalid current password" msgstr "Password actual invàlid" -#: taiga/users/api.py:191 +#: taiga/users/api.py:203 msgid "Incomplete arguments" msgstr "Arguments incomplets." -#: taiga/users/api.py:196 +#: taiga/users/api.py:208 msgid "Invalid image format" msgstr "Format d'image invàlid" -#: taiga/users/api.py:249 +#: taiga/users/api.py:261 msgid "Duplicated email" msgstr "Email duplicat" -#: taiga/users/api.py:251 +#: taiga/users/api.py:263 msgid "Not valid email" 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 "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Invàlid. Estás segur que el token es correcte i que no l'has usat abans?" -#: taiga/users/api.py: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?" msgstr "Invàlid. Estás segur que el token es correcte?" @@ -2761,22 +2757,26 @@ msgid "default language" msgstr "llenguatge per defecte" #: taiga/users/models.py:122 +msgid "default theme" +msgstr "" + +#: taiga/users/models.py:124 msgid "default timezone" msgstr "zona horaria per defecte" -#: taiga/users/models.py:124 +#: taiga/users/models.py:126 msgid "colorize tags" msgstr "coloritza tags" -#: taiga/users/models.py:129 +#: taiga/users/models.py:131 msgid "email token" msgstr "token de correu" -#: taiga/users/models.py:131 +#: taiga/users/models.py:133 msgid "new email address" msgstr "nova adreça de correu" -#: taiga/users/models.py:185 +#: taiga/users/models.py:188 msgid "permissions" msgstr "permissos" diff --git a/taiga/locale/en/LC_MESSAGES/django.po b/taiga/locale/en/LC_MESSAGES/django.po index 51f4decc..54e7762a 100644 --- a/taiga/locale/en/LC_MESSAGES/django.po +++ b/taiga/locale/en/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 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" "Last-Translator: Taiga Dev Team \n" "Language-Team: Taiga Dev Team \n" @@ -427,11 +427,11 @@ msgstr "" #, python-format msgid "" "\n" -"

comment:" +"

comment:" "

\n" -"

" +"

" "%(comment)s

\n" -" " +" " msgstr "" #: 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/notifications/models.py:69 taiga/projects/tasks/models.py:41 #: 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" msgstr "" @@ -1095,7 +1095,7 @@ msgstr "" #: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:453 taiga/projects/models.py:476 #: 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" msgstr "" @@ -1117,7 +1117,7 @@ msgstr "" #: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:474 taiga/projects/models.py:497 #: 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" msgstr "" @@ -1340,7 +1340,7 @@ msgstr "" #: 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: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" msgstr "" @@ -1390,7 +1390,7 @@ msgstr "" msgid "create at" msgstr "" -#: taiga/projects/models.py:63 taiga/users/models.py:126 +#: taiga/projects/models.py:63 taiga/users/models.py:128 msgid "token" msgstr "" @@ -2070,15 +2070,15 @@ msgstr "" msgid "The version must be an integer" msgstr "" -#: taiga/projects/occ/mixins.py:56 -msgid "The version is not valid" +#: taiga/projects/occ/mixins.py:58 +msgid "The version parameter is not valid" msgstr "" -#: taiga/projects/occ/mixins.py:72 +#: taiga/projects/occ/mixins.py:74 msgid "The version doesn't match with the current one" msgstr "" -#: taiga/projects/occ/mixins.py:92 +#: taiga/projects/occ/mixins.py:93 msgid "version" msgstr "" @@ -2094,43 +2094,43 @@ msgstr "" msgid "Invalid role for the project" msgstr "" -#: taiga/projects/serializers.py:343 +#: taiga/projects/serializers.py:340 msgid "Total milestones must be major or equal to zero" msgstr "" -#: taiga/projects/serializers.py:400 +#: taiga/projects/serializers.py:402 msgid "Default options" msgstr "" -#: taiga/projects/serializers.py:401 +#: taiga/projects/serializers.py:403 msgid "User story's statuses" msgstr "" -#: taiga/projects/serializers.py:402 +#: taiga/projects/serializers.py:404 msgid "Points" msgstr "" -#: taiga/projects/serializers.py:403 +#: taiga/projects/serializers.py:405 msgid "Task's statuses" msgstr "" -#: taiga/projects/serializers.py:404 +#: taiga/projects/serializers.py:406 msgid "Issue's statuses" msgstr "" -#: taiga/projects/serializers.py:405 +#: taiga/projects/serializers.py:407 msgid "Issue's types" msgstr "" -#: taiga/projects/serializers.py:406 +#: taiga/projects/serializers.py:408 msgid "Priorities" msgstr "" -#: taiga/projects/serializers.py:407 +#: taiga/projects/serializers.py:409 msgid "Severities" msgstr "" -#: taiga/projects/serializers.py:408 +#: taiga/projects/serializers.py:410 msgid "Roles" msgstr "" @@ -2596,56 +2596,56 @@ msgstr "" msgid "Important dates" 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" msgstr "" -#: taiga/users/api.py:128 +#: taiga/users/api.py:140 msgid "Mail sended successful!" 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" msgstr "" -#: taiga/users/api.py:166 +#: taiga/users/api.py:178 msgid "Current password parameter needed" msgstr "" -#: taiga/users/api.py:169 +#: taiga/users/api.py:181 msgid "New password parameter needed" msgstr "" -#: taiga/users/api.py:172 +#: taiga/users/api.py:184 msgid "Invalid password length at least 6 charaters needed" msgstr "" -#: taiga/users/api.py:175 +#: taiga/users/api.py:187 msgid "Invalid current password" msgstr "" -#: taiga/users/api.py:191 +#: taiga/users/api.py:203 msgid "Incomplete arguments" msgstr "" -#: taiga/users/api.py:196 +#: taiga/users/api.py:208 msgid "Invalid image format" msgstr "" -#: taiga/users/api.py:249 +#: taiga/users/api.py:261 msgid "Duplicated email" msgstr "" -#: taiga/users/api.py:251 +#: taiga/users/api.py:263 msgid "Not valid email" msgstr "" -#: taiga/users/api.py:271 taiga/users/api.py:277 +#: taiga/users/api.py:283 taiga/users/api.py:289 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" 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?" msgstr "" @@ -2699,22 +2699,26 @@ msgid "default language" msgstr "" #: taiga/users/models.py:122 -msgid "default timezone" +msgid "default theme" msgstr "" #: taiga/users/models.py:124 +msgid "default timezone" +msgstr "" + +#: taiga/users/models.py:126 msgid "colorize tags" msgstr "" -#: taiga/users/models.py:129 +#: taiga/users/models.py:131 msgid "email token" msgstr "" -#: taiga/users/models.py:131 +#: taiga/users/models.py:133 msgid "new email address" msgstr "" -#: taiga/users/models.py:185 +#: taiga/users/models.py:188 msgid "permissions" msgstr "" diff --git a/taiga/locale/es/LC_MESSAGES/django.po b/taiga/locale/es/LC_MESSAGES/django.po index 1886e115..23f5842e 100644 --- a/taiga/locale/es/LC_MESSAGES/django.po +++ b/taiga/locale/es/LC_MESSAGES/django.po @@ -12,9 +12,9 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-25 17:31+0200\n" -"PO-Revision-Date: 2015-05-25 15:50+0000\n" -"Last-Translator: David Barragán \n" +"POT-Creation-Date: 2015-06-09 09:47+0200\n" +"PO-Revision-Date: 2015-06-09 07:47+0000\n" +"Last-Translator: Taiga Dev Team \n" "Language-Team: Spanish (http://www.transifex.com/projects/p/taiga-back/" "language/es/)\n" "MIME-Version: 1.0\n" @@ -471,15 +471,12 @@ msgstr "Actualizaciones" #, python-format msgid "" "\n" -"

comment:" +"

comment:" "

\n" -"

" +"

" "%(comment)s

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

comentario:

\n" -"

%(comment)s

" #: taiga/base/templates/emails/updates-body-text.jinja:6 #, python-format @@ -1220,7 +1217,7 @@ msgstr "Dueño" #: taiga/projects/models.py:507 taiga/projects/models.py:538 #: 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/wiki/models.py:66 taiga/users/models.py:193 +#: taiga/projects/wiki/models.py:66 taiga/users/models.py:196 msgid "project" msgstr "Proyecto" @@ -1264,7 +1261,7 @@ msgstr "descripción" #: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:453 taiga/projects/models.py:476 #: 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" msgstr "orden" @@ -1286,7 +1283,7 @@ msgstr "Talky" #: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:474 taiga/projects/models.py:497 #: 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" msgstr "nombre" @@ -1509,7 +1506,7 @@ msgstr "referencia externa" #: 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: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" msgstr "slug" @@ -1561,7 +1558,7 @@ msgstr "email" msgid "create at" 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" msgstr "token" @@ -2478,15 +2475,15 @@ msgstr "Los observadores tienen usuarios invalidos" msgid "The version must be an integer" msgstr "La versión debe ser un número entero" -#: taiga/projects/occ/mixins.py:56 -msgid "The version is not valid" -msgstr "La versión no es válida" +#: taiga/projects/occ/mixins.py:58 +msgid "The version parameter is not valid" +msgstr "" -#: taiga/projects/occ/mixins.py:72 +#: taiga/projects/occ/mixins.py:74 msgid "The version doesn't match with the current one" msgstr "Las version difiere de la actual" -#: taiga/projects/occ/mixins.py:92 +#: taiga/projects/occ/mixins.py:93 msgid "version" msgstr "versión" @@ -2503,43 +2500,43 @@ msgstr "La dirección de email ya está en uso." msgid "Invalid role for the project" 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" 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" msgstr "Opciones por defecto" -#: taiga/projects/serializers.py:401 +#: taiga/projects/serializers.py:403 msgid "User story's statuses" msgstr "Estados de historia de usuario" -#: taiga/projects/serializers.py:402 +#: taiga/projects/serializers.py:404 msgid "Points" msgstr "Puntos" -#: taiga/projects/serializers.py:403 +#: taiga/projects/serializers.py:405 msgid "Task's statuses" msgstr "Estado de tareas" -#: taiga/projects/serializers.py:404 +#: taiga/projects/serializers.py:406 msgid "Issue's statuses" msgstr "Estados de peticion" -#: taiga/projects/serializers.py:405 +#: taiga/projects/serializers.py:407 msgid "Issue's types" msgstr "Tipos de petición" -#: taiga/projects/serializers.py:406 +#: taiga/projects/serializers.py:408 msgid "Priorities" msgstr "Prioridades" -#: taiga/projects/serializers.py:407 +#: taiga/projects/serializers.py:409 msgid "Severities" msgstr "Gravedades" -#: taiga/projects/serializers.py:408 +#: taiga/projects/serializers.py:410 msgid "Roles" msgstr "Roles" @@ -3059,57 +3056,57 @@ msgstr "Permisos" msgid "Important dates" 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" msgstr "Nombre de usuario o email no válidos" -#: taiga/users/api.py:128 +#: taiga/users/api.py:140 msgid "Mail sended successful!" 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" msgstr "token inválido" -#: taiga/users/api.py:166 +#: taiga/users/api.py:178 msgid "Current password parameter needed" msgstr "La contraseña actual es obligatoria." -#: taiga/users/api.py:169 +#: taiga/users/api.py:181 msgid "New password parameter needed" 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" 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" msgstr "Contraseña actual inválida" -#: taiga/users/api.py:191 +#: taiga/users/api.py:203 msgid "Incomplete arguments" msgstr "Argumentos incompletos" -#: taiga/users/api.py:196 +#: taiga/users/api.py:208 msgid "Invalid image format" msgstr "Formato de imagen no válido" -#: taiga/users/api.py:249 +#: taiga/users/api.py:261 msgid "Duplicated email" msgstr "Email duplicado" -#: taiga/users/api.py:251 +#: taiga/users/api.py:263 msgid "Not valid email" 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 "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Invalido, ¿estás seguro de que el token es correcto y no se ha usado antes?" -#: taiga/users/api.py: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?" msgstr "Inválido, ¿estás seguro de que el token es correcto?" @@ -3167,22 +3164,26 @@ msgid "default language" msgstr "idioma por defecto" #: taiga/users/models.py:122 +msgid "default theme" +msgstr "" + +#: taiga/users/models.py:124 msgid "default timezone" msgstr "zona horaria por defecto" -#: taiga/users/models.py:124 +#: taiga/users/models.py:126 msgid "colorize tags" msgstr "añade color a las etiquetas" -#: taiga/users/models.py:129 +#: taiga/users/models.py:131 msgid "email token" msgstr "token de email" -#: taiga/users/models.py:131 +#: taiga/users/models.py:133 msgid "new email address" msgstr "nueva dirección de email" -#: taiga/users/models.py:185 +#: taiga/users/models.py:188 msgid "permissions" msgstr "permisos" diff --git a/taiga/locale/fi/LC_MESSAGES/django.po b/taiga/locale/fi/LC_MESSAGES/django.po index cad130a6..be67efcc 100644 --- a/taiga/locale/fi/LC_MESSAGES/django.po +++ b/taiga/locale/fi/LC_MESSAGES/django.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-25 17:31+0200\n" -"PO-Revision-Date: 2015-05-25 15:44+0000\n" -"Last-Translator: David Barragán \n" +"POT-Creation-Date: 2015-06-09 09:47+0200\n" +"PO-Revision-Date: 2015-06-09 07:47+0000\n" +"Last-Translator: Taiga Dev Team \n" "Language-Team: Finnish (http://www.transifex.com/projects/p/taiga-back/" "language/fi/)\n" "MIME-Version: 1.0\n" @@ -461,15 +461,12 @@ msgstr "Päivityksiä" #, python-format msgid "" "\n" -"

comment:" +"

comment:" "

\n" -"

" +"

" "%(comment)s

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

kommentti:

\n" -"

%(comment)s

" #: taiga/base/templates/emails/updates-body-text.jinja:6 #, python-format @@ -1210,7 +1207,7 @@ msgstr "omistaja" #: taiga/projects/models.py:507 taiga/projects/models.py:538 #: 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/wiki/models.py:66 taiga/users/models.py:193 +#: taiga/projects/wiki/models.py:66 taiga/users/models.py:196 msgid "project" msgstr "projekti" @@ -1254,7 +1251,7 @@ msgstr "kuvaus" #: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:453 taiga/projects/models.py:476 #: 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" msgstr "order" @@ -1276,7 +1273,7 @@ msgstr "Talky" #: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:474 taiga/projects/models.py:497 #: 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" msgstr "nimi" @@ -1499,7 +1496,7 @@ msgstr "ulkoinen viittaus" #: 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: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" msgstr "hukka-aika" @@ -1549,7 +1546,7 @@ msgstr "sähköposti" msgid "create at" 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" msgstr "tunniste" @@ -2475,15 +2472,15 @@ msgstr "Vahdit sisältävät virheellisiä käyttäjiä" msgid "The version must be an integer" msgstr "Versio pitää olla kokonaisluku" -#: taiga/projects/occ/mixins.py:56 -msgid "The version is not valid" -msgstr "Versio on virheellinen" +#: taiga/projects/occ/mixins.py:58 +msgid "The version parameter is not valid" +msgstr "" -#: taiga/projects/occ/mixins.py:72 +#: taiga/projects/occ/mixins.py:74 msgid "The version doesn't match with the current one" msgstr "Versio ei ole sama kuin nykyinen" -#: taiga/projects/occ/mixins.py:92 +#: taiga/projects/occ/mixins.py:93 msgid "version" msgstr "versio" @@ -2499,43 +2496,43 @@ msgstr "Sähköpostiosoite on jo käytössä" msgid "Invalid role for the project" msgstr "Virheellinen rooli projektille" -#: taiga/projects/serializers.py:343 +#: taiga/projects/serializers.py:340 msgid "Total milestones must be major or equal to zero" msgstr "Virstapylväitä yhteensä pitää olla vähintään 0." -#: taiga/projects/serializers.py:400 +#: taiga/projects/serializers.py:402 msgid "Default options" msgstr "Oletusoptiot" -#: taiga/projects/serializers.py:401 +#: taiga/projects/serializers.py:403 msgid "User story's statuses" msgstr "Käyttäjätarinatilat" -#: taiga/projects/serializers.py:402 +#: taiga/projects/serializers.py:404 msgid "Points" msgstr "Pisteet" -#: taiga/projects/serializers.py:403 +#: taiga/projects/serializers.py:405 msgid "Task's statuses" msgstr "Tehtävien tilat" -#: taiga/projects/serializers.py:404 +#: taiga/projects/serializers.py:406 msgid "Issue's statuses" msgstr "Pyyntöjen tilat" -#: taiga/projects/serializers.py:405 +#: taiga/projects/serializers.py:407 msgid "Issue's types" msgstr "pyyntötyypit" -#: taiga/projects/serializers.py:406 +#: taiga/projects/serializers.py:408 msgid "Priorities" msgstr "Kiireellisyydet" -#: taiga/projects/serializers.py:407 +#: taiga/projects/serializers.py:409 msgid "Severities" msgstr "Vakavuudet" -#: taiga/projects/serializers.py:408 +#: taiga/projects/serializers.py:410 msgid "Roles" msgstr "Roolit" @@ -3052,58 +3049,58 @@ msgstr "Oikeudet" msgid "Important dates" 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" msgstr "Tuntematon käyttäjänimi tai sähköposti" -#: taiga/users/api.py:128 +#: taiga/users/api.py:140 msgid "Mail sended successful!" 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" msgstr "Tunniste on virheellinen" -#: taiga/users/api.py:166 +#: taiga/users/api.py:178 msgid "Current password parameter needed" msgstr "Nykyinen salasanaparametri tarvitaan" -#: taiga/users/api.py:169 +#: taiga/users/api.py:181 msgid "New password parameter needed" msgstr "Uusi salasanaparametri tarvitaan" -#: taiga/users/api.py:172 +#: taiga/users/api.py:184 msgid "Invalid password length at least 6 charaters needed" msgstr "Salasanan pitää olla vähintään 6 merkkiä pitkä" -#: taiga/users/api.py:175 +#: taiga/users/api.py:187 msgid "Invalid current password" msgstr "Virheellinen nykyinen salasana" -#: taiga/users/api.py:191 +#: taiga/users/api.py:203 msgid "Incomplete arguments" msgstr "Puutteelliset argumentit" -#: taiga/users/api.py:196 +#: taiga/users/api.py:208 msgid "Invalid image format" msgstr "Väärä kuvaformaatti" -#: taiga/users/api.py:249 +#: taiga/users/api.py:261 msgid "Duplicated email" msgstr "Sähköposti on jo olemassa" -#: taiga/users/api.py:251 +#: taiga/users/api.py:263 msgid "Not valid email" 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 "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Virheellinen. Oletko varma, että tunniste on oikea ja et ole jo käyttänyt " "sitä?" -#: taiga/users/api.py: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?" msgstr "Virheellinen, oletko varma että tunniste on oikea?" @@ -3161,22 +3158,26 @@ msgid "default language" msgstr "oletuskieli" #: taiga/users/models.py:122 +msgid "default theme" +msgstr "" + +#: taiga/users/models.py:124 msgid "default timezone" msgstr "oletus aikavyöhyke" -#: taiga/users/models.py:124 +#: taiga/users/models.py:126 msgid "colorize tags" msgstr "väritä avainsanat" -#: taiga/users/models.py:129 +#: taiga/users/models.py:131 msgid "email token" msgstr "sähköpostitunniste" -#: taiga/users/models.py:131 +#: taiga/users/models.py:133 msgid "new email address" msgstr "uusi sähköpostiosoite" -#: taiga/users/models.py:185 +#: taiga/users/models.py:188 msgid "permissions" msgstr "oikeudet" diff --git a/taiga/locale/fr/LC_MESSAGES/django.po b/taiga/locale/fr/LC_MESSAGES/django.po index 7dbe32d4..a4481c97 100644 --- a/taiga/locale/fr/LC_MESSAGES/django.po +++ b/taiga/locale/fr/LC_MESSAGES/django.po @@ -5,15 +5,17 @@ # Translators: # Alain Poirier , 2015 # David Barragán , 2015 +# Florent B. , 2015 # Louis-Michel Couture , 2015 +# Matthieu Durocher , 2015 # Stéphane Mor , 2015 # William Godin , 2015 msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-25 17:31+0200\n" -"PO-Revision-Date: 2015-05-25 13:58+0000\n" +"POT-Creation-Date: 2015-06-09 09:47+0200\n" +"PO-Revision-Date: 2015-06-09 07:47+0000\n" "Last-Translator: Taiga Dev Team \n" "Language-Team: French (http://www.transifex.com/projects/p/taiga-back/" "language/fr/)\n" @@ -142,7 +144,7 @@ msgstr "" #: taiga/base/api/fields.py:860 #, python-format 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 msgid "Enter a number." @@ -225,7 +227,7 @@ msgstr "Type incorrect. Valeur pk attendue, %s reçu." #: taiga/base/api/relations.py:310 #, python-format 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 msgid "Invalid hyperlink - No URL match" @@ -461,15 +463,12 @@ msgstr "Updates" #, python-format msgid "" "\n" -"

comment:" +"

comment:" "

\n" -"

" +"

" "%(comment)s

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

commentaire:

\n" -"

%(comment)s

" #: taiga/base/templates/emails/updates-body-text.jinja:6 #, python-format @@ -484,7 +483,7 @@ msgstr "" #: taiga/export_import/api.py:103 msgid "We needed at least one role" -msgstr "" +msgstr "Veuillez sélectionner au moins un rôle." #: taiga/export_import/api.py:197 msgid "Needed dump file" @@ -496,15 +495,16 @@ msgstr "Format de dump invalide" #: taiga/export_import/dump_service.py:96 msgid "error importing project data" -msgstr "" +msgstr "Erreur lors de l'importation de données" #: taiga/export_import/dump_service.py:109 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 msgid "error importing default project attributes values" msgstr "" +"erreur lors de l'importation des valeurs par défaut des attributs de projet" #: taiga/export_import/dump_service.py:124 msgid "error importing custom attributes" @@ -520,7 +520,7 @@ msgstr "Erreur à l'importation des groupes d'utilisateurs" #: taiga/export_import/dump_service.py:149 msgid "error importing sprints" -msgstr "" +msgstr "Erreur lors de l'importation des sprints." #: taiga/export_import/dump_service.py:154 msgid "error importing wiki pages" @@ -540,11 +540,11 @@ msgstr "erreur à l'importation des histoires utilisateur" #: taiga/export_import/dump_service.py:174 msgid "error importing tasks" -msgstr "" +msgstr "Erreur lors de l'importation des tâches." #: taiga/export_import/dump_service.py:179 msgid "error importing tags" -msgstr "" +msgstr "erreur lors de l'importation des mots-clés" #: taiga/export_import/dump_service.py:183 msgid "error importing timelines" @@ -870,7 +870,7 @@ msgstr "" #: taiga/hooks/github/event_hooks.py:169 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 msgid "Invalid issue comment information" @@ -1106,7 +1106,7 @@ msgstr "propriétaire" #: taiga/projects/models.py:507 taiga/projects/models.py:538 #: 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/wiki/models.py:66 taiga/users/models.py:193 +#: taiga/projects/wiki/models.py:66 taiga/users/models.py:196 msgid "project" msgstr "projet" @@ -1150,7 +1150,7 @@ msgstr "description" #: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:453 taiga/projects/models.py:476 #: 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" msgstr "ordre" @@ -1172,7 +1172,7 @@ msgstr "Talky" #: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:474 taiga/projects/models.py:497 #: 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" msgstr "nom" @@ -1395,7 +1395,7 @@ msgstr "référence externe" #: 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: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" msgstr "slug" @@ -1443,9 +1443,9 @@ msgstr "email" #: taiga/projects/models.py:61 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" msgstr "jeton" @@ -2131,15 +2131,15 @@ msgstr "La liste des observateurs contient des utilisateurs invalides" msgid "The version must be an integer" msgstr "La version doit être un nombre entier" -#: taiga/projects/occ/mixins.py:56 -msgid "The version is not valid" -msgstr "La version n'est pas valide" +#: taiga/projects/occ/mixins.py:58 +msgid "The version parameter is not valid" +msgstr "" -#: taiga/projects/occ/mixins.py:72 +#: taiga/projects/occ/mixins.py:74 msgid "The version doesn't match with the current one" msgstr "La version ne correspond pas à la version courante" -#: taiga/projects/occ/mixins.py:92 +#: taiga/projects/occ/mixins.py:93 msgid "version" msgstr "version" @@ -2156,43 +2156,43 @@ msgstr "Adresse email déjà existante" msgid "Invalid role for the project" 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" 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" msgstr "Options par défaut" -#: taiga/projects/serializers.py:401 +#: taiga/projects/serializers.py:403 msgid "User story's statuses" msgstr "Etats de la User Story" -#: taiga/projects/serializers.py:402 +#: taiga/projects/serializers.py:404 msgid "Points" msgstr "Points" -#: taiga/projects/serializers.py:403 +#: taiga/projects/serializers.py:405 msgid "Task's statuses" msgstr "Etats des tâches" -#: taiga/projects/serializers.py:404 +#: taiga/projects/serializers.py:406 msgid "Issue's statuses" msgstr "Statuts des problèmes" -#: taiga/projects/serializers.py:405 +#: taiga/projects/serializers.py:407 msgid "Issue's types" msgstr "Types de problèmes" -#: taiga/projects/serializers.py:406 +#: taiga/projects/serializers.py:408 msgid "Priorities" msgstr "Priorités" -#: taiga/projects/serializers.py:407 +#: taiga/projects/serializers.py:409 msgid "Severities" msgstr "Sévérités" -#: taiga/projects/serializers.py:408 +#: taiga/projects/serializers.py:410 msgid "Roles" msgstr "Rôles" @@ -2689,58 +2689,58 @@ msgstr "Permissions" msgid "Important dates" 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" msgstr "Nom d'utilisateur ou email non valide" -#: taiga/users/api.py:128 +#: taiga/users/api.py:140 msgid "Mail sended successful!" 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" msgstr "Jeton invalide" -#: taiga/users/api.py:166 +#: taiga/users/api.py:178 msgid "Current password parameter needed" msgstr "Paramètre 'mot de passe actuel' requis" -#: taiga/users/api.py:169 +#: taiga/users/api.py:181 msgid "New password parameter needed" 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" 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" msgstr "Mot de passe actuel incorrect" -#: taiga/users/api.py:191 +#: taiga/users/api.py:203 msgid "Incomplete arguments" msgstr "arguments manquants" -#: taiga/users/api.py:196 +#: taiga/users/api.py:208 msgid "Invalid image format" msgstr "format de l'image non valide" -#: taiga/users/api.py:249 +#: taiga/users/api.py:261 msgid "Duplicated email" msgstr "Email dupliquée" -#: taiga/users/api.py:251 +#: taiga/users/api.py:263 msgid "Not valid email" 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 "" "Invalid, are you sure the token is correct and you didn't use it before?" msgstr "" "Invalide, êtes-vous sûre que le jeton est correct et qu'il n'a pas déjà été " "utilisé ?" -#: taiga/users/api.py: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?" msgstr "Invalide, êtes-vous sûre que le jeton est correct ?" @@ -2799,22 +2799,26 @@ msgid "default language" msgstr "langage par défaut" #: taiga/users/models.py:122 +msgid "default theme" +msgstr "" + +#: taiga/users/models.py:124 msgid "default timezone" msgstr "Fuseau horaire par défaut" -#: taiga/users/models.py:124 +#: taiga/users/models.py:126 msgid "colorize tags" msgstr "changer la couleur des tags" -#: taiga/users/models.py:129 +#: taiga/users/models.py:131 msgid "email token" msgstr "jeton email" -#: taiga/users/models.py:131 +#: taiga/users/models.py:133 msgid "new email address" msgstr "nouvelle adresse email" -#: taiga/users/models.py:185 +#: taiga/users/models.py:188 msgid "permissions" msgstr "permissions" diff --git a/taiga/locale/zh-Hant/LC_MESSAGES/django.po b/taiga/locale/zh-Hant/LC_MESSAGES/django.po index 4a887a52..39db28aa 100644 --- a/taiga/locale/zh-Hant/LC_MESSAGES/django.po +++ b/taiga/locale/zh-Hant/LC_MESSAGES/django.po @@ -11,9 +11,9 @@ msgid "" msgstr "" "Project-Id-Version: taiga-back\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-25 17:31+0200\n" -"PO-Revision-Date: 2015-05-25 15:44+0000\n" -"Last-Translator: David Barragán \n" +"POT-Creation-Date: 2015-06-09 09:47+0200\n" +"PO-Revision-Date: 2015-06-09 07:47+0000\n" +"Last-Translator: Taiga Dev Team \n" "Language-Team: Chinese Traditional (http://www.transifex.com/projects/p/" "taiga-back/language/zh-Hant/)\n" "MIME-Version: 1.0\n" @@ -463,15 +463,12 @@ msgstr "更新" #, python-format msgid "" "\n" -"

comment:" +"

comment:" "

\n" -"

" +"

" "%(comment)s

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

評論:

\n" -"

%(comment)s

" #: taiga/base/templates/emails/updates-body-text.jinja:6 #, python-format @@ -1205,7 +1202,7 @@ msgstr "所有者" #: taiga/projects/models.py:507 taiga/projects/models.py:538 #: 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/wiki/models.py:66 taiga/users/models.py:193 +#: taiga/projects/wiki/models.py:66 taiga/users/models.py:196 msgid "project" msgstr "專案" @@ -1249,7 +1246,7 @@ msgstr "描述" #: taiga/projects/models.py:391 taiga/projects/models.py:418 #: taiga/projects/models.py:453 taiga/projects/models.py:476 #: 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" msgstr "次序" @@ -1271,7 +1268,7 @@ msgstr "Talky" #: taiga/projects/models.py:414 taiga/projects/models.py:451 #: taiga/projects/models.py:474 taiga/projects/models.py:497 #: 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" msgstr "姓名" @@ -1494,7 +1491,7 @@ msgstr "外部參考" #: 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: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" msgstr "代稱" @@ -1544,7 +1541,7 @@ msgstr "電子郵件" msgid "create at" msgstr "創建於" -#: taiga/projects/models.py:63 taiga/users/models.py:126 +#: taiga/projects/models.py:63 taiga/users/models.py:128 msgid "token" msgstr "代號" @@ -2474,15 +2471,15 @@ msgstr "監督者包含無效使用者" msgid "The version must be an integer" msgstr "版本須為整數值 " -#: taiga/projects/occ/mixins.py:56 -msgid "The version is not valid" -msgstr "版本無效" +#: taiga/projects/occ/mixins.py:58 +msgid "The version parameter is not valid" +msgstr "" -#: taiga/projects/occ/mixins.py:72 +#: taiga/projects/occ/mixins.py:74 msgid "The version doesn't match with the current one" msgstr "版本與目前使用不相符" -#: taiga/projects/occ/mixins.py:92 +#: taiga/projects/occ/mixins.py:93 msgid "version" msgstr "版本" @@ -2498,43 +2495,43 @@ msgstr "電子郵件已使用" msgid "Invalid role for the project" msgstr "專案無效的角色" -#: taiga/projects/serializers.py:343 +#: taiga/projects/serializers.py:340 msgid "Total milestones must be major or equal to zero" msgstr "Kanban" -#: taiga/projects/serializers.py:400 +#: taiga/projects/serializers.py:402 msgid "Default options" msgstr "預設選項" -#: taiga/projects/serializers.py:401 +#: taiga/projects/serializers.py:403 msgid "User story's statuses" msgstr "使用者故事狀態" -#: taiga/projects/serializers.py:402 +#: taiga/projects/serializers.py:404 msgid "Points" msgstr "點數" -#: taiga/projects/serializers.py:403 +#: taiga/projects/serializers.py:405 msgid "Task's statuses" msgstr "任務狀態" -#: taiga/projects/serializers.py:404 +#: taiga/projects/serializers.py:406 msgid "Issue's statuses" msgstr "問題狀態" -#: taiga/projects/serializers.py:405 +#: taiga/projects/serializers.py:407 msgid "Issue's types" msgstr "問題類型" -#: taiga/projects/serializers.py:406 +#: taiga/projects/serializers.py:408 msgid "Priorities" msgstr "優先性" -#: taiga/projects/serializers.py:407 +#: taiga/projects/serializers.py:409 msgid "Severities" msgstr "嚴重性" -#: taiga/projects/serializers.py:408 +#: taiga/projects/serializers.py:410 msgid "Roles" msgstr "角色" @@ -3043,56 +3040,56 @@ msgstr "許可" msgid "Important dates" 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" msgstr "無效使用者或郵件" -#: taiga/users/api.py:128 +#: taiga/users/api.py:140 msgid "Mail sended successful!" 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" msgstr "代號無效" -#: taiga/users/api.py:166 +#: taiga/users/api.py:178 msgid "Current password parameter needed" msgstr "需要目前密碼之參數" -#: taiga/users/api.py:169 +#: taiga/users/api.py:181 msgid "New password parameter needed" msgstr "需要新密碼參數" -#: taiga/users/api.py:172 +#: taiga/users/api.py:184 msgid "Invalid password length at least 6 charaters needed" msgstr "無效密碼長度,至少需6個字元" -#: taiga/users/api.py:175 +#: taiga/users/api.py:187 msgid "Invalid current password" msgstr "無效密碼" -#: taiga/users/api.py:191 +#: taiga/users/api.py:203 msgid "Incomplete arguments" msgstr "不完整參數" -#: taiga/users/api.py:196 +#: taiga/users/api.py:208 msgid "Invalid image format" msgstr "無效的圖片檔案" -#: taiga/users/api.py:249 +#: taiga/users/api.py:261 msgid "Duplicated email" msgstr "複製電子郵件" -#: taiga/users/api.py:251 +#: taiga/users/api.py:263 msgid "Not valid email" msgstr "非有效電子郵性" -#: taiga/users/api.py:271 taiga/users/api.py:277 +#: taiga/users/api.py:283 taiga/users/api.py:289 msgid "" "Invalid, are you sure the token is correct and you didn't use it before?" 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?" msgstr "無效,請確認代號是否正確?" @@ -3146,22 +3143,26 @@ msgid "default language" msgstr "預設語言 " #: taiga/users/models.py:122 +msgid "default theme" +msgstr "" + +#: taiga/users/models.py:124 msgid "default timezone" msgstr "預設時區" -#: taiga/users/models.py:124 +#: taiga/users/models.py:126 msgid "colorize tags" msgstr "顏色標籤" -#: taiga/users/models.py:129 +#: taiga/users/models.py:131 msgid "email token" msgstr "電子郵件符號 " -#: taiga/users/models.py:131 +#: taiga/users/models.py:133 msgid "new email address" msgstr "新電子郵件地址" -#: taiga/users/models.py:185 +#: taiga/users/models.py:188 msgid "permissions" msgstr "許可" diff --git a/taiga/mdrender/extensions/mentions.py b/taiga/mdrender/extensions/mentions.py index 4c243910..0664bd94 100644 --- a/taiga/mdrender/extensions/mentions.py +++ b/taiga/mdrender/extensions/mentions.py @@ -25,22 +25,22 @@ from markdown.extensions import Extension from markdown.inlinepatterns import Pattern -from markdown.util import etree +from markdown.util import etree, AtomicString from taiga.users.models import User class MentionsExtension(Extension): 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.md = md - md.inlinePatterns.add('mentions', mentionsPattern, '_begin') + md.inlinePatterns.add('mentions', mentionsPattern, '_end') class MentionsPattern(Pattern): def handleMatch(self, m): - username = m.group(2) + username = m.group(3) try: user = User.objects.get(username=username) @@ -49,10 +49,11 @@ class MentionsPattern(Pattern): url = "/profile/{}".format(username) - link_text = "@{}".format(username) + link_text = "@{}".format(username) a = etree.Element('a') - a.text = link_text + a.text = AtomicString(link_text) + a.set('href', url) a.set('title', user.get_full_name()) a.set('class', "mention") diff --git a/taiga/mdrender/extensions/references.py b/taiga/mdrender/extensions/references.py index 28f81a50..d472d663 100644 --- a/taiga/mdrender/extensions/references.py +++ b/taiga/mdrender/extensions/references.py @@ -28,7 +28,7 @@ from markdown.inlinepatterns import Pattern from markdown.util import etree 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): diff --git a/taiga/mdrender/extensions/target_link.py b/taiga/mdrender/extensions/target_link.py index 26cc6a5f..992399ea 100644 --- a/taiga/mdrender/extensions/target_link.py +++ b/taiga/mdrender/extensions/target_link.py @@ -21,7 +21,7 @@ import markdown from markdown.treeprocessors import Treeprocessor -from taiga.front import resolve +from taiga.front.templatetags.functions import resolve class TargetBlankLinkExtension(markdown.Extension): diff --git a/taiga/mdrender/extensions/wikilinks.py b/taiga/mdrender/extensions/wikilinks.py index 1c16ae54..1fd703b3 100644 --- a/taiga/mdrender/extensions/wikilinks.py +++ b/taiga/mdrender/extensions/wikilinks.py @@ -21,7 +21,7 @@ from markdown.treeprocessors import Treeprocessor from markdown.util import etree -from taiga.front import resolve +from taiga.front.templatetags.functions import resolve from taiga.base.utils.slug import slugify import re diff --git a/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja b/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja index 88b56f41..4c99f755 100644 --- a/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja +++ b/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja @@ -96,7 +96,7 @@ {% set values_removed = lists_diff(values_from, values_to) %} - +

{{ verbose_name(obj_class, field_name) }}

diff --git a/taiga/projects/issues/api.py b/taiga/projects/issues/api.py index 9b05eb47..d2582223 100644 --- a/taiga/projects/issues/api.py +++ b/taiga/projects/issues/api.py @@ -110,7 +110,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, IssuesFilter, IssuesOrdering,) retrieve_exclude_filters = (IssuesFilter,) - filter_fields = ("project", "assigned_to", "status__is_closed", "watchers") + filter_fields = ("project", "status__is_closed", "watchers") order_by_fields = ("type", "severity", "status", diff --git a/taiga/projects/occ/mixins.py b/taiga/projects/occ/mixins.py index 27b8e78a..777a59b4 100644 --- a/taiga/projects/occ/mixins.py +++ b/taiga/projects/occ/mixins.py @@ -37,7 +37,9 @@ class OCCResourceMixin(object): return param_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: return False if current_version is not None and param_version > current_version: @@ -50,28 +52,27 @@ class OCCResourceMixin(object): if obj.id: current_version = type(obj).objects.model.objects.get(id=obj.id).version - # Extract param version - param_version = self._extract_param_version() - if not self._validate_param_version(param_version, current_version): - raise exc.WrongArguments({"version": _("The version is not valid")}) + # Extract param version + param_version = self._extract_param_version() + if not self._validate_param_version(param_version, current_version): + raise exc.WrongArguments({"version": _("The version parameter is not valid")}) - if current_version != param_version: - diff_versions = current_version - param_version + if current_version != param_version: + diff_versions = current_version - param_version - modifying_fields = set(self.request.DATA.keys()) - if "version" in modifying_fields: - modifying_fields.remove("version") + modifying_fields = set(self.request.DATA.keys()) + if "version" in modifying_fields: + modifying_fields.remove("version") - modified_fields = set(get_modified_fields(obj, diff_versions)) - if "version" in modifying_fields: - modified_fields.remove("version") + modified_fields = set(get_modified_fields(obj, diff_versions)) + if "version" in modifying_fields: + modified_fields.remove("version") - both_modified = modifying_fields & modified_fields + both_modified = modifying_fields & modified_fields - if both_modified: - raise exc.WrongArguments({"version": _("The version doesn't match with the current one")}) + if both_modified: + raise exc.WrongArguments({"version": _("The version doesn't match with the current one")}) - if obj.id: obj.version = models.F('version') + 1 def pre_save(self, obj): diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index 48791495..cedb54f2 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -305,7 +305,6 @@ class ProjectSerializer(serializers.ModelSerializer): my_permissions = serializers.SerializerMethodField("get_my_permissions") i_am_owner = serializers.SerializerMethodField("get_i_am_owner") tags_colors = TagsColorsField(required=False) - users = serializers.SerializerMethodField("get_users") total_closed_milestones = serializers.SerializerMethodField("get_total_closed_milestones") class Meta: @@ -328,9 +327,6 @@ class ProjectSerializer(serializers.ModelSerializer): return is_project_owner(self.context["request"].user, obj) return False - def get_users(self, obj): - return UserSerializer(obj.members.all(), many=True).data - def get_total_closed_milestones(self, obj): return obj.milestones.filter(closed=True).count() @@ -355,18 +351,20 @@ class ProjectDetailSerializer(ProjectSerializer): issue_types = IssueTypeSerializer(many=True, required=False) priorities = PrioritySerializer(many=True, required=False) # Issues severities = SeveritySerializer(many=True, required=False) + userstory_custom_attributes = UserStoryCustomAttributeSerializer(source="userstorycustomattributes", many=True, required=False) task_custom_attributes = TaskCustomAttributeSerializer(source="taskcustomattributes", many=True, required=False) issue_custom_attributes = IssueCustomAttributeSerializer(source="issuecustomattributes", many=True, required=False) + users = serializers.SerializerMethodField("get_users") def get_memberships(self, obj): 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") - serializer = ProjectMembershipSerializer(qs, many=True) return serializer.data @@ -374,6 +372,9 @@ class ProjectDetailSerializer(ProjectSerializer): serializer = ProjectRoleSerializer(obj.roles.all(), many=True) return serializer.data + def get_users(self, obj): + return UserSerializer(obj.members.all(), many=True).data + class ProjectDetailAdminSerializer(ProjectDetailSerializer): class Meta: diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py index 2e07719d..b3e6ed05 100644 --- a/taiga/projects/userstories/api.py +++ b/taiga/projects/userstories/api.py @@ -50,12 +50,13 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi list_serializer_class = serializers.UserStorySerializer permission_classes = (permissions.UserStoryPermission,) - filter_backends = (filters.CanViewUsFilterBackend, filters.TagsFilter, + filter_backends = (filters.StatusFilter, filters.CanViewUsFilterBackend, filters.TagsFilter, 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", - "status__is_closed", "watchers"] + "status__is_closed", "watchers", "is_closed"] order_by_fields = ["backlog_order", "sprint_order", "kanban_order"] # Specific filter used for filtering neighbor user stories diff --git a/taiga/timeline/management/commands/update_timeline_for_updated_tasks.py b/taiga/timeline/management/commands/update_timeline_for_updated_tasks.py new file mode 100644 index 00000000..15b42172 --- /dev/null +++ b/taiga/timeline/management/commands/update_timeline_for_updated_tasks.py @@ -0,0 +1,86 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from django.conf import settings +from django.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"]) diff --git a/taiga/timeline/models.py b/taiga/timeline/models.py index 52882850..5a070c49 100644 --- a/taiga/timeline/models.py +++ b/taiga/timeline/models.py @@ -36,11 +36,6 @@ class Timeline(models.Model): data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") 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: index_together = [('content_type', 'object_id', 'namespace'), ] diff --git a/taiga/timeline/timeline_implementations.py b/taiga/timeline/timeline_implementations.py index 69f5f83f..8a782ce1 100644 --- a/taiga/timeline/timeline_implementations.py +++ b/taiga/timeline/timeline_implementations.py @@ -76,10 +76,13 @@ def task_timeline(instance, extra_data={}): "task": service.extract_task_info(instance), "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) return result - @register_timeline_implementation("wiki.wikipage", "create") @register_timeline_implementation("wiki.wikipage", "change") @register_timeline_implementation("wiki.wikipage", "delete") diff --git a/taiga/urls.py b/taiga/urls.py index 90cea932..416da685 100644 --- a/taiga/urls.py +++ b/taiga/urls.py @@ -16,12 +16,16 @@ from django.conf import settings from django.conf.urls import patterns, include, url -from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib import admin from .routers import router from .contrib_routers import router as contrib_router + +############################################## +# Default +############################################## + urlpatterns = [ url(r'^api/v1/', include(router.urls)), url(r'^api/v1/', include(contrib_router.urls)), @@ -29,21 +33,51 @@ urlpatterns = [ url(r'^admin/', include(admin.site.urls)), ] -def mediafiles_urlpatterns(prefix): - """ - Method for serve media files with runserver. - """ - import re - from django.views.static import serve +handler500 = "taiga.base.api.views.api_server_error" - return [ - url(r'^%s(?P.*)$' % 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
.+)\.xml$", + cache_page(settings.FRONT_SITEMAP_CACHE_TIMEOUT)(sitemap), + {"sitemaps": sitemaps}, + name="front-sitemap") ] + +############################################## +# Static and media files in debug mode +############################################## + 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.*)$' % re.escape(prefix.lstrip('/')), serve, + {'document_root': settings.MEDIA_ROOT}) + ] + # Hardcoded only for development server urlpatterns += staticfiles_urlpatterns(prefix="/static/") urlpatterns += mediafiles_urlpatterns(prefix="/media/") - -handler500 = "taiga.base.api.views.api_server_error" diff --git a/taiga/users/api.py b/taiga/users/api.py index 487229d4..33ec137c 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -17,7 +17,7 @@ import uuid 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.core.validators import validate_email from django.core.exceptions import ValidationError @@ -97,7 +97,8 @@ class UsersViewSet(ModelCrudViewSet): self.check_permissions(request, 'contacts', user) 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) if page is not None: @@ -111,7 +112,7 @@ class UsersViewSet(ModelCrudViewSet): def stats(self, request, *args, **kwargs): user = get_object_or_404(models.User, **kwargs) 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"]) def password_recovery(self, request, pk=None): diff --git a/taiga/users/filters.py b/taiga/users/filters.py index 5e7ceb92..df7f04ae 100644 --- a/taiga/users/filters.py +++ b/taiga/users/filters.py @@ -15,30 +15,14 @@ # along with this program. If not, see . from django.apps import apps -from django.db.models import Q from taiga.base.filters import PermissionBasedFilterBackend +from . import services class ContactsFilterBackend(PermissionBasedFilterBackend): - permission = "view_project" - def filter_queryset(self, user, request, queryset, view): qs = queryset.filter(is_active=True) - Membership = apps.get_model('projects', 'Membership') - memberships_qs = Membership.objects.filter(user=user) - - # 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) + project_ids = services.get_visible_project_ids(user, request.user) + qs = qs.filter(memberships__project_id__in=project_ids) qs = qs.exclude(id=user.id) return qs.distinct() diff --git a/taiga/users/migrations/0011_user_theme.py b/taiga/users/migrations/0011_user_theme.py new file mode 100644 index 00000000..59f4daf0 --- /dev/null +++ b/taiga/users/migrations/0011_user_theme.py @@ -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, + ), + ] diff --git a/taiga/users/models.py b/taiga/users/models.py index dc4eb862..345115be 100644 --- a/taiga/users/models.py +++ b/taiga/users/models.py @@ -118,6 +118,8 @@ class User(AbstractBaseUser, PermissionsMixin): date_joined = models.DateTimeField(_('date joined'), default=timezone.now) lang = models.CharField(max_length=20, null=True, blank=True, default="", 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="", verbose_name=_("default timezone")) colorize_tags = models.BooleanField(null=False, blank=True, default=False, @@ -167,6 +169,7 @@ class User(AbstractBaseUser, PermissionsMixin): self.color = "" self.bio = "" self.lang = "" + self.theme = "" self.timezone = "" self.colorize_tags = True self.token = None diff --git a/taiga/users/serializers.py b/taiga/users/serializers.py index 7b6276b5..934c0677 100644 --- a/taiga/users/serializers.py +++ b/taiga/users/serializers.py @@ -49,7 +49,7 @@ class UserSerializer(serializers.ModelSerializer): # IMPORTANT: Maintain the UserAdminSerializer Meta up to date # with this info (including there the email) 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") read_only_fields = ("id",) @@ -103,7 +103,7 @@ class UserAdminSerializer(UserSerializer): # IMPORTANT: Maintain the UserSerializer Meta up to date # with this info (including here the 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") read_only_fields = ("id", "email") diff --git a/taiga/users/services.py b/taiga/users/services.py index d1f765bf..caf27226 100644 --- a/taiga/users/services.py +++ b/taiga/users/services.py @@ -89,21 +89,51 @@ def get_big_photo_or_gravatar_url(user): else: 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() - total_num_projects = project_ids.count() - roles = [_(r) for r in user.memberships.values_list("role__name", flat=True)] +def get_visible_project_ids(from_user, by_user): + """Calculate the project_ids from one user visible by another""" + 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)) + User = apps.get_model('users', 'User') total_num_contacts = User.objects.filter(memberships__project__id__in=project_ids)\ - .exclude(id=user.id)\ + .exclude(id=from_user.id)\ .distinct()\ .count() 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 = { 'total_num_projects': total_num_projects, diff --git a/tests/integration/test_mdrender.py b/tests/integration/test_mdrender.py index a5e076e8..3735eac2 100644 --- a/tests/integration/test_mdrender.py +++ b/tests/integration/test_mdrender.py @@ -46,3 +46,8 @@ def test_render_and_extract_mentions(): user = factories.UserFactory(username="user1", full_name="test") (_, extracted) = render_and_extract(dummy_project, "**@user1**") assert extracted['mentions'] == [user] + +def test_proccessor_valid_email(): + result = render(dummy_project, "**beta.tester@taiga.io**") + expected_result = "

beta.tester@taiga.io

" + assert result == expected_result diff --git a/tests/integration/test_occ.py b/tests/integration/test_occ.py index 0e6eb974..9826cf0e 100644 --- a/tests/integration/test_occ.py +++ b/tests/integration/test_occ.py @@ -333,3 +333,26 @@ def test_valid_concurrent_save_for_task_different_fields(client): data = {"version": 1, "description": "test 2"} response = client.patch(url, json.dumps(data), content_type="application/json") 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 diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index 8f26e8c7..9819d413 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -214,6 +214,25 @@ def test_archived_filter(client): 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): project = f.ProjectFactory.create() diff --git a/tests/unit/test_timeline.py b/tests/unit/test_timeline.py index 2a4c52ed..c580404e 100644 --- a/tests/unit/test_timeline.py +++ b/tests/unit/test_timeline.py @@ -57,13 +57,6 @@ def test_add_to_objects_timeline(): 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(): assert service._get_impl_key_from_model(Timeline, "test") == "timeline.timeline.test" with pytest.raises(Exception):