Compare commits
No commits in common. "master" and "4.0.1" have entirely different histories.
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -2,35 +2,6 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
## 4.1.0 (2019-02-04)
|
||||
|
||||
### Misc
|
||||
|
||||
- Fix Close sprints
|
||||
|
||||
### Features:
|
||||
|
||||
- Negative filters
|
||||
- Activate the Ukrainian language
|
||||
|
||||
## 4.0.4 (2019-01-15)
|
||||
|
||||
### Misc
|
||||
|
||||
- Minor bug fixes.
|
||||
|
||||
## 4.0.3 (2018-12-11)
|
||||
|
||||
### Misc
|
||||
|
||||
- Add extra requirements for oauthlib
|
||||
|
||||
## 4.0.2 (2018-12-04)
|
||||
|
||||
### Misc
|
||||
|
||||
- Update messages catalog.
|
||||
|
||||
## 4.0.1 (2018-11-28)
|
||||
|
||||
### Misc
|
||||
|
|
2
Pipfile
2
Pipfile
|
@ -31,7 +31,6 @@ pytz = "*"
|
|||
raven = "==6.1.0"
|
||||
redis = "==2.10.5"
|
||||
requests = "==2.20.0"
|
||||
requests_oauthlib = "*"
|
||||
serpy = "==0.1.1"
|
||||
webcolors = "==1.7"
|
||||
CairoSVG = "==2.0.3"
|
||||
|
@ -40,7 +39,6 @@ Markdown = "==3.0.1"
|
|||
Pillow = "==4.1.1"
|
||||
Unidecode = "==0.4.20"
|
||||
Pygments = "==2.2.0"
|
||||
oauthlib = {extras = ["signedtoken"],version = "*"}
|
||||
|
||||
[dev-packages]
|
||||
coverage = "*"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "2505838cdc4b5390130990ae63647a4447c5f146ab37ca8bf29f627091112cf7"
|
||||
"sha256": "888d57920f90a3bcb3ad23268eef74f7c2951caa473cab150774fe460ac797a5"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -40,9 +40,9 @@
|
|||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
"sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e"
|
||||
"sha256:ed65448da5877b5558f19d2f7f11f8355ea76b3e63e1c0a6059f47cfae5f1c84"
|
||||
],
|
||||
"version": "==3.5.0.5"
|
||||
"version": "==3.5.0.4"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
|
@ -75,10 +75,10 @@
|
|||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
|
||||
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
|
||||
],
|
||||
"version": "==2018.11.29"
|
||||
"version": "==2018.10.15"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
|
@ -498,13 +498,6 @@
|
|||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:00414bfef802aaecd8cc0d5258b6cb87bd8f553c2986c2c5f29b19dd5633aeb7",
|
||||
"sha256:ddec8409c57e9d371c6006e388f91daf3b0b43bdf9fcbf99451fb7cf5ce0a86d"
|
||||
],
|
||||
"version": "==1.7.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
|
||||
|
@ -641,10 +634,10 @@
|
|||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
|
||||
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
|
||||
],
|
||||
"version": "==2018.11.29"
|
||||
"version": "==2018.10.15"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
|
@ -714,10 +707,10 @@
|
|||
},
|
||||
"faker": {
|
||||
"hashes": [
|
||||
"sha256:c61a41d0dab8865b850bd00454fb11e90f3fd2a092d8bc90120d1e1c01cff906",
|
||||
"sha256:f909ff9133ce0625ca388b6838190630ad7a593f87eaf058d872338a76241d5d"
|
||||
"sha256:2621643b80a10b91999925cfd20f64d2b36f20bf22136bbdc749bb57d6ffe124",
|
||||
"sha256:5ed822d31bd2d6edf10944d176d30dc9c886afdd381eefb7ba8b7aad86171646"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
"version": "==0.9.2"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
amqp==2.3.2
|
||||
asana==0.6.7
|
||||
asn1crypto==0.24.0
|
||||
billiard==3.5.0.5
|
||||
billiard==3.5.0.4
|
||||
bleach==2.1.4
|
||||
cairocffi==0.9.0
|
||||
cairosvg==2.0.3
|
||||
celery==4.0.2
|
||||
certifi==2018.11.29
|
||||
certifi==2018.10.15
|
||||
cffi==1.11.5
|
||||
chardet==3.0.4
|
||||
contextlib2==0.5.5
|
||||
|
@ -44,7 +44,6 @@ psd-tools==1.4
|
|||
psycopg2-binary==2.7.5
|
||||
pycparser==2.19
|
||||
pygments==2.2.0
|
||||
pyjwt==1.7.0
|
||||
python-dateutil==2.7.5
|
||||
python-magic==0.4.13
|
||||
pytz==2018.7
|
||||
|
|
|
@ -148,7 +148,7 @@ LANGUAGES = [
|
|||
("tr", "Türkçe"), # Turkish
|
||||
#("tt", "татар теле"), # Tatar
|
||||
#("udm", "удмурт кыл"), # Udmurt
|
||||
("uk", "Українська"), # Ukrainian
|
||||
#("uk", "Українська"), # Ukrainian
|
||||
#("ur", "اردو"), # Urdu
|
||||
#("vi", "Tiếng Việt"), # Vietnamese
|
||||
("zh-hans", "中文(简体)"), # Simplified Chinese
|
||||
|
|
|
@ -375,18 +375,14 @@ class IsProjectAdminFromWebhookLogFilterBackend(FilterBackend, BaseIsProjectAdmi
|
|||
class BaseRelatedFieldsFilter(FilterBackend):
|
||||
filter_name = None
|
||||
param_name = None
|
||||
exclude_param_name = None
|
||||
|
||||
def __init__(self, filter_name=None, param_name=None, exclude_param_name=None):
|
||||
def __init__(self, filter_name=None, param_name=None):
|
||||
if filter_name:
|
||||
self.filter_name = filter_name
|
||||
|
||||
if param_name:
|
||||
self.param_name = param_name
|
||||
|
||||
if exclude_param_name:
|
||||
self.exclude_param_name
|
||||
|
||||
def _prepare_filter_data(self, query_param_value):
|
||||
def _transform_value(value):
|
||||
try:
|
||||
|
@ -400,57 +396,48 @@ class BaseRelatedFieldsFilter(FilterBackend):
|
|||
values = map(_transform_value, values)
|
||||
return list(values)
|
||||
|
||||
def _get_queryparams(self, params, mode=''):
|
||||
param_name = self.exclude_param_name if mode == 'exclude' else self.param_name or self.filter_name
|
||||
def _get_queryparams(self, params):
|
||||
param_name = self.param_name or self.filter_name
|
||||
raw_value = params.get(param_name, None)
|
||||
|
||||
if raw_value:
|
||||
value = self._prepare_filter_data(raw_value)
|
||||
|
||||
if None in value:
|
||||
qs_in_kwargs = {"{}__in".format(self.filter_name): [v for v in value if v is not None]}
|
||||
qs_isnull_kwargs = {"{}__isnull".format(self.filter_name): True}
|
||||
return Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs)
|
||||
else:
|
||||
return Q(**{"{}__in".format(self.filter_name): value})
|
||||
return {"{}__in".format(self.filter_name): value}
|
||||
|
||||
return None
|
||||
|
||||
def _prepare_filter_query(self, query):
|
||||
return query
|
||||
|
||||
def _prepare_exclude_query(self, query):
|
||||
return ~Q(query)
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
operations = {
|
||||
"filter": self._prepare_filter_query,
|
||||
"exclude": self._prepare_exclude_query,
|
||||
}
|
||||
|
||||
for mode, prepare_method in operations.items():
|
||||
query = self._get_queryparams(request.QUERY_PARAMS, mode=mode)
|
||||
if query:
|
||||
queryset = queryset.filter(prepare_method(query))
|
||||
query = self._get_queryparams(request.QUERY_PARAMS)
|
||||
if query:
|
||||
if isinstance(query, dict):
|
||||
queryset = queryset.filter(**query)
|
||||
else:
|
||||
queryset = queryset.filter(query)
|
||||
|
||||
return super().filter_queryset(request, queryset, view)
|
||||
|
||||
|
||||
class OwnersFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = 'owner'
|
||||
exclude_param_name = 'exclude_owner'
|
||||
|
||||
|
||||
class AssignedToFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = 'assigned_to'
|
||||
exclude_param_name = 'exclude_assigned_to'
|
||||
|
||||
|
||||
class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
||||
filter_name = 'assigned_users'
|
||||
exclude_param_name = 'exclude_assigned_users'
|
||||
|
||||
def _get_queryparams(self, params, mode=''):
|
||||
param_name = self.exclude_param_name if mode == 'exclude' else self.param_name or self.filter_name
|
||||
def _get_queryparams(self, params):
|
||||
param_name = self.param_name or self.filter_name
|
||||
raw_value = params.get(param_name, None)
|
||||
|
||||
if raw_value:
|
||||
value = self._prepare_filter_data(raw_value)
|
||||
UserStoryModel = apps.get_model("userstories", "UserStory")
|
||||
|
@ -474,65 +461,38 @@ class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
|||
|
||||
class StatusesFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = 'status'
|
||||
exclude_param_name = 'exclude_status'
|
||||
|
||||
|
||||
class IssueTypesFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = 'type'
|
||||
param_name = 'type'
|
||||
exclude_param_name = 'exclude_type'
|
||||
|
||||
|
||||
class PrioritiesFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = 'priority'
|
||||
exclude_param_name = 'exclude_priority'
|
||||
|
||||
|
||||
class SeveritiesFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = 'severity'
|
||||
exclude_param_name = 'exclude_severity'
|
||||
|
||||
|
||||
class TagsFilter(FilterBackend):
|
||||
filter_name = 'tags'
|
||||
exclude_param_name = 'exclude_tags'
|
||||
|
||||
def __init__(self, filter_name=None, exclude_param_name=None):
|
||||
def __init__(self, filter_name=None):
|
||||
if filter_name:
|
||||
self.filter_name = filter_name
|
||||
|
||||
if exclude_param_name:
|
||||
self.exclude_param_name = exclude_param_name
|
||||
|
||||
def _get_tags_queryparams(self, params, mode=''):
|
||||
param_name = self.exclude_param_name if mode == "exclude" else self.filter_name
|
||||
tags = params.get(param_name, None)
|
||||
def _get_tags_queryparams(self, params):
|
||||
tags = params.get(self.filter_name, None)
|
||||
if tags:
|
||||
return tags.split(",")
|
||||
|
||||
return None
|
||||
|
||||
def _prepare_filter_query(self, query):
|
||||
return Q(tags__contains=query)
|
||||
|
||||
def _prepare_exclude_query(self, tags):
|
||||
queries = [Q(tags__contains=[tag]) for tag in tags]
|
||||
query = queries.pop()
|
||||
for item in queries:
|
||||
query |= item
|
||||
|
||||
return ~Q(query)
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
operations = {
|
||||
"filter": self._prepare_filter_query,
|
||||
"exclude": self._prepare_exclude_query,
|
||||
}
|
||||
|
||||
for mode, prepare_method in operations.items():
|
||||
query = self._get_tags_queryparams(request.QUERY_PARAMS, mode=mode)
|
||||
if query:
|
||||
queryset = queryset.filter(prepare_method(query))
|
||||
query_tags = self._get_tags_queryparams(request.QUERY_PARAMS)
|
||||
if query_tags:
|
||||
queryset = queryset.filter(tags__contains=query_tags)
|
||||
|
||||
return super().filter_queryset(request, queryset, view)
|
||||
|
||||
|
@ -671,22 +631,18 @@ class QFilter(FilterBackend):
|
|||
class RoleFilter(BaseRelatedFieldsFilter):
|
||||
filter_name = "role_id"
|
||||
param_name = "role"
|
||||
exclude_param_name = "exclude_role"
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
Membership = apps.get_model('projects', 'Membership')
|
||||
|
||||
operations = {
|
||||
"filter": self._prepare_filter_query,
|
||||
"exclude": self._prepare_exclude_query,
|
||||
}
|
||||
|
||||
for mode, qs_method in operations.items():
|
||||
query = self._get_queryparams(request.QUERY_PARAMS, mode=mode)
|
||||
if query:
|
||||
memberships = Membership.objects.filter(query).exclude(user__isnull=True).values_list("user_id", flat=True)
|
||||
if memberships:
|
||||
queryset = queryset.filter(qs_method(Q(assigned_to__in=memberships)))
|
||||
query = self._get_queryparams(request.QUERY_PARAMS)
|
||||
if query:
|
||||
if isinstance(query, dict):
|
||||
memberships = Membership.objects.filter(**query).values_list("user_id", flat=True)
|
||||
queryset = queryset.filter(assigned_to__in=memberships)
|
||||
else:
|
||||
memberships = Membership.objects.filter(query).values_list("user_id", flat=True)
|
||||
if memberships:
|
||||
queryset = queryset.filter(assigned_to__in=memberships)
|
||||
|
||||
return FilterBackend.filter_queryset(self, request, queryset, view)
|
||||
|
||||
|
@ -694,24 +650,20 @@ class RoleFilter(BaseRelatedFieldsFilter):
|
|||
class UserStoriesRoleFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
||||
filter_name = "role_id"
|
||||
param_name = "role"
|
||||
exclude_param_name = 'exclude_role'
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
Membership = apps.get_model('projects', 'Membership')
|
||||
query = self._get_queryparams(request.QUERY_PARAMS)
|
||||
|
||||
operations = {
|
||||
"filter": self._prepare_filter_query,
|
||||
"exclude": self._prepare_exclude_query,
|
||||
}
|
||||
|
||||
for mode, qs_method in operations.items():
|
||||
query = self._get_queryparams(request.QUERY_PARAMS, mode=mode)
|
||||
if query:
|
||||
memberships = Membership.objects.filter(query).exclude(user__isnull=True).values_list("user_id", flat=True)
|
||||
if memberships:
|
||||
user_story_model = apps.get_model("userstories", "UserStory")
|
||||
queryset = queryset.filter(
|
||||
qs_method(Q(self.get_assigned_users_filter(user_story_model, memberships)))
|
||||
)
|
||||
if query:
|
||||
if isinstance(query, dict):
|
||||
memberships = Membership.objects.filter(**query).values_list("user_id", flat=True)
|
||||
else:
|
||||
memberships = Membership.objects.filter(query).values_list("user_id", flat=True)
|
||||
if memberships:
|
||||
user_story_model = apps.get_model("userstories", "UserStory")
|
||||
queryset = queryset.filter(
|
||||
self.get_assigned_users_filter(user_story_model, memberships)
|
||||
)
|
||||
|
||||
return FilterBackend.filter_queryset(self, request, queryset, view)
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Amirhoshang Hoseinpour Dehkordi <amir.hoseinpour@gmail.com>, 2018
|
||||
# Vahid Dayyani <vahid.dayyani@ymail.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: taiga-back\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||
"PO-Revision-Date: 2018-12-02 10:54+0000\n"
|
||||
"Last-Translator: Amirhoshang Hoseinpour Dehkordi <amir.hoseinpour@gmail."
|
||||
"com>\n"
|
||||
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
|
||||
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
|
||||
"Language-Team: Persian (Iran) (http://www.transifex.com/taiga-agile-llc/"
|
||||
"taiga-back/language/fa_IR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -28,7 +26,7 @@ msgstr "ثبت نام عمومی غیرفعال است."
|
|||
|
||||
#: taiga/auth/api.py:93
|
||||
msgid "You must accept our terms of service and privacy policy"
|
||||
msgstr "شما باید موارد سرویس و سیاست های امنیت ما را قبول کنید."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/auth/api.py:102
|
||||
msgid "invalid register type"
|
||||
|
@ -75,7 +73,7 @@ msgstr "نام کاربری نامعتبر"
|
|||
#: taiga/auth/validators.py:42 taiga/users/validators.py:50
|
||||
msgid ""
|
||||
"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'"
|
||||
msgstr "255 کاراکتر یا کمتر ضروری است. حروف و اعداد و . و - و ـ مجاز است."
|
||||
msgstr "ضروری است. ۲۵۵ کاراکتر یا کمتر. حروف و اعداد و . و - و ـ مجاز است."
|
||||
|
||||
#: taiga/base/api/fields.py:294
|
||||
msgid "This field is required."
|
||||
|
@ -88,7 +86,7 @@ msgstr "مقدار نامعتبر."
|
|||
#: taiga/base/api/fields.py:484
|
||||
#, python-format
|
||||
msgid "'%s' value must be either True or False."
|
||||
msgstr "'%s' میبایست صحیح یا غلط باشد."
|
||||
msgstr "'%s' میبایست True (صحیح) یا False (غلط) باشد."
|
||||
|
||||
#: taiga/base/api/fields.py:549
|
||||
msgid ""
|
||||
|
@ -519,91 +517,91 @@ msgstr ""
|
|||
|
||||
#: taiga/base/utils/urls.py:68
|
||||
msgid "Host access error"
|
||||
msgstr "خطای دسترسی به هاست"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/base/utils/urls.py:74
|
||||
msgid "IP Address error"
|
||||
msgstr "خطای آدرس آی پی"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:106
|
||||
msgid "User story created"
|
||||
msgstr "گزارش کاربر ساخته شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:109
|
||||
msgid "User story changed"
|
||||
msgstr "گزارش کاربر تغییر کرد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:112
|
||||
msgid "User story deleted"
|
||||
msgstr "گزارش کاربر حذف شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:114
|
||||
msgid "US #{} - {}"
|
||||
msgstr "US #{} - {}"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:117
|
||||
msgid "Task created"
|
||||
msgstr "وظیفه ساخته شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:120
|
||||
msgid "Task changed"
|
||||
msgstr "وظیفه تغییر کرد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:123
|
||||
msgid "Task deleted"
|
||||
msgstr "وظیفه حذف شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:125
|
||||
msgid "Task #{} - {}"
|
||||
msgstr "وظیفه #{} - {}"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:128
|
||||
msgid "Issue created"
|
||||
msgstr "موضوع ساخته شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:131
|
||||
msgid "Issue changed"
|
||||
msgstr "موضوع تغییر کرد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:134
|
||||
msgid "Issue deleted"
|
||||
msgstr "موضوع حذف شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:136
|
||||
msgid "Issue: #{} - {}"
|
||||
msgstr "موضوع: #{} - {}"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:139
|
||||
msgid "Wiki Page created"
|
||||
msgstr "صفحه ویکی ساخته شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:142
|
||||
msgid "Wiki Page changed"
|
||||
msgstr "صفحه ویکی تغییر داده شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:145
|
||||
msgid "Wiki Page deleted"
|
||||
msgstr "صفحه ویکی حذف شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:147
|
||||
msgid "Wiki Page: {}"
|
||||
msgstr "صفحه ویکی: {}"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:150
|
||||
msgid "Sprint created"
|
||||
msgstr "سرعتی ساخته شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:153
|
||||
msgid "Sprint changed"
|
||||
msgstr "سرعتی تغییر داده شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:156
|
||||
msgid "Sprint deleted"
|
||||
msgstr "سرعتی حذف شد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/events/events.py:158
|
||||
msgid "Sprint: {}"
|
||||
msgstr "سرعتی: {}"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/export_import/api.py:127
|
||||
msgid "We needed at least one role"
|
||||
|
@ -1784,27 +1782,27 @@ msgstr "کاربر میبایست قبلاً از اعضای پروژه بو
|
|||
|
||||
#: taiga/projects/api.py:642
|
||||
msgid "You can't delete user story due date by default"
|
||||
msgstr "شما سر رسید گزارش کاربر را نمی توانید بصورت عادی حذف کنید."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/api.py:658
|
||||
msgid "Project already have due dates"
|
||||
msgstr "پروژه موعد سر رسید دارد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/api.py:718
|
||||
msgid "You can't delete task due date by default"
|
||||
msgstr "شما سر رسید وظیفه کاربر را نمی توانید بصورت عادی حذف کنید."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/api.py:734
|
||||
msgid "Project already have task due dates"
|
||||
msgstr "پروژه موعد سر رسید وظیفه دارد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/api.py:858
|
||||
msgid "You can't delete issue due date by default"
|
||||
msgstr "شما سر رسید موضوع کاربر را نمی توانید بصورت عادی حذف کنید."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/api.py:874
|
||||
msgid "Project already have issue due dates"
|
||||
msgstr "پروژه موعد سر رسید موضوع دارد."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/api.py:1023
|
||||
msgid ""
|
||||
|
@ -2053,11 +2051,11 @@ msgstr "موردی با همین نام وجود دارد."
|
|||
|
||||
#: taiga/projects/due_dates/models.py:21
|
||||
msgid "due date"
|
||||
msgstr "موعد سر رسید"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/due_dates/models.py:24
|
||||
msgid "reason for the due date"
|
||||
msgstr "دلیل موعد سر رسید."
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/epics/api.py:94
|
||||
msgid "You don't have permissions to set this status to this epic."
|
||||
|
@ -2214,7 +2212,7 @@ msgstr "آزاد شد"
|
|||
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:164
|
||||
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:175
|
||||
msgid "Not set"
|
||||
msgstr "تنظیم نشده"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:286
|
||||
#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:91
|
||||
|
@ -2569,12 +2567,12 @@ msgstr "ارزش"
|
|||
#: taiga/projects/models.py:614 taiga/projects/models.py:671
|
||||
#: taiga/projects/models.py:789
|
||||
msgid "by default"
|
||||
msgstr "بصورت عمومی"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/models.py:618 taiga/projects/models.py:675
|
||||
#: taiga/projects/models.py:793
|
||||
msgid "days to due"
|
||||
msgstr "روز تا موعد"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/models.py:823
|
||||
msgid "default owner's role"
|
||||
|
@ -2594,7 +2592,7 @@ msgstr "وضعیتهای استوریهای کاربری"
|
|||
|
||||
#: taiga/projects/models.py:849
|
||||
msgid "us duedates"
|
||||
msgstr "موعد ما"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/models.py:850 taiga/projects/userstories/models.py:45
|
||||
#: taiga/projects/userstories/models.py:78
|
||||
|
@ -2607,7 +2605,7 @@ msgstr "وضعیتهای وظایف"
|
|||
|
||||
#: taiga/projects/models.py:852
|
||||
msgid "task duedates"
|
||||
msgstr "موعد وظیفه ها"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/models.py:853
|
||||
msgid "issue statuses"
|
||||
|
@ -2619,7 +2617,7 @@ msgstr "انواع موضوعات"
|
|||
|
||||
#: taiga/projects/models.py:855
|
||||
msgid "issue duedates"
|
||||
msgstr "موعد موضوعات"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/models.py:856
|
||||
msgid "priorities"
|
||||
|
@ -4223,7 +4221,7 @@ msgstr "؟"
|
|||
#. Translators: User story point value (value = 0)
|
||||
#: taiga/projects/translations.py:47
|
||||
msgid "0"
|
||||
msgstr "0"
|
||||
msgstr "۰"
|
||||
|
||||
#. Translators: User story point value (value = 0.5)
|
||||
#: taiga/projects/translations.py:49
|
||||
|
@ -4463,7 +4461,7 @@ msgstr "تاریخ تکمیل"
|
|||
|
||||
#: taiga/projects/userstories/models.py:102
|
||||
msgid "assigned users"
|
||||
msgstr "کاربران متصل شده"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/projects/userstories/models.py:111
|
||||
msgid "generated from issue"
|
||||
|
@ -4726,11 +4724,11 @@ msgstr "تاریخ عضویت"
|
|||
|
||||
#: taiga/users/models.py:155
|
||||
msgid "accepted terms"
|
||||
msgstr "قبول شرایط"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/users/models.py:156
|
||||
msgid "new terms read"
|
||||
msgstr "خواندن شرایط جدید"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/users/models.py:158
|
||||
msgid "default language"
|
||||
|
@ -4974,7 +4972,7 @@ msgstr "نام کاربری نامعتبر. نام کاربری دیگری ان
|
|||
|
||||
#: taiga/users/validators.py:73
|
||||
msgid "Read new terms has to be true'"
|
||||
msgstr "خواندن شرایط جدید باید صحیح باشد"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/userstorage/api.py:53
|
||||
msgid ""
|
||||
|
@ -5019,4 +5017,4 @@ msgstr "مدت زمان"
|
|||
|
||||
#: taiga/webhooks/validators.py:42
|
||||
msgid "Not allowed IP Address"
|
||||
msgstr "آی پی آدرس غیر مجاز"
|
||||
msgstr ""
|
||||
|
|
|
@ -10,14 +10,13 @@
|
|||
# Shun Yanaura <metroplexity@gmail.com>, 2016
|
||||
# Suguru Sato <usagi.vs.tanuki@gmail.com>, 2016
|
||||
# Tomonori Tanabe <tanb+github@me.com>, 2015
|
||||
# Masaki Honda <mh35jp@gmail.com>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: taiga-back\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||
"PO-Revision-Date: 2018-12-28 01:50+0000\n"
|
||||
"Last-Translator: Masaki Honda <mh35jp@gmail.com>\n"
|
||||
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
|
||||
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
|
||||
"Language-Team: Japanese (http://www.transifex.com/taiga-agile-llc/taiga-back/"
|
||||
"language/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -32,7 +31,7 @@ msgstr "パブリックなレジスタは無効です。"
|
|||
|
||||
#: taiga/auth/api.py:93
|
||||
msgid "You must accept our terms of service and privacy policy"
|
||||
msgstr "利用規約とプライバシーポリシーに同意する必要があります"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/auth/api.py:102
|
||||
msgid "invalid register type"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# Lucas Boscaini <lucasboscaini@gmail.com>, 2017
|
||||
# Mairieli Wessel <mairieliw@alunos.utfpr.edu.br>, 2016
|
||||
# Marlon Carvalho <m.lopes@qiwi.com>, 2015
|
||||
# Michael Douglas Meneses de Souza <michael.douglas.meneses.2@gmail.com>, 2018
|
||||
# Michael Meneses <michael.douglas.meneses.2@gmail.com>, 2018
|
||||
# Michel Wilhelm <michelwilhelm@gmail.com>, 2016
|
||||
# pedromvm <pedromvm@gmail.com>, 2015
|
||||
# Pedro Rangel Raft <me@pedroraft.com>, 2017
|
||||
|
@ -32,8 +32,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||
"PO-Revision-Date: 2018-11-17 17:51+0000\n"
|
||||
"Last-Translator: Michael Douglas Meneses de Souza <michael.douglas."
|
||||
"meneses.2@gmail.com>\n"
|
||||
"Last-Translator: Michael Meneses <michael.douglas.meneses.2@gmail.com>\n"
|
||||
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/taiga-agile-llc/"
|
||||
"taiga-back/language/pt_BR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,6 @@
|
|||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Andy zhan <daliangzao189@126.com>, 2018
|
||||
# Ares <yangzibin.cn@hotmail.com>, 2017
|
||||
# gm l <linguangmo@gmail.com>, 2016
|
||||
# Hanbing Yin <yin_suk@hotmail.com>, 2016
|
||||
|
@ -26,8 +25,8 @@ msgstr ""
|
|||
"Project-Id-Version: taiga-back\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||
"PO-Revision-Date: 2018-12-16 07:10+0000\n"
|
||||
"Last-Translator: Andy zhan <daliangzao189@126.com>\n"
|
||||
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
|
||||
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
|
||||
"Language-Team: Chinese Simplified (http://www.transifex.com/taiga-agile-llc/"
|
||||
"taiga-back/language/zh-Hans/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -4650,11 +4649,11 @@ msgstr "加入日期"
|
|||
|
||||
#: taiga/users/models.py:155
|
||||
msgid "accepted terms"
|
||||
msgstr "已接受的条款"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/users/models.py:156
|
||||
msgid "new terms read"
|
||||
msgstr "阅读新条款"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/users/models.py:158
|
||||
msgid "default language"
|
||||
|
@ -4895,7 +4894,7 @@ msgstr "无效用户名,请尝试其他的。"
|
|||
|
||||
#: taiga/users/validators.py:73
|
||||
msgid "Read new terms has to be true'"
|
||||
msgstr "还没有阅读新条款"
|
||||
msgstr ""
|
||||
|
||||
#: taiga/userstorage/api.py:53
|
||||
msgid ""
|
||||
|
@ -4940,4 +4939,4 @@ msgstr "持续时间"
|
|||
|
||||
#: taiga/webhooks/validators.py:42
|
||||
msgid "Not allowed IP Address"
|
||||
msgstr "IP地址被禁用"
|
||||
msgstr ""
|
||||
|
|
|
@ -25,11 +25,6 @@ def connect_issues_signals():
|
|||
from taiga.projects.tagging import signals as tagging_handlers
|
||||
from . import signals as handlers
|
||||
|
||||
# Cached prev object version
|
||||
signals.pre_save.connect(handlers.cached_prev_issue,
|
||||
sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="cached_prev_issue")
|
||||
|
||||
# Finished date
|
||||
signals.pre_save.connect(handlers.set_finished_date_when_edit_issue,
|
||||
sender=apps.get_model("issues", "Issue"),
|
||||
|
@ -40,14 +35,6 @@ def connect_issues_signals():
|
|||
sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="tags_normalization_issue")
|
||||
|
||||
# Open/Close US and Milestone
|
||||
signals.post_save.connect(handlers.try_to_close_or_open_milestone_when_create_or_edit_issue,
|
||||
sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="try_to_close_or_open_milestone_when_create_or_edit_issue")
|
||||
signals.post_delete.connect(handlers.try_to_close_milestone_when_delete_issue,
|
||||
sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="try_to_close_milestone_when_delete_issue")
|
||||
|
||||
|
||||
def connect_issues_custom_attributes_signals():
|
||||
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
||||
|
@ -63,19 +50,11 @@ def connect_all_issues_signals():
|
|||
|
||||
|
||||
def disconnect_issues_signals():
|
||||
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="cached_prev_issue")
|
||||
|
||||
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="set_finished_date_when_edit_issue")
|
||||
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="tags_normalization_issue")
|
||||
|
||||
signals.post_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="try_to_close_or_open_milestone_when_create_or_edit_issue")
|
||||
signals.post_delete.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||
dispatch_uid="try_to_close_milestone_when_delete_issue")
|
||||
|
||||
|
||||
def disconnect_issues_custom_attributes_signals():
|
||||
signals.post_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||
|
|
|
@ -16,22 +16,9 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from contextlib import suppress
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
####################################
|
||||
# Signals for cached prev task
|
||||
####################################
|
||||
|
||||
# Define the previous version of the task for use it on the post_save handler
|
||||
def cached_prev_issue(sender, instance, **kwargs):
|
||||
instance.prev = None
|
||||
if instance.id:
|
||||
instance.prev = sender.objects.get(id=instance.id)
|
||||
|
||||
|
||||
####################################
|
||||
# Signals for set finished date
|
||||
####################################
|
||||
|
@ -43,48 +30,3 @@ def set_finished_date_when_edit_issue(sender, instance, **kwargs):
|
|||
instance.finished_date = timezone.now()
|
||||
elif not instance.status.is_closed and instance.finished_date:
|
||||
instance.finished_date = None
|
||||
|
||||
|
||||
def try_to_close_or_open_milestone_when_create_or_edit_issue(sender, instance, created, **kwargs):
|
||||
if instance._importing:
|
||||
return
|
||||
|
||||
_try_to_close_or_open_milestone_when_create_or_edit_issue(instance)
|
||||
|
||||
|
||||
def try_to_close_milestone_when_delete_issue(sender, instance, **kwargs):
|
||||
if instance._importing:
|
||||
return
|
||||
|
||||
_try_to_close_milestone_when_delete_issue(instance)
|
||||
|
||||
|
||||
# Milestone
|
||||
def _try_to_close_or_open_milestone_when_create_or_edit_issue(instance):
|
||||
if instance._importing:
|
||||
return
|
||||
|
||||
from taiga.projects.milestones import services as milestone_service
|
||||
|
||||
if instance.milestone_id:
|
||||
if milestone_service.calculate_milestone_is_closed(instance.milestone):
|
||||
milestone_service.close_milestone(instance.milestone)
|
||||
else:
|
||||
milestone_service.open_milestone(instance.milestone)
|
||||
|
||||
if instance.prev and instance.prev.milestone_id and instance.prev.milestone_id != instance.milestone_id:
|
||||
if milestone_service.calculate_milestone_is_closed(instance.prev.milestone):
|
||||
milestone_service.close_milestone(instance.prev.milestone)
|
||||
else:
|
||||
milestone_service.open_milestone(instance.prev.milestone)
|
||||
|
||||
|
||||
def _try_to_close_milestone_when_delete_issue(instance):
|
||||
if instance._importing:
|
||||
return
|
||||
|
||||
from taiga.projects.milestones import services as milestone_service
|
||||
|
||||
with suppress(ObjectDoesNotExist):
|
||||
if instance.milestone_id and milestone_service.calculate_milestone_is_closed(instance.milestone):
|
||||
milestone_service.close_milestone(instance.milestone)
|
||||
|
|
|
@ -27,17 +27,11 @@ from taiga.base.api.mixins import BlockedByProjectMixin
|
|||
from taiga.base.api.utils import get_object_or_404
|
||||
from taiga.base.utils.db import get_object_or_none
|
||||
|
||||
from taiga.projects.models import Project
|
||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||
from taiga.projects.notifications.mixins import WatchersViewSetMixin
|
||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||
from taiga.projects.tasks.validators import UpdateMilestoneBulkValidator as \
|
||||
TasksUpdateMilestoneValidator
|
||||
from taiga.projects.issues.validators import UpdateMilestoneBulkValidator as \
|
||||
IssuesUpdateMilestoneValidator
|
||||
|
||||
from . import serializers
|
||||
from . import services
|
||||
from . import validators
|
||||
from . import models
|
||||
from . import permissions
|
||||
|
@ -148,69 +142,6 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin,
|
|||
|
||||
return response.Ok(milestone_stats)
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def move_userstories_to_sprint(self, request, pk=None, **kwargs):
|
||||
milestone = get_object_or_404(models.Milestone, pk=pk)
|
||||
|
||||
self.check_permissions(request, "move_related_items", milestone)
|
||||
|
||||
validator = validators.UpdateMilestoneBulkValidator(data=request.DATA)
|
||||
if not validator.is_valid():
|
||||
return response.BadRequest(validator.errors)
|
||||
|
||||
data = validator.data
|
||||
project = get_object_or_404(Project, pk=data["project_id"])
|
||||
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
|
||||
|
||||
if data["bulk_stories"]:
|
||||
self.check_permissions(request, "move_uss_to_sprint", project)
|
||||
services.update_userstories_milestone_in_bulk(data["bulk_stories"], milestone_result)
|
||||
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
|
||||
|
||||
return response.NoContent()
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def move_tasks_to_sprint(self, request, pk=None, **kwargs):
|
||||
milestone = get_object_or_404(models.Milestone, pk=pk)
|
||||
|
||||
self.check_permissions(request, "move_related_items", milestone)
|
||||
|
||||
validator = TasksUpdateMilestoneValidator(data=request.DATA)
|
||||
if not validator.is_valid():
|
||||
return response.BadRequest(validator.errors)
|
||||
|
||||
data = validator.data
|
||||
project = get_object_or_404(Project, pk=data["project_id"])
|
||||
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
|
||||
|
||||
if data["bulk_tasks"]:
|
||||
self.check_permissions(request, "move_tasks_to_sprint", project)
|
||||
services.update_tasks_milestone_in_bulk(data["bulk_tasks"], milestone_result)
|
||||
services.snapshot_tasks_in_bulk(data["bulk_tasks"], request.user)
|
||||
|
||||
return response.NoContent()
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def move_issues_to_sprint(self, request, pk=None, **kwargs):
|
||||
milestone = get_object_or_404(models.Milestone, pk=pk)
|
||||
|
||||
self.check_permissions(request, "move_related_items", milestone)
|
||||
|
||||
validator = IssuesUpdateMilestoneValidator(data=request.DATA)
|
||||
if not validator.is_valid():
|
||||
return response.BadRequest(validator.errors)
|
||||
|
||||
data = validator.data
|
||||
project = get_object_or_404(Project, pk=data["project_id"])
|
||||
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
|
||||
|
||||
if data["bulk_issues"]:
|
||||
self.check_permissions(request, "move_issues_to_sprint", project)
|
||||
services.update_issues_milestone_in_bulk(data["bulk_issues"], milestone_result)
|
||||
services.snapshot_issues_in_bulk(data["bulk_issues"], request.user)
|
||||
|
||||
return response.NoContent()
|
||||
|
||||
|
||||
class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
|
||||
permission_classes = (permissions.MilestoneWatchersPermission,)
|
||||
|
|
|
@ -33,11 +33,6 @@ class MilestonePermission(TaigaResourcePermission):
|
|||
stats_perms = HasProjectPerm('view_milestones')
|
||||
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
|
||||
unwatch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
|
||||
move_related_items_perms = HasProjectPerm('modify_milestone')
|
||||
move_uss_to_sprint_perms = HasProjectPerm('modify_us')
|
||||
move_tasks_to_sprint_perms = HasProjectPerm('modify_task')
|
||||
move_issues_to_sprint_perms = HasProjectPerm('modify_issue')
|
||||
|
||||
|
||||
class MilestoneWatchersPermission(TaigaResourcePermission):
|
||||
enought_perms = IsProjectAdmin() | IsSuperUser()
|
||||
|
|
|
@ -16,29 +16,16 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from taiga.base.utils import db
|
||||
from taiga.events import events
|
||||
from taiga.projects.history.services import take_snapshot
|
||||
from taiga.projects.services import apply_order_updates
|
||||
from taiga.projects.issues.models import Issue
|
||||
from taiga.projects.tasks.models import Task
|
||||
from taiga.projects.userstories.models import UserStory
|
||||
from django.utils import timezone
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
|
||||
def calculate_milestone_is_closed(milestone):
|
||||
all_us_closed = all([user_story.is_closed for user_story in milestone.user_stories.all()])
|
||||
all_tasks_closed = all([task.status is not None and task.status.is_closed for task in
|
||||
milestone.tasks.all()])
|
||||
all_issues_closed = all([issue.is_closed for issue in milestone.issues.all()])
|
||||
|
||||
uss_check = milestone.user_stories.all().count() > 0 \
|
||||
and all_tasks_closed and all_us_closed and all_issues_closed
|
||||
issues_check = milestone.issues.all().count() > 0 and all_issues_closed \
|
||||
and all_tasks_closed and all_us_closed
|
||||
tasks_check = milestone.tasks.all().count() > 0 and all_tasks_closed \
|
||||
and all_issues_closed and all_us_closed
|
||||
|
||||
return uss_check or issues_check or tasks_check
|
||||
return (milestone.user_stories.all().count() > 0 and
|
||||
all([task.status is not None and task.status.is_closed for task in milestone.tasks.all()]) and
|
||||
all([user_story.is_closed for user_story in milestone.user_stories.all()]))
|
||||
|
||||
|
||||
def close_milestone(milestone):
|
||||
|
@ -51,137 +38,3 @@ def open_milestone(milestone):
|
|||
if milestone.closed:
|
||||
milestone.closed = False
|
||||
milestone.save(update_fields=["closed",])
|
||||
|
||||
|
||||
def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
|
||||
"""
|
||||
Update the milestone and the milestone order of some user stories adding
|
||||
the extra orders needed to keep consistency.
|
||||
`bulk_data` should be a list of dicts with the following format:
|
||||
[{'us_id': <value>, 'order': <value>}, ...]
|
||||
"""
|
||||
user_stories = milestone.user_stories.all()
|
||||
us_orders = {us.id: getattr(us, "sprint_order") for us in user_stories}
|
||||
new_us_orders = {}
|
||||
for e in bulk_data:
|
||||
new_us_orders[e["us_id"]] = e["order"]
|
||||
# The base orders where we apply the new orders must containg all
|
||||
# the values
|
||||
us_orders[e["us_id"]] = e["order"]
|
||||
|
||||
apply_order_updates(us_orders, new_us_orders)
|
||||
|
||||
us_milestones = {e["us_id"]: milestone.id for e in bulk_data}
|
||||
user_story_ids = us_milestones.keys()
|
||||
|
||||
events.emit_event_for_ids(ids=user_story_ids,
|
||||
content_type="userstories.userstory",
|
||||
projectid=milestone.project.pk)
|
||||
|
||||
us_instance_list = []
|
||||
us_values = []
|
||||
for us_id in user_story_ids:
|
||||
us = UserStory.objects.get(pk=us_id)
|
||||
us_instance_list.append(us)
|
||||
us_values.append({'milestone_id': milestone.id})
|
||||
|
||||
db.update_in_bulk(us_instance_list, us_values)
|
||||
db.update_attr_in_bulk_for_ids(us_orders, "sprint_order", UserStory)
|
||||
|
||||
# Updating the milestone for the tasks
|
||||
Task.objects.filter(
|
||||
user_story_id__in=[e["us_id"] for e in bulk_data]).update(
|
||||
milestone=milestone)
|
||||
|
||||
return us_orders
|
||||
|
||||
|
||||
def snapshot_userstories_in_bulk(bulk_data, user):
|
||||
for us_data in bulk_data:
|
||||
try:
|
||||
us = UserStory.objects.get(pk=us_data['us_id'])
|
||||
take_snapshot(us, user=user)
|
||||
except UserStory.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def update_tasks_milestone_in_bulk(bulk_data: list, milestone: object):
|
||||
"""
|
||||
Update the milestone and the milestone order of some tasks adding
|
||||
the extra orders needed to keep consistency.
|
||||
`bulk_data` should be a list of dicts with the following format:
|
||||
[{'task_id': <value>, 'order': <value>}, ...]
|
||||
"""
|
||||
tasks = milestone.tasks.all()
|
||||
task_orders = {task.id: getattr(task, "taskboard_order") for task in tasks}
|
||||
new_task_orders = {}
|
||||
for e in bulk_data:
|
||||
new_task_orders[e["task_id"]] = e["order"]
|
||||
# The base orders where we apply the new orders must containg all
|
||||
# the values
|
||||
task_orders[e["task_id"]] = e["order"]
|
||||
|
||||
apply_order_updates(task_orders, new_task_orders)
|
||||
|
||||
task_milestones = {e["task_id"]: milestone.id for e in bulk_data}
|
||||
task_ids = task_milestones.keys()
|
||||
|
||||
events.emit_event_for_ids(ids=task_ids,
|
||||
content_type="tasks.task",
|
||||
projectid=milestone.project.pk)
|
||||
|
||||
|
||||
task_instance_list = []
|
||||
task_values = []
|
||||
for task_id in task_ids:
|
||||
task = Task.objects.get(pk=task_id)
|
||||
task_instance_list.append(task)
|
||||
task_values.append({'milestone_id': milestone.id})
|
||||
|
||||
db.update_in_bulk(task_instance_list, task_values)
|
||||
db.update_attr_in_bulk_for_ids(task_orders, "taskboard_order", Task)
|
||||
|
||||
return task_milestones
|
||||
|
||||
|
||||
def snapshot_tasks_in_bulk(bulk_data, user):
|
||||
for task_data in bulk_data:
|
||||
try:
|
||||
task = Task.objects.get(pk=task_data['task_id'])
|
||||
take_snapshot(task, user=user)
|
||||
except Task.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def update_issues_milestone_in_bulk(bulk_data: list, milestone: object):
|
||||
"""
|
||||
Update the milestone some issues adding
|
||||
`bulk_data` should be a list of dicts with the following format:
|
||||
[{'task_id': <value>}, ...]
|
||||
"""
|
||||
issue_milestones = {e["issue_id"]: milestone.id for e in bulk_data}
|
||||
issue_ids = issue_milestones.keys()
|
||||
|
||||
events.emit_event_for_ids(ids=issue_ids,
|
||||
content_type="issues.issues",
|
||||
projectid=milestone.project.pk)
|
||||
|
||||
issues_instance_list = []
|
||||
issues_values = []
|
||||
for issue_id in issue_ids:
|
||||
issue = Issue.objects.get(pk=issue_id)
|
||||
issues_instance_list.append(issue)
|
||||
issues_values.append({'milestone_id': milestone.id})
|
||||
|
||||
db.update_in_bulk(issues_instance_list, issues_values)
|
||||
|
||||
return issue_milestones
|
||||
|
||||
|
||||
def snapshot_issues_in_bulk(bulk_data, user):
|
||||
for issue_data in bulk_data:
|
||||
try:
|
||||
issue = Issue.objects.get(pk=issue_data['issue_id'])
|
||||
take_snapshot(issue, user=user)
|
||||
except Issue.DoesNotExist:
|
||||
pass
|
||||
|
|
|
@ -19,17 +19,15 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
from taiga.base.exceptions import ValidationError
|
||||
from taiga.base.api import serializers
|
||||
from taiga.base.api import validators
|
||||
from taiga.projects.notifications.validators import WatchersValidator
|
||||
from taiga.projects.userstories.models import UserStory
|
||||
from taiga.projects.validators import DuplicatedNameInProjectValidator
|
||||
from taiga.projects.validators import ProjectExistsValidator
|
||||
from taiga.projects.notifications.validators import WatchersValidator
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class MilestoneExistsValidator:
|
||||
def validate_milestone_id(self, attrs, source):
|
||||
def validate_sprint_id(self, attrs, source):
|
||||
value = attrs[source]
|
||||
if not models.Milestone.objects.filter(pk=value).exists():
|
||||
msg = _("There's no milestone with that id")
|
||||
|
@ -41,28 +39,3 @@ class MilestoneValidator(WatchersValidator, DuplicatedNameInProjectValidator, va
|
|||
class Meta:
|
||||
model = models.Milestone
|
||||
read_only_fields = ("id", "created_date", "modified_date")
|
||||
|
||||
|
||||
# bulk validators
|
||||
class _UserStoryMilestoneBulkValidator(validators.Validator):
|
||||
us_id = serializers.IntegerField()
|
||||
order = serializers.IntegerField()
|
||||
|
||||
|
||||
class UpdateMilestoneBulkValidator(MilestoneExistsValidator,
|
||||
ProjectExistsValidator,
|
||||
validators.Validator):
|
||||
project_id = serializers.IntegerField()
|
||||
milestone_id = serializers.IntegerField()
|
||||
bulk_stories = _UserStoryMilestoneBulkValidator(many=True)
|
||||
|
||||
def validate_bulk_stories(self, attrs, source):
|
||||
filters = {
|
||||
"project__id": attrs["project_id"],
|
||||
"id__in": [us["us_id"] for us in attrs[source]]
|
||||
}
|
||||
|
||||
if UserStory.objects.filter(**filters).count() != len(filters["id__in"]):
|
||||
raise ValidationError(_("All the user stories must be from the same project"))
|
||||
|
||||
return attrs
|
||||
|
|
|
@ -44,10 +44,6 @@ from .models import HistoryChangeNotification, Watched
|
|||
from .squashing import squash_history_entries
|
||||
|
||||
|
||||
def remove_lr_cr(s):
|
||||
return s.replace("\n", "").replace("\r", "")
|
||||
|
||||
|
||||
def notify_policy_exists(project, user) -> bool:
|
||||
"""
|
||||
Check if policy exists for specified project
|
||||
|
@ -317,11 +313,10 @@ def send_sync_notifications(notification_id):
|
|||
msg_id = 'taiga-system'
|
||||
|
||||
now = datetime.datetime.now()
|
||||
project_name = remove_lr_cr(notification.project.name)
|
||||
format_args = {
|
||||
"unsubscribe_url": resolve_front_url('settings-mail-notifications'),
|
||||
"project_slug": notification.project.slug,
|
||||
"project_name": project_name,
|
||||
"project_name": notification.project.name,
|
||||
"msg_id": msg_id,
|
||||
"time": int(now.timestamp()),
|
||||
"domain": domain
|
||||
|
|
|
@ -91,7 +91,3 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, DueDateM
|
|||
|
||||
def __str__(self):
|
||||
return "({1}) {0}".format(self.ref, self.subject)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self.status is not None and self.status.is_closed
|
||||
|
|
|
@ -31,9 +31,9 @@ def cached_prev_task(sender, instance, **kwargs):
|
|||
instance.prev = sender.objects.get(id=instance.id)
|
||||
|
||||
|
||||
######################################
|
||||
# Signals for close Task and Milestone
|
||||
######################################
|
||||
####################################
|
||||
# Signals for close US and Milestone
|
||||
####################################
|
||||
|
||||
def try_to_close_or_open_us_and_milestone_when_create_or_edit_task(sender, instance, created, **kwargs):
|
||||
_try_to_close_or_open_us_when_create_or_edit_task(instance)
|
||||
|
|
|
@ -72,6 +72,7 @@ def update_milestone_of_tasks_when_edit_us(sender, instance, created, **kwargs):
|
|||
def try_to_close_or_open_us_and_milestone_when_create_or_edit_us(sender, instance, created, **kwargs):
|
||||
if instance._importing:
|
||||
return
|
||||
|
||||
_try_to_close_or_open_us_when_create_or_edit_us(instance)
|
||||
_try_to_close_or_open_milestone_when_create_or_edit_us(instance)
|
||||
|
||||
|
|
|
@ -626,10 +626,6 @@ def create_issue(**kwargs):
|
|||
return IssueFactory.create(**defaults)
|
||||
|
||||
|
||||
class Missing:
|
||||
pass
|
||||
|
||||
|
||||
def create_task(**kwargs):
|
||||
"Create a task and along with its dependencies."
|
||||
owner = kwargs.pop("owner", None)
|
||||
|
@ -640,23 +636,13 @@ def create_task(**kwargs):
|
|||
if project is None:
|
||||
project = ProjectFactory.create(owner=owner)
|
||||
|
||||
status = kwargs.pop("status", None)
|
||||
milestone = kwargs.pop("milestone", None)
|
||||
|
||||
defaults = {
|
||||
"project": project,
|
||||
"owner": owner,
|
||||
"status": status or TaskStatusFactory.create(project=project),
|
||||
"milestone": milestone or MilestoneFactory.create(project=project),
|
||||
"status": TaskStatusFactory.create(project=project),
|
||||
"milestone": MilestoneFactory.create(project=project),
|
||||
"user_story": UserStoryFactory.create(project=project, owner=owner),
|
||||
}
|
||||
|
||||
user_story = kwargs.pop("user_story", Missing)
|
||||
|
||||
defaults["user_story"] = (
|
||||
UserStoryFactory.create(project=project, owner=owner, milestone=defaults["milestone"])
|
||||
if user_story is Missing
|
||||
else user_story
|
||||
)
|
||||
defaults.update(kwargs)
|
||||
|
||||
return TaskFactory.create(**defaults)
|
||||
|
|
|
@ -39,78 +39,6 @@ import pytest
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def create_filter_issues_context():
|
||||
data = {}
|
||||
|
||||
data["project"] = f.ProjectFactory.create()
|
||||
project = data["project"]
|
||||
data["users"] = [f.UserFactory.create(is_superuser=True) for i in range(0, 3)]
|
||||
data["roles"] = [f.RoleFactory.create() for i in range(0, 3)]
|
||||
user_roles = zip(data["users"], data["roles"])
|
||||
# Add membership fixtures
|
||||
[f.MembershipFactory.create(user=user, project=project, role=role) for (user, role) in user_roles]
|
||||
|
||||
data["statuses"] = [f.IssueStatusFactory.create(project=project) for i in range(0, 4)]
|
||||
data["types"] = [f.IssueTypeFactory.create(project=project) for i in range(0, 2)]
|
||||
data["severities"] = [f.SeverityFactory.create(project=project) for i in range(0, 4)]
|
||||
data["priorities"] = [f.PriorityFactory.create(project=project) for i in range(0, 4)]
|
||||
data["tags"] = ["test1test2test3", "test1", "test2", "test3"]
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
# | Issue | Owner | Assigned To | Status | Type | Priority | Severity | Tags |
|
||||
# |-------#--------#-------------#---------#-------#-----------#-----------#---------------------|
|
||||
# | 0 | user2 | None | status3 | type1 | priority2 | severity1 | tag1 |
|
||||
# | 1 | user1 | None | status3 | type2 | priority2 | severity1 | tag2 |
|
||||
# | 2 | user3 | None | status1 | type1 | priority3 | severity2 | tag1 tag2 |
|
||||
# | 3 | user2 | None | status0 | type2 | priority3 | severity1 | tag3 |
|
||||
# | 4 | user1 | user1 | status0 | type1 | priority2 | severity3 | tag1 tag2 tag3 |
|
||||
# | 5 | user3 | user1 | status2 | type2 | priority3 | severity2 | tag3 |
|
||||
# | 6 | user2 | user1 | status3 | type1 | priority2 | severity0 | tag1 tag2 |
|
||||
# | 7 | user1 | user2 | status0 | type2 | priority1 | severity3 | tag3 |
|
||||
# | 8 | user3 | user2 | status3 | type1 | priority0 | severity1 | tag1 |
|
||||
# | 9 | user2 | user3 | status1 | type2 | priority0 | severity2 | tag0 |
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
(user1, user2, user3, ) = data["users"]
|
||||
(status0, status1, status2, status3 ) = data["statuses"]
|
||||
(type1, type2, ) = data["types"]
|
||||
(severity0, severity1, severity2, severity3, ) = data["severities"]
|
||||
(priority0, priority1, priority2, priority3, ) = data["priorities"]
|
||||
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||
|
||||
f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status3, type=type1, priority=priority2, severity=severity1,
|
||||
tags=[tag1])
|
||||
f.IssueFactory.create(project=project, owner=user1, assigned_to=None,
|
||||
status=status3, type=type2, priority=priority2, severity=severity1,
|
||||
tags=[tag2])
|
||||
f.IssueFactory.create(project=project, owner=user3, assigned_to=None,
|
||||
status=status1, type=type1, priority=priority3, severity=severity2,
|
||||
tags=[tag1, tag2])
|
||||
f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status0, type=type2, priority=priority3, severity=severity1,
|
||||
tags=[tag3])
|
||||
f.IssueFactory.create(project=project, owner=user1, assigned_to=user1,
|
||||
status=status0, type=type1, priority=priority2, severity=severity3,
|
||||
tags=[tag1, tag2, tag3])
|
||||
f.IssueFactory.create(project=project, owner=user3, assigned_to=user1,
|
||||
status=status2, type=type2, priority=priority3, severity=severity2,
|
||||
tags=[tag3])
|
||||
f.IssueFactory.create(project=project, owner=user2, assigned_to=user1,
|
||||
status=status3, type=type1, priority=priority2, severity=severity0,
|
||||
tags=[tag1, tag2])
|
||||
f.IssueFactory.create(project=project, owner=user1, assigned_to=user2,
|
||||
status=status0, type=type2, priority=priority1, severity=severity3,
|
||||
tags=[tag3])
|
||||
f.IssueFactory.create(project=project, owner=user3, assigned_to=user2,
|
||||
status=status3, type=type1, priority=priority0, severity=severity1,
|
||||
tags=[tag1])
|
||||
f.IssueFactory.create(project=project, owner=user2, assigned_to=user3,
|
||||
status=status1, type=type2, priority=priority0, severity=severity2,
|
||||
tags=[tag0])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def test_get_issues_from_bulk():
|
||||
data = """
|
||||
Issue #1
|
||||
|
@ -442,63 +370,86 @@ def test_api_filter_by_finished_date(client):
|
|||
assert response.data[0]["ref"] == finished_issue.ref
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filter_name,collection,expected,exclude_expected,is_text", [
|
||||
('type', 'types', 5, 5, False),
|
||||
('severity', 'severities', 1, 9, False),
|
||||
('priority', 'priorities', 2, 8, False),
|
||||
('status', 'statuses', 3, 7, False),
|
||||
('assigned_to', 'users', 3, 7, False),
|
||||
('tags', 'tags', 1, 9, True),
|
||||
('owner', 'users', 3, 7, False),
|
||||
('role', 'roles', 3, 7, False),
|
||||
])
|
||||
def test_api_filters(client, filter_name, collection, expected, exclude_expected, is_text):
|
||||
data = create_filter_issues_context()
|
||||
project = data["project"]
|
||||
options = data[collection]
|
||||
|
||||
client.login(data["users"][0])
|
||||
if is_text:
|
||||
param = options[0]
|
||||
else:
|
||||
param = options[0].id
|
||||
|
||||
# include test
|
||||
url = "{}?project={}&{}={}".format(reverse('issues-list'), project.id, filter_name, param)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == expected
|
||||
|
||||
# exclude test
|
||||
url = "{}?project={}&exclude_{}={}".format(reverse('issues-list'), project.id, filter_name, param)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == exclude_expected
|
||||
|
||||
|
||||
def test_mulitple_exclude_filter_tags(client):
|
||||
data = create_filter_issues_context()
|
||||
project = data["project"]
|
||||
client.login(data["users"][0])
|
||||
tags = data["tags"]
|
||||
|
||||
url = "{}?project={}&exclude_tags={},{}".format(reverse('issues-list'), project.id, tags[1], tags[2])
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == 4
|
||||
|
||||
|
||||
def test_api_filters_data(client):
|
||||
data = create_filter_issues_context()
|
||||
project = data["project"]
|
||||
(user1, user2, user3, ) = data["users"]
|
||||
(status0, status1, status2, status3, ) = data["statuses"]
|
||||
(type1, type2, ) = data["types"]
|
||||
(priority0, priority1, priority2, priority3, ) = data["priorities"]
|
||||
(severity0, severity1, severity2, severity3, ) = data["severities"]
|
||||
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||
project = f.ProjectFactory.create()
|
||||
user1 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user1, project=project)
|
||||
user2 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user2, project=project)
|
||||
user3 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user3, project=project)
|
||||
|
||||
status0 = f.IssueStatusFactory.create(project=project)
|
||||
status1 = f.IssueStatusFactory.create(project=project)
|
||||
status2 = f.IssueStatusFactory.create(project=project)
|
||||
status3 = f.IssueStatusFactory.create(project=project)
|
||||
|
||||
type1 = f.IssueTypeFactory.create(project=project)
|
||||
type2 = f.IssueTypeFactory.create(project=project)
|
||||
|
||||
severity0 = f.SeverityFactory.create(project=project)
|
||||
severity1 = f.SeverityFactory.create(project=project)
|
||||
severity2 = f.SeverityFactory.create(project=project)
|
||||
severity3 = f.SeverityFactory.create(project=project)
|
||||
|
||||
priority0 = f.PriorityFactory.create(project=project)
|
||||
priority1 = f.PriorityFactory.create(project=project)
|
||||
priority2 = f.PriorityFactory.create(project=project)
|
||||
priority3 = f.PriorityFactory.create(project=project)
|
||||
|
||||
tag0 = "test1test2test3"
|
||||
tag1 = "test1"
|
||||
tag2 = "test2"
|
||||
tag3 = "test3"
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
# | Issue | Owner | Assigned To | Status | Type | Priority | Severity | Tags |
|
||||
# |-------#--------#-------------#---------#-------#-----------#-----------#---------------------|
|
||||
# | 0 | user2 | None | status3 | type1 | priority2 | severity1 | tag1 |
|
||||
# | 1 | user1 | None | status3 | type2 | priority2 | severity1 | tag2 |
|
||||
# | 2 | user3 | None | status1 | type1 | priority3 | severity2 | tag1 tag2 |
|
||||
# | 3 | user2 | None | status0 | type2 | priority3 | severity1 | tag3 |
|
||||
# | 4 | user1 | user1 | status0 | type1 | priority2 | severity3 | tag1 tag2 tag3 |
|
||||
# | 5 | user3 | user1 | status2 | type2 | priority3 | severity2 | tag3 |
|
||||
# | 6 | user2 | user1 | status3 | type1 | priority2 | severity0 | tag1 tag2 |
|
||||
# | 7 | user1 | user2 | status0 | type2 | priority1 | severity3 | tag3 |
|
||||
# | 8 | user3 | user2 | status3 | type1 | priority0 | severity1 | tag1 |
|
||||
# | 9 | user2 | user3 | status1 | type2 | priority0 | severity2 | tag0 |
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
issue0 = f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status3, type=type1, priority=priority2, severity=severity1,
|
||||
tags=[tag1])
|
||||
issue1 = f.IssueFactory.create(project=project, owner=user1, assigned_to=None,
|
||||
status=status3, type=type2, priority=priority2, severity=severity1,
|
||||
tags=[tag2])
|
||||
issue2 = f.IssueFactory.create(project=project, owner=user3, assigned_to=None,
|
||||
status=status1, type=type1, priority=priority3, severity=severity2,
|
||||
tags=[tag1, tag2])
|
||||
issue3 = f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status0, type=type2, priority=priority3, severity=severity1,
|
||||
tags=[tag3])
|
||||
issue4 = f.IssueFactory.create(project=project, owner=user1, assigned_to=user1,
|
||||
status=status0, type=type1, priority=priority2, severity=severity3,
|
||||
tags=[tag1, tag2, tag3])
|
||||
issue5 = f.IssueFactory.create(project=project, owner=user3, assigned_to=user1,
|
||||
status=status2, type=type2, priority=priority3, severity=severity2,
|
||||
tags=[tag3])
|
||||
issue6 = f.IssueFactory.create(project=project, owner=user2, assigned_to=user1,
|
||||
status=status3, type=type1, priority=priority2, severity=severity0,
|
||||
tags=[tag1, tag2])
|
||||
issue7 = f.IssueFactory.create(project=project, owner=user1, assigned_to=user2,
|
||||
status=status0, type=type2, priority=priority1, severity=severity3,
|
||||
tags=[tag3])
|
||||
issue8 = f.IssueFactory.create(project=project, owner=user3, assigned_to=user2,
|
||||
status=status3, type=type1, priority=priority0, severity=severity1,
|
||||
tags=[tag1])
|
||||
issue9 = f.IssueFactory.create(project=project, owner=user2, assigned_to=user3,
|
||||
status=status1, type=type2, priority=priority0, severity=severity2,
|
||||
tags=[tag0])
|
||||
|
||||
url = reverse("issues-filters-data") + "?project={}".format(project.id)
|
||||
|
||||
client.login(user1)
|
||||
|
||||
## No filter
|
||||
|
|
|
@ -26,6 +26,7 @@ from urllib.parse import quote
|
|||
from django.core.urlresolvers import reverse
|
||||
|
||||
from taiga.base.utils import json
|
||||
from taiga.projects.userstories.serializers import UserStorySerializer
|
||||
|
||||
from .. import factories as f
|
||||
|
||||
|
@ -179,211 +180,3 @@ def test_api_filter_by_milestone__estimated_start_and_end(client, field_name):
|
|||
assert number_of_milestones == expection, param
|
||||
if number_of_milestones > 0:
|
||||
assert response.data[0]["slug"] == milestone.slug
|
||||
|
||||
|
||||
def test_api_update_milestone_in_bulk_userstories(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
us1 = f.create_userstory(project=project, milestone=milestone1,
|
||||
sprint_order=1)
|
||||
us2 = f.create_userstory(project=project, milestone=milestone1,
|
||||
sprint_order=2)
|
||||
|
||||
assert project.milestones.get(id=milestone1.id).user_stories.count() == 2
|
||||
|
||||
url = reverse("milestones-move-userstories-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_stories": [{"us_id": us2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).user_stories.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).user_stories.count() == 1
|
||||
|
||||
|
||||
def test_api_move_userstories_to_another_sprint(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
us1 = f.create_userstory(project=project, milestone=milestone1,
|
||||
sprint_order=1)
|
||||
us2 = f.create_userstory(project=project, milestone=milestone1,
|
||||
sprint_order=2)
|
||||
|
||||
assert project.milestones.get(id=milestone1.id).user_stories.count() == 2
|
||||
|
||||
url = reverse("milestones-move-userstories-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_stories": [{"us_id": us2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).user_stories.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).user_stories.count() == 1
|
||||
|
||||
|
||||
def test_api_move_userstories_to_another_sprint_close_previous(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.UserStoryStatusFactory.create(is_closed=True)
|
||||
us1 = f.create_userstory(project=project, milestone=milestone1,
|
||||
sprint_order=1, status=closed_status)
|
||||
us2 = f.create_userstory(project=project, milestone=milestone1, sprint_order=2)
|
||||
|
||||
assert milestone1.user_stories.count() == 2
|
||||
assert not milestone1.closed
|
||||
|
||||
url = reverse("milestones-move-userstories-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_stories": [{"us_id": us2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).user_stories.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).user_stories.count() == 1
|
||||
assert project.milestones.get(id=milestone1.id).closed
|
||||
|
||||
|
||||
def test_api_move_tasks_to_another_sprint(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
task1 = f.create_task(project=project, milestone=milestone1, taskboard_order=1)
|
||||
task2 = f.create_task(project=project, milestone=milestone1, taskboard_order=2)
|
||||
|
||||
assert project.milestones.get(id=milestone1.id).tasks.count() == 2
|
||||
|
||||
url = reverse("milestones-move-tasks-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_tasks": [{"task_id": task2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).tasks.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).tasks.count() == 1
|
||||
|
||||
|
||||
def test_api_move_tasks_to_another_sprint_close_previous(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.TaskStatusFactory.create(project=project, is_closed=True)
|
||||
|
||||
task1 = f.create_task(project=project, milestone=milestone1, taskboard_order=1,
|
||||
status=closed_status, user_story=None)
|
||||
task2 = f.create_task(project=project, milestone=milestone1, taskboard_order=2,
|
||||
user_story=None)
|
||||
|
||||
assert project.milestones.get(id=milestone1.id).tasks.count() == 2
|
||||
assert not milestone1.closed
|
||||
|
||||
url = reverse("milestones-move-tasks-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_tasks": [{"task_id": task2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).tasks.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).tasks.count() == 1
|
||||
assert project.milestones.get(id=milestone1.id).closed
|
||||
|
||||
|
||||
def test_api_move_issues_to_another_sprint(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
issue1 = f.create_issue(project=project, milestone=milestone1)
|
||||
issue2 = f.create_issue(project=project, milestone=milestone1)
|
||||
|
||||
assert project.milestones.get(id=milestone1.id).issues.count() == 2
|
||||
|
||||
url = reverse("milestones-move-issues-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_issues": [{"issue_id": issue2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).issues.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).issues.count() == 1
|
||||
|
||||
|
||||
def test_api_move_issues_to_another_sprint_close_previous(client):
|
||||
project = f.create_project()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
milestone2 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
issue1 = f.create_issue(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
issue2 = f.create_issue(project=project, milestone=milestone1)
|
||||
|
||||
assert project.milestones.get(id=milestone1.id).closed is False
|
||||
assert project.milestones.get(id=milestone1.id).issues.count() == 2
|
||||
|
||||
url = reverse("milestones-move-issues-to-sprint", kwargs={"pk": milestone1.pk})
|
||||
data = {
|
||||
"project_id": project.id,
|
||||
"milestone_id": milestone2.id,
|
||||
"bulk_issues": [{"issue_id": issue2.id, "order": 2}]
|
||||
}
|
||||
client.login(project.owner)
|
||||
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
|
||||
assert response.status_code == 204, response.data
|
||||
assert project.milestones.get(id=milestone1.id).issues.count() == 1
|
||||
assert project.milestones.get(id=milestone2.id).issues.count() == 1
|
||||
assert project.milestones.get(id=milestone1.id).closed
|
||||
|
|
|
@ -55,20 +55,6 @@ def mail():
|
|||
return mail
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"header, expected",
|
||||
[
|
||||
("", ""),
|
||||
("One line", "One line"),
|
||||
("Two \nlines", "Two lines"),
|
||||
("Mix \r\nCR and LF \rin the string", "Mix CR and LF in the string"),
|
||||
]
|
||||
)
|
||||
def test_remove_lr_cr(header, expected):
|
||||
rv = services.remove_lr_cr(header)
|
||||
assert rv == expected
|
||||
|
||||
|
||||
def test_create_retrieve_notify_policy():
|
||||
project = f.ProjectFactory.create()
|
||||
|
||||
|
|
|
@ -39,62 +39,6 @@ import pytest
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def create_tasks_fixtures():
|
||||
data = {}
|
||||
data["project"] = f.ProjectFactory.create()
|
||||
project = data["project"]
|
||||
data["users"] = [f.UserFactory.create(is_superuser=True) for i in range(0, 3)]
|
||||
data["roles"] = [f.RoleFactory.create() for i in range(0, 3)]
|
||||
user_roles = zip(data["users"], data["roles"])
|
||||
# Add membership fixtures
|
||||
[f.MembershipFactory.create(user=user, project=project, role=role) for (user, role) in user_roles]
|
||||
|
||||
data["statuses"] = [f.TaskStatusFactory.create(project=project) for i in range(0, 4)]
|
||||
data["tags"] = ["test1test2test3", "test1", "test2", "test3"]
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# | Task | Owner | Assigned To | Tags | Status |
|
||||
# |-------#--------#-------------#---------------------|---------|
|
||||
# | 0 | user2 | None | tag1 | status3 |
|
||||
# | 1 | user1 | None | tag2 | status3 |
|
||||
# | 2 | user3 | None | tag1 tag2 | status1 |
|
||||
# | 3 | user2 | None | tag3 | status0 |
|
||||
# | 4 | user1 | user1 | tag1 tag2 tag3 | status0 |
|
||||
# | 5 | user3 | user1 | tag3 | status2 |
|
||||
# | 6 | user2 | user1 | tag1 tag2 | status3 |
|
||||
# | 7 | user1 | user2 | tag3 | status0 |
|
||||
# | 8 | user3 | user2 | tag1 | status3 |
|
||||
# | 9 | user2 | user3 | tag0 | status1 |
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
(user1, user2, user3, ) = data["users"]
|
||||
(status0, status1, status2, status3 ) = data["statuses"]
|
||||
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||
|
||||
f.TaskFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status3, tags=[tag1])
|
||||
f.TaskFactory.create(project=project, owner=user1, assigned_to=None,
|
||||
status=status3, tags=[tag2])
|
||||
f.TaskFactory.create(project=project, owner=user3, assigned_to=None,
|
||||
status=status1, tags=[tag1, tag2])
|
||||
f.TaskFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status0, tags=[tag3])
|
||||
f.TaskFactory.create(project=project, owner=user1, assigned_to=user1,
|
||||
status=status0, tags=[tag1, tag2, tag3])
|
||||
f.TaskFactory.create(project=project, owner=user3, assigned_to=user1,
|
||||
status=status2, tags=[tag3])
|
||||
f.TaskFactory.create(project=project, owner=user2, assigned_to=user1,
|
||||
status=status3, tags=[tag1, tag2])
|
||||
f.TaskFactory.create(project=project, owner=user1, assigned_to=user2,
|
||||
status=status0, tags=[tag3])
|
||||
f.TaskFactory.create(project=project, owner=user3, assigned_to=user2,
|
||||
status=status3, tags=[tag1])
|
||||
f.TaskFactory.create(project=project, owner=user2, assigned_to=user3,
|
||||
status=status1, tags=[tag0])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def test_get_tasks_from_bulk():
|
||||
data = """
|
||||
Task #1
|
||||
|
@ -852,45 +796,63 @@ def test_api_filter_by_milestone__estimated_start_and_end(client, field_name):
|
|||
assert response.data[0]["subject"] == task.subject
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filter_name,collection,expected,exclude_expected,is_text", [
|
||||
('status', 'statuses', 3, 7, False),
|
||||
('assigned_to', 'users', 3, 7, False),
|
||||
('tags', 'tags', 1, 9, True),
|
||||
('owner', 'users', 3, 7, False),
|
||||
('role', 'roles', 3, 7, False),
|
||||
])
|
||||
def test_api_filters(client, filter_name, collection, expected, exclude_expected, is_text):
|
||||
data = create_tasks_fixtures()
|
||||
project = data["project"]
|
||||
options = data[collection]
|
||||
|
||||
client.login(data["users"][0])
|
||||
if is_text:
|
||||
param = options[0]
|
||||
else:
|
||||
param = options[0].id
|
||||
|
||||
# include test
|
||||
url = "{}?project={}&{}={}".format(reverse('tasks-list'), project.id, filter_name, param)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == expected
|
||||
|
||||
# exclude test
|
||||
url = "{}?project={}&exclude_{}={}".format(reverse('tasks-list'), project.id, filter_name, param)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == exclude_expected
|
||||
|
||||
|
||||
def test_api_filters_data(client):
|
||||
data = create_tasks_fixtures()
|
||||
project = data["project"]
|
||||
(user1, user2, user3, ) = data["users"]
|
||||
(status0, status1, status2, status3, ) = data["statuses"]
|
||||
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||
project = f.ProjectFactory.create()
|
||||
user1 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user1, project=project)
|
||||
user2 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user2, project=project)
|
||||
user3 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user3, project=project)
|
||||
|
||||
status0 = f.TaskStatusFactory.create(project=project)
|
||||
status1 = f.TaskStatusFactory.create(project=project)
|
||||
status2 = f.TaskStatusFactory.create(project=project)
|
||||
status3 = f.TaskStatusFactory.create(project=project)
|
||||
|
||||
tag0 = "test1test2test3"
|
||||
tag1 = "test1"
|
||||
tag2 = "test2"
|
||||
tag3 = "test3"
|
||||
|
||||
# ------------------------------------------------------
|
||||
# | Task | Owner | Assigned To | Tags |
|
||||
# |-------#--------#-------------#---------------------|
|
||||
# | 0 | user2 | None | tag1 |
|
||||
# | 1 | user1 | None | tag2 |
|
||||
# | 2 | user3 | None | tag1 tag2 |
|
||||
# | 3 | user2 | None | tag3 |
|
||||
# | 4 | user1 | user1 | tag1 tag2 tag3 |
|
||||
# | 5 | user3 | user1 | tag3 |
|
||||
# | 6 | user2 | user1 | tag1 tag2 |
|
||||
# | 7 | user1 | user2 | tag3 |
|
||||
# | 8 | user3 | user2 | tag1 |
|
||||
# | 9 | user2 | user3 | tag0 |
|
||||
# ------------------------------------------------------
|
||||
|
||||
task0 = f.TaskFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status3, tags=[tag1])
|
||||
task1 = f.TaskFactory.create(project=project, owner=user1, assigned_to=None,
|
||||
status=status3, tags=[tag2])
|
||||
task2 = f.TaskFactory.create(project=project, owner=user3, assigned_to=None,
|
||||
status=status1, tags=[tag1, tag2])
|
||||
task3 = f.TaskFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status0, tags=[tag3])
|
||||
task4 = f.TaskFactory.create(project=project, owner=user1, assigned_to=user1,
|
||||
status=status0, tags=[tag1, tag2, tag3])
|
||||
task5 = f.TaskFactory.create(project=project, owner=user3, assigned_to=user1,
|
||||
status=status2, tags=[tag3])
|
||||
task6 = f.TaskFactory.create(project=project, owner=user2, assigned_to=user1,
|
||||
status=status3, tags=[tag1, tag2])
|
||||
task7 = f.TaskFactory.create(project=project, owner=user1, assigned_to=user2,
|
||||
status=status0, tags=[tag3])
|
||||
task8 = f.TaskFactory.create(project=project, owner=user3, assigned_to=user2,
|
||||
status=status3, tags=[tag1])
|
||||
task9 = f.TaskFactory.create(project=project, owner=user2, assigned_to=user3,
|
||||
status=status1, tags=[tag0])
|
||||
|
||||
url = reverse("tasks-filters-data") + "?project={}".format(project.id)
|
||||
|
||||
client.login(user1)
|
||||
|
||||
## No filter
|
||||
|
|
|
@ -38,71 +38,6 @@ import pytest
|
|||
pytestmark = pytest.mark.django_db(transaction=True)
|
||||
|
||||
|
||||
def create_uss_fixtures():
|
||||
data = {}
|
||||
|
||||
data["project"] = f.ProjectFactory.create()
|
||||
project = data["project"]
|
||||
data["users"] = [f.UserFactory.create(is_superuser=True) for i in range(0, 3)]
|
||||
data["roles"] = [f.RoleFactory.create() for i in range(0, 3)]
|
||||
user_roles = zip(data["users"], data["roles"])
|
||||
# Add membership fixtures
|
||||
[f.MembershipFactory.create(user=user, project=project, role=role) for (user, role) in user_roles]
|
||||
|
||||
data["statuses"] = [f.UserStoryStatusFactory.create(project=project) for i in range(0, 4)]
|
||||
data["epics"] = [f.EpicFactory.create(project=project) for i in range(0, 3)]
|
||||
data["tags"] = ["test1test2test3", "test1", "test2", "test3"]
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------
|
||||
# | US | Status | Owner | Assigned To | Assigned Users | Tags | Epic |
|
||||
# |-------#---------#--------#-------------#---------------------#---------------------#--------------
|
||||
# | 0 | status3 | user2 | None | None | tag1 | epic0 |
|
||||
# | 1 | status3 | user1 | None | user1 | tag2 | None |
|
||||
# | 2 | status1 | user3 | None | None | tag1 tag2 | epic1 |
|
||||
# | 3 | status0 | user2 | None | None | tag3 | None |
|
||||
# | 4 | status0 | user1 | user1 | None | tag1 tag2 tag3 | epic0 |
|
||||
# | 5 | status2 | user3 | user1 | None | tag3 | None |
|
||||
# | 6 | status3 | user2 | user1 | None | tag1 tag2 | epic0 epic2 |
|
||||
# | 7 | status0 | user1 | user2 | None | tag3 | None |
|
||||
# | 8 | status3 | user3 | user2 | None | tag1 | epic2 |
|
||||
# | 9 | status1 | user2 | user3 | user1 | tag0 | None |
|
||||
# ----------------------------------------------------------------------------------------------------
|
||||
|
||||
(user1, user2, user3, ) = data["users"]
|
||||
(status0, status1, status2, status3 ) = data["statuses"]
|
||||
(epic0, epic1, epic2) = data["epics"]
|
||||
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||
|
||||
us0 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status3, tags=[tag1])
|
||||
f.RelatedUserStory.create(user_story=us0, epic=epic0)
|
||||
us1 = f.UserStoryFactory.create(project=project, owner=user1, assigned_to=None,
|
||||
status=status3, tags=[tag2], assigned_users=[user1])
|
||||
us2 = f.UserStoryFactory.create(project=project, owner=user3, assigned_to=None,
|
||||
status=status1, tags=[tag1, tag2])
|
||||
f.RelatedUserStory.create(user_story=us2, epic=epic1)
|
||||
us3 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status0, tags=[tag3])
|
||||
us4 = f.UserStoryFactory.create(project=project, owner=user1, assigned_to=user1,
|
||||
status=status0, tags=[tag1, tag2, tag3])
|
||||
f.RelatedUserStory.create(user_story=us4, epic=epic0)
|
||||
us5 = f.UserStoryFactory.create(project=project, owner=user3, assigned_to=user1,
|
||||
status=status2, tags=[tag3])
|
||||
us6 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=user1,
|
||||
status=status3, tags=[tag1, tag2])
|
||||
f.RelatedUserStory.create(user_story=us6, epic=epic0)
|
||||
f.RelatedUserStory.create(user_story=us6, epic=epic2)
|
||||
us7 = f.UserStoryFactory.create(project=project, owner=user1, assigned_to=user2,
|
||||
status=status0, tags=[tag3])
|
||||
us8 = f.UserStoryFactory.create(project=project, owner=user3, assigned_to=user2,
|
||||
status=status3, tags=[tag1])
|
||||
f.RelatedUserStory.create(user_story=us8, epic=epic2)
|
||||
us9 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=user3,
|
||||
status=status1, tags=[tag0], assigned_users=[user1])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def test_get_userstories_from_bulk():
|
||||
data = "User Story #1\nUser Story #2\n"
|
||||
userstories = services.get_userstories_from_bulk(data)
|
||||
|
@ -842,14 +777,72 @@ def test_api_filter_by_milestone__estimated_start_and_end(client, field_name):
|
|||
|
||||
|
||||
def test_api_filters_data(client):
|
||||
data = create_uss_fixtures()
|
||||
project = data["project"]
|
||||
(user1, user2, user3, ) = data["users"]
|
||||
(status0, status1, status2, status3, ) = data["statuses"]
|
||||
(epic0, epic1, epic2, ) = data["epics"]
|
||||
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||
project = f.ProjectFactory.create()
|
||||
user1 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user1, project=project)
|
||||
user2 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user2, project=project)
|
||||
user3 = f.UserFactory.create(is_superuser=True)
|
||||
f.MembershipFactory.create(user=user3, project=project)
|
||||
|
||||
status0 = f.UserStoryStatusFactory.create(project=project)
|
||||
status1 = f.UserStoryStatusFactory.create(project=project)
|
||||
status2 = f.UserStoryStatusFactory.create(project=project)
|
||||
status3 = f.UserStoryStatusFactory.create(project=project)
|
||||
|
||||
epic0 = f.EpicFactory.create(project=project)
|
||||
epic1 = f.EpicFactory.create(project=project)
|
||||
epic2 = f.EpicFactory.create(project=project)
|
||||
|
||||
tag0 = "test1test2test3"
|
||||
tag1 = "test1"
|
||||
tag2 = "test2"
|
||||
tag3 = "test3"
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------
|
||||
# | US | Status | Owner | Assigned To | Assigned Users | Tags | Epic |
|
||||
# |-------#---------#--------#-------------#---------------------#---------------------#--------------
|
||||
# | 0 | status3 | user2 | None | None | tag1 | epic0 |
|
||||
# | 1 | status3 | user1 | None | user1 | tag2 | None |
|
||||
# | 2 | status1 | user3 | None | None | tag1 tag2 | epic1 |
|
||||
# | 3 | status0 | user2 | None | None | tag3 | None |
|
||||
# | 4 | status0 | user1 | user1 | None | tag1 tag2 tag3 | epic0 |
|
||||
# | 5 | status2 | user3 | user1 | None | tag3 | None |
|
||||
# | 6 | status3 | user2 | user1 | None | tag1 tag2 | epic0 epic2 |
|
||||
# | 7 | status0 | user1 | user2 | None | tag3 | None |
|
||||
# | 8 | status3 | user3 | user2 | None | tag1 | epic2 |
|
||||
# | 9 | status1 | user2 | user3 | user1 | tag0 | None |
|
||||
# ----------------------------------------------------------------------------------------------------
|
||||
|
||||
us0 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status3, tags=[tag1])
|
||||
f.RelatedUserStory.create(user_story=us0, epic=epic0)
|
||||
us1 = f.UserStoryFactory.create(project=project, owner=user1, assigned_to=None,
|
||||
status=status3, tags=[tag2], assigned_users=[user1])
|
||||
us2 = f.UserStoryFactory.create(project=project, owner=user3, assigned_to=None,
|
||||
status=status1, tags=[tag1, tag2])
|
||||
f.RelatedUserStory.create(user_story=us2, epic=epic1)
|
||||
us3 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=None,
|
||||
status=status0, tags=[tag3])
|
||||
us4 = f.UserStoryFactory.create(project=project, owner=user1, assigned_to=user1,
|
||||
status=status0, tags=[tag1, tag2, tag3])
|
||||
f.RelatedUserStory.create(user_story=us4, epic=epic0)
|
||||
us5 = f.UserStoryFactory.create(project=project, owner=user3, assigned_to=user1,
|
||||
status=status2, tags=[tag3])
|
||||
us6 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=user1,
|
||||
status=status3, tags=[tag1, tag2])
|
||||
f.RelatedUserStory.create(user_story=us6, epic=epic0)
|
||||
f.RelatedUserStory.create(user_story=us6, epic=epic2)
|
||||
us7 = f.UserStoryFactory.create(project=project, owner=user1, assigned_to=user2,
|
||||
status=status0, tags=[tag3])
|
||||
us8 = f.UserStoryFactory.create(project=project, owner=user3, assigned_to=user2,
|
||||
status=status3, tags=[tag1])
|
||||
f.RelatedUserStory.create(user_story=us8, epic=epic2)
|
||||
us9 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=user3,
|
||||
status=status1, tags=[tag0], assigned_users=[user1])
|
||||
|
||||
url = reverse("userstories-filters-data") + "?project={}".format(project.id)
|
||||
|
||||
client.login(user1)
|
||||
|
||||
# No filter
|
||||
|
@ -968,37 +961,6 @@ def test_api_filters_data(client):
|
|||
assert next(filter(lambda i: i['id'] == epic2.id, response.data["epics"]))["count"] == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filter_name,collection,expected,exclude_expected,is_text", [
|
||||
('status', 'statuses', 3, 7, False),
|
||||
('tags', 'tags', 1, 9, True),
|
||||
('owner', 'users', 3, 7, False),
|
||||
('role', 'roles', 5, 5, False),
|
||||
('assigned_users', 'users', 5, 5, False),
|
||||
])
|
||||
def test_api_filters(client, filter_name, collection, expected, exclude_expected, is_text):
|
||||
data = create_uss_fixtures()
|
||||
project = data["project"]
|
||||
options = data[collection]
|
||||
|
||||
client.login(data["users"][0])
|
||||
if is_text:
|
||||
param = options[0]
|
||||
else:
|
||||
param = options[0].id
|
||||
|
||||
# include test
|
||||
url = "{}?project={}&{}={}".format(reverse('userstories-list'), project.id, filter_name, param)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == expected
|
||||
|
||||
# exclude test
|
||||
url = "{}?project={}&exclude_{}={}".format(reverse('userstories-list'), project.id, filter_name, param)
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == exclude_expected
|
||||
|
||||
|
||||
def test_api_filters_data_with_assigned_users(client):
|
||||
project = f.ProjectFactory.create()
|
||||
user1 = f.UserFactory.create(is_superuser=True)
|
||||
|
|
|
@ -1,268 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from .. import factories as f
|
||||
from taiga.projects.milestones import services
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_issues_not_closed():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_issue(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
f.create_issue(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_issues_closed():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_issue(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
|
||||
assert services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_issues_but_closed_tasks():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
tasks_closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1,
|
||||
taskboard_order=1, status=tasks_closed_status)
|
||||
f.create_issue(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_issues_but_closed_uss():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
us_closed_status = f.UserStoryStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_userstory(project=project, milestone=milestone1,
|
||||
status=us_closed_status, is_closed=True)
|
||||
|
||||
f.create_issue(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_closed_issues_but_open_uss():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_issue(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
|
||||
f.create_userstory(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_closed_issues_but_open_tasks():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_issue(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
|
||||
f.create_task(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_tasks_not_closed():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
f.create_task(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_tasks_closed():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1,
|
||||
status=closed_status, user_story=None)
|
||||
|
||||
assert services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_tasks_but_closed_issues():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
issue_closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_issue(project=project, milestone=milestone1,
|
||||
status=issue_closed_status)
|
||||
f.create_task(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_tasks_but_closed_uss():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
us_closed_status = f.UserStoryStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_userstory(project=project, milestone=milestone1,
|
||||
status=us_closed_status, is_closed=True)
|
||||
|
||||
f.create_task(project=project, milestone=milestone1, user_story=None)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_closed_tasks_but_open_uss():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1,
|
||||
status=closed_status, user_story=None)
|
||||
|
||||
f.create_userstory(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_closed_tasks_but_open_issues():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1,
|
||||
status=closed_status, user_story=None)
|
||||
|
||||
f.create_issue(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_uss_not_closed():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.UserStoryStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_userstory(project=project, milestone=milestone1,
|
||||
status=closed_status, is_closed=True)
|
||||
f.create_userstory(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_uss_closed():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.UserStoryStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_userstory(project=project, milestone=milestone1,
|
||||
sprint_order=1, status=closed_status,
|
||||
is_closed=True)
|
||||
|
||||
assert services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_uss_but_closed_tasks_and_us():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
us_closed_status = f.UserStoryStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
us = f.create_userstory(project=project, milestone=milestone1,
|
||||
status=us_closed_status, is_closed=True)
|
||||
|
||||
task_closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1, user_story=us,
|
||||
status=task_closed_status)
|
||||
|
||||
f.create_userstory(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_uss_but_closed_tasks():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.TaskStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_task(project=project, milestone=milestone1,
|
||||
status=closed_status, user_story=None)
|
||||
f.create_userstory(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
||||
|
||||
|
||||
def test_stay_open_with_uss_but_closed_issues():
|
||||
project = f.ProjectFactory()
|
||||
f.MembershipFactory.create(project=project, user=project.owner,
|
||||
is_admin=True)
|
||||
milestone1 = f.MilestoneFactory.create(project=project)
|
||||
|
||||
closed_status = f.IssueStatusFactory.create(project=project,
|
||||
is_closed=True)
|
||||
f.create_issue(project=project, milestone=milestone1,
|
||||
status=closed_status)
|
||||
f.create_userstory(project=project, milestone=milestone1)
|
||||
|
||||
assert not services.calculate_milestone_is_closed(milestone1)
|
Loading…
Reference in New Issue