Compare commits
31 Commits
Author | SHA1 | Date |
---|---|---|
|
120442206b | |
|
d6d3f8c6a8 | |
|
3cebad87eb | |
|
f45f5ae08a | |
|
0434e8b78b | |
|
0c0e09819a | |
|
2b38fefa13 | |
|
7c5ba16d24 | |
|
b0d065167c | |
|
4bb12d73d9 | |
|
e736846562 | |
|
2bdd652ea7 | |
|
483d3ffd5f | |
|
2d77f8974b | |
|
77fa09a953 | |
|
df9830bb4f | |
|
849ce97a1c | |
|
c260a4dd22 | |
|
5f301450df | |
|
abf2b11220 | |
|
a4256c3f09 | |
|
18e97be27c | |
|
a17ed83755 | |
|
fe4cddac30 | |
|
0cb423c929 | |
|
39e9de71cf | |
|
4c74e6182f | |
|
dae83618a1 | |
|
97b69cdb61 | |
|
a5386cb79c | |
|
6a0a55f982 |
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -2,6 +2,35 @@
|
||||||
|
|
||||||
## Unreleased
|
## 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)
|
## 4.0.1 (2018-11-28)
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
2
Pipfile
2
Pipfile
|
@ -31,6 +31,7 @@ pytz = "*"
|
||||||
raven = "==6.1.0"
|
raven = "==6.1.0"
|
||||||
redis = "==2.10.5"
|
redis = "==2.10.5"
|
||||||
requests = "==2.20.0"
|
requests = "==2.20.0"
|
||||||
|
requests_oauthlib = "*"
|
||||||
serpy = "==0.1.1"
|
serpy = "==0.1.1"
|
||||||
webcolors = "==1.7"
|
webcolors = "==1.7"
|
||||||
CairoSVG = "==2.0.3"
|
CairoSVG = "==2.0.3"
|
||||||
|
@ -39,6 +40,7 @@ Markdown = "==3.0.1"
|
||||||
Pillow = "==4.1.1"
|
Pillow = "==4.1.1"
|
||||||
Unidecode = "==0.4.20"
|
Unidecode = "==0.4.20"
|
||||||
Pygments = "==2.2.0"
|
Pygments = "==2.2.0"
|
||||||
|
oauthlib = {extras = ["signedtoken"],version = "*"}
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
coverage = "*"
|
coverage = "*"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "888d57920f90a3bcb3ad23268eef74f7c2951caa473cab150774fe460ac797a5"
|
"sha256": "2505838cdc4b5390130990ae63647a4447c5f146ab37ca8bf29f627091112cf7"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -40,9 +40,9 @@
|
||||||
},
|
},
|
||||||
"billiard": {
|
"billiard": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:ed65448da5877b5558f19d2f7f11f8355ea76b3e63e1c0a6059f47cfae5f1c84"
|
"sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e"
|
||||||
],
|
],
|
||||||
"version": "==3.5.0.4"
|
"version": "==3.5.0.5"
|
||||||
},
|
},
|
||||||
"bleach": {
|
"bleach": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -75,10 +75,10 @@
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
|
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||||
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
|
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||||
],
|
],
|
||||||
"version": "==2018.10.15"
|
"version": "==2018.11.29"
|
||||||
},
|
},
|
||||||
"cffi": {
|
"cffi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -498,6 +498,13 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.0"
|
"version": "==2.2.0"
|
||||||
},
|
},
|
||||||
|
"pyjwt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:00414bfef802aaecd8cc0d5258b6cb87bd8f553c2986c2c5f29b19dd5633aeb7",
|
||||||
|
"sha256:ddec8409c57e9d371c6006e388f91daf3b0b43bdf9fcbf99451fb7cf5ce0a86d"
|
||||||
|
],
|
||||||
|
"version": "==1.7.0"
|
||||||
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
|
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
|
||||||
|
@ -634,10 +641,10 @@
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
|
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||||
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
|
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||||
],
|
],
|
||||||
"version": "==2018.10.15"
|
"version": "==2018.11.29"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -707,10 +714,10 @@
|
||||||
},
|
},
|
||||||
"faker": {
|
"faker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2621643b80a10b91999925cfd20f64d2b36f20bf22136bbdc749bb57d6ffe124",
|
"sha256:c61a41d0dab8865b850bd00454fb11e90f3fd2a092d8bc90120d1e1c01cff906",
|
||||||
"sha256:5ed822d31bd2d6edf10944d176d30dc9c886afdd381eefb7ba8b7aad86171646"
|
"sha256:f909ff9133ce0625ca388b6838190630ad7a593f87eaf058d872338a76241d5d"
|
||||||
],
|
],
|
||||||
"version": "==0.9.2"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
amqp==2.3.2
|
amqp==2.3.2
|
||||||
asana==0.6.7
|
asana==0.6.7
|
||||||
asn1crypto==0.24.0
|
asn1crypto==0.24.0
|
||||||
billiard==3.5.0.4
|
billiard==3.5.0.5
|
||||||
bleach==2.1.4
|
bleach==2.1.4
|
||||||
cairocffi==0.9.0
|
cairocffi==0.9.0
|
||||||
cairosvg==2.0.3
|
cairosvg==2.0.3
|
||||||
celery==4.0.2
|
celery==4.0.2
|
||||||
certifi==2018.10.15
|
certifi==2018.11.29
|
||||||
cffi==1.11.5
|
cffi==1.11.5
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
contextlib2==0.5.5
|
contextlib2==0.5.5
|
||||||
|
@ -44,6 +44,7 @@ psd-tools==1.4
|
||||||
psycopg2-binary==2.7.5
|
psycopg2-binary==2.7.5
|
||||||
pycparser==2.19
|
pycparser==2.19
|
||||||
pygments==2.2.0
|
pygments==2.2.0
|
||||||
|
pyjwt==1.7.0
|
||||||
python-dateutil==2.7.5
|
python-dateutil==2.7.5
|
||||||
python-magic==0.4.13
|
python-magic==0.4.13
|
||||||
pytz==2018.7
|
pytz==2018.7
|
||||||
|
|
|
@ -148,7 +148,7 @@ LANGUAGES = [
|
||||||
("tr", "Türkçe"), # Turkish
|
("tr", "Türkçe"), # Turkish
|
||||||
#("tt", "татар теле"), # Tatar
|
#("tt", "татар теле"), # Tatar
|
||||||
#("udm", "удмурт кыл"), # Udmurt
|
#("udm", "удмурт кыл"), # Udmurt
|
||||||
#("uk", "Українська"), # Ukrainian
|
("uk", "Українська"), # Ukrainian
|
||||||
#("ur", "اردو"), # Urdu
|
#("ur", "اردو"), # Urdu
|
||||||
#("vi", "Tiếng Việt"), # Vietnamese
|
#("vi", "Tiếng Việt"), # Vietnamese
|
||||||
("zh-hans", "中文(简体)"), # Simplified Chinese
|
("zh-hans", "中文(简体)"), # Simplified Chinese
|
||||||
|
|
|
@ -375,14 +375,18 @@ class IsProjectAdminFromWebhookLogFilterBackend(FilterBackend, BaseIsProjectAdmi
|
||||||
class BaseRelatedFieldsFilter(FilterBackend):
|
class BaseRelatedFieldsFilter(FilterBackend):
|
||||||
filter_name = None
|
filter_name = None
|
||||||
param_name = None
|
param_name = None
|
||||||
|
exclude_param_name = None
|
||||||
|
|
||||||
def __init__(self, filter_name=None, param_name=None):
|
def __init__(self, filter_name=None, param_name=None, exclude_param_name=None):
|
||||||
if filter_name:
|
if filter_name:
|
||||||
self.filter_name = filter_name
|
self.filter_name = filter_name
|
||||||
|
|
||||||
if param_name:
|
if param_name:
|
||||||
self.param_name = param_name
|
self.param_name = param_name
|
||||||
|
|
||||||
|
if exclude_param_name:
|
||||||
|
self.exclude_param_name
|
||||||
|
|
||||||
def _prepare_filter_data(self, query_param_value):
|
def _prepare_filter_data(self, query_param_value):
|
||||||
def _transform_value(value):
|
def _transform_value(value):
|
||||||
try:
|
try:
|
||||||
|
@ -396,48 +400,57 @@ class BaseRelatedFieldsFilter(FilterBackend):
|
||||||
values = map(_transform_value, values)
|
values = map(_transform_value, values)
|
||||||
return list(values)
|
return list(values)
|
||||||
|
|
||||||
def _get_queryparams(self, params):
|
def _get_queryparams(self, params, mode=''):
|
||||||
param_name = self.param_name or self.filter_name
|
param_name = self.exclude_param_name if mode == 'exclude' else self.param_name or self.filter_name
|
||||||
raw_value = params.get(param_name, None)
|
raw_value = params.get(param_name, None)
|
||||||
|
|
||||||
if raw_value:
|
if raw_value:
|
||||||
value = self._prepare_filter_data(raw_value)
|
value = self._prepare_filter_data(raw_value)
|
||||||
|
|
||||||
if None in value:
|
if None in value:
|
||||||
qs_in_kwargs = {"{}__in".format(self.filter_name): [v for v in value if v is not None]}
|
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}
|
qs_isnull_kwargs = {"{}__isnull".format(self.filter_name): True}
|
||||||
return Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs)
|
return Q(**qs_in_kwargs) | Q(**qs_isnull_kwargs)
|
||||||
else:
|
else:
|
||||||
return {"{}__in".format(self.filter_name): value}
|
return Q(**{"{}__in".format(self.filter_name): value})
|
||||||
|
|
||||||
return None
|
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):
|
def filter_queryset(self, request, queryset, view):
|
||||||
query = self._get_queryparams(request.QUERY_PARAMS)
|
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:
|
if query:
|
||||||
if isinstance(query, dict):
|
queryset = queryset.filter(prepare_method(query))
|
||||||
queryset = queryset.filter(**query)
|
|
||||||
else:
|
|
||||||
queryset = queryset.filter(query)
|
|
||||||
|
|
||||||
return super().filter_queryset(request, queryset, view)
|
return super().filter_queryset(request, queryset, view)
|
||||||
|
|
||||||
|
|
||||||
class OwnersFilter(BaseRelatedFieldsFilter):
|
class OwnersFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = 'owner'
|
filter_name = 'owner'
|
||||||
|
exclude_param_name = 'exclude_owner'
|
||||||
|
|
||||||
|
|
||||||
class AssignedToFilter(BaseRelatedFieldsFilter):
|
class AssignedToFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = 'assigned_to'
|
filter_name = 'assigned_to'
|
||||||
|
exclude_param_name = 'exclude_assigned_to'
|
||||||
|
|
||||||
|
|
||||||
class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
||||||
filter_name = 'assigned_users'
|
filter_name = 'assigned_users'
|
||||||
|
exclude_param_name = 'exclude_assigned_users'
|
||||||
|
|
||||||
def _get_queryparams(self, params):
|
def _get_queryparams(self, params, mode=''):
|
||||||
param_name = self.param_name or self.filter_name
|
param_name = self.exclude_param_name if mode == 'exclude' else self.param_name or self.filter_name
|
||||||
raw_value = params.get(param_name, None)
|
raw_value = params.get(param_name, None)
|
||||||
|
|
||||||
if raw_value:
|
if raw_value:
|
||||||
value = self._prepare_filter_data(raw_value)
|
value = self._prepare_filter_data(raw_value)
|
||||||
UserStoryModel = apps.get_model("userstories", "UserStory")
|
UserStoryModel = apps.get_model("userstories", "UserStory")
|
||||||
|
@ -461,38 +474,65 @@ class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
||||||
|
|
||||||
class StatusesFilter(BaseRelatedFieldsFilter):
|
class StatusesFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = 'status'
|
filter_name = 'status'
|
||||||
|
exclude_param_name = 'exclude_status'
|
||||||
|
|
||||||
|
|
||||||
class IssueTypesFilter(BaseRelatedFieldsFilter):
|
class IssueTypesFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = 'type'
|
filter_name = 'type'
|
||||||
|
param_name = 'type'
|
||||||
|
exclude_param_name = 'exclude_type'
|
||||||
|
|
||||||
|
|
||||||
class PrioritiesFilter(BaseRelatedFieldsFilter):
|
class PrioritiesFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = 'priority'
|
filter_name = 'priority'
|
||||||
|
exclude_param_name = 'exclude_priority'
|
||||||
|
|
||||||
|
|
||||||
class SeveritiesFilter(BaseRelatedFieldsFilter):
|
class SeveritiesFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = 'severity'
|
filter_name = 'severity'
|
||||||
|
exclude_param_name = 'exclude_severity'
|
||||||
|
|
||||||
|
|
||||||
class TagsFilter(FilterBackend):
|
class TagsFilter(FilterBackend):
|
||||||
filter_name = 'tags'
|
filter_name = 'tags'
|
||||||
|
exclude_param_name = 'exclude_tags'
|
||||||
|
|
||||||
def __init__(self, filter_name=None):
|
def __init__(self, filter_name=None, exclude_param_name=None):
|
||||||
if filter_name:
|
if filter_name:
|
||||||
self.filter_name = filter_name
|
self.filter_name = filter_name
|
||||||
|
|
||||||
def _get_tags_queryparams(self, params):
|
if exclude_param_name:
|
||||||
tags = params.get(self.filter_name, None)
|
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)
|
||||||
if tags:
|
if tags:
|
||||||
return tags.split(",")
|
return tags.split(",")
|
||||||
|
|
||||||
return None
|
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):
|
def filter_queryset(self, request, queryset, view):
|
||||||
query_tags = self._get_tags_queryparams(request.QUERY_PARAMS)
|
operations = {
|
||||||
if query_tags:
|
"filter": self._prepare_filter_query,
|
||||||
queryset = queryset.filter(tags__contains=query_tags)
|
"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))
|
||||||
|
|
||||||
return super().filter_queryset(request, queryset, view)
|
return super().filter_queryset(request, queryset, view)
|
||||||
|
|
||||||
|
@ -631,18 +671,22 @@ class QFilter(FilterBackend):
|
||||||
class RoleFilter(BaseRelatedFieldsFilter):
|
class RoleFilter(BaseRelatedFieldsFilter):
|
||||||
filter_name = "role_id"
|
filter_name = "role_id"
|
||||||
param_name = "role"
|
param_name = "role"
|
||||||
|
exclude_param_name = "exclude_role"
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
Membership = apps.get_model('projects', 'Membership')
|
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:
|
if query:
|
||||||
if isinstance(query, dict):
|
memberships = Membership.objects.filter(query).exclude(user__isnull=True).values_list("user_id", flat=True)
|
||||||
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:
|
if memberships:
|
||||||
queryset = queryset.filter(assigned_to__in=memberships)
|
queryset = queryset.filter(qs_method(Q(assigned_to__in=memberships)))
|
||||||
|
|
||||||
return FilterBackend.filter_queryset(self, request, queryset, view)
|
return FilterBackend.filter_queryset(self, request, queryset, view)
|
||||||
|
|
||||||
|
@ -650,20 +694,24 @@ class RoleFilter(BaseRelatedFieldsFilter):
|
||||||
class UserStoriesRoleFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
class UserStoriesRoleFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
|
||||||
filter_name = "role_id"
|
filter_name = "role_id"
|
||||||
param_name = "role"
|
param_name = "role"
|
||||||
|
exclude_param_name = 'exclude_role'
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
Membership = apps.get_model('projects', 'Membership')
|
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:
|
if query:
|
||||||
if isinstance(query, dict):
|
memberships = Membership.objects.filter(query).exclude(user__isnull=True).values_list("user_id", flat=True)
|
||||||
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:
|
if memberships:
|
||||||
user_story_model = apps.get_model("userstories", "UserStory")
|
user_story_model = apps.get_model("userstories", "UserStory")
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
self.get_assigned_users_filter(user_story_model, memberships)
|
qs_method(Q(self.get_assigned_users_filter(user_story_model, memberships)))
|
||||||
)
|
)
|
||||||
|
|
||||||
return FilterBackend.filter_queryset(self, request, queryset, view)
|
return FilterBackend.filter_queryset(self, request, queryset, view)
|
||||||
|
|
|
@ -4,14 +4,16 @@
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Translators:
|
# Translators:
|
||||||
|
# Amirhoshang Hoseinpour Dehkordi <amir.hoseinpour@gmail.com>, 2018
|
||||||
# Vahid Dayyani <vahid.dayyani@ymail.com>, 2018
|
# Vahid Dayyani <vahid.dayyani@ymail.com>, 2018
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: taiga-back\n"
|
"Project-Id-Version: taiga-back\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||||
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
|
"PO-Revision-Date: 2018-12-02 10:54+0000\n"
|
||||||
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
|
"Last-Translator: Amirhoshang Hoseinpour Dehkordi <amir.hoseinpour@gmail."
|
||||||
|
"com>\n"
|
||||||
"Language-Team: Persian (Iran) (http://www.transifex.com/taiga-agile-llc/"
|
"Language-Team: Persian (Iran) (http://www.transifex.com/taiga-agile-llc/"
|
||||||
"taiga-back/language/fa_IR/)\n"
|
"taiga-back/language/fa_IR/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -26,7 +28,7 @@ msgstr "ثبت نام عمومی غیرفعال است."
|
||||||
|
|
||||||
#: taiga/auth/api.py:93
|
#: taiga/auth/api.py:93
|
||||||
msgid "You must accept our terms of service and privacy policy"
|
msgid "You must accept our terms of service and privacy policy"
|
||||||
msgstr ""
|
msgstr "شما باید موارد سرویس و سیاست های امنیت ما را قبول کنید."
|
||||||
|
|
||||||
#: taiga/auth/api.py:102
|
#: taiga/auth/api.py:102
|
||||||
msgid "invalid register type"
|
msgid "invalid register type"
|
||||||
|
@ -73,7 +75,7 @@ msgstr "نام کاربری نامعتبر"
|
||||||
#: taiga/auth/validators.py:42 taiga/users/validators.py:50
|
#: taiga/auth/validators.py:42 taiga/users/validators.py:50
|
||||||
msgid ""
|
msgid ""
|
||||||
"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'"
|
"Required. 255 characters or fewer. Letters, numbers and /./-/_ characters'"
|
||||||
msgstr "ضروری است. ۲۵۵ کاراکتر یا کمتر. حروف و اعداد و . و - و ـ مجاز است."
|
msgstr "255 کاراکتر یا کمتر ضروری است. حروف و اعداد و . و - و ـ مجاز است."
|
||||||
|
|
||||||
#: taiga/base/api/fields.py:294
|
#: taiga/base/api/fields.py:294
|
||||||
msgid "This field is required."
|
msgid "This field is required."
|
||||||
|
@ -86,7 +88,7 @@ msgstr "مقدار نامعتبر."
|
||||||
#: taiga/base/api/fields.py:484
|
#: taiga/base/api/fields.py:484
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "'%s' value must be either True or False."
|
msgid "'%s' value must be either True or False."
|
||||||
msgstr "'%s' میبایست True (صحیح) یا False (غلط) باشد."
|
msgstr "'%s' میبایست صحیح یا غلط باشد."
|
||||||
|
|
||||||
#: taiga/base/api/fields.py:549
|
#: taiga/base/api/fields.py:549
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -517,91 +519,91 @@ msgstr ""
|
||||||
|
|
||||||
#: taiga/base/utils/urls.py:68
|
#: taiga/base/utils/urls.py:68
|
||||||
msgid "Host access error"
|
msgid "Host access error"
|
||||||
msgstr ""
|
msgstr "خطای دسترسی به هاست"
|
||||||
|
|
||||||
#: taiga/base/utils/urls.py:74
|
#: taiga/base/utils/urls.py:74
|
||||||
msgid "IP Address error"
|
msgid "IP Address error"
|
||||||
msgstr ""
|
msgstr "خطای آدرس آی پی"
|
||||||
|
|
||||||
#: taiga/events/events.py:106
|
#: taiga/events/events.py:106
|
||||||
msgid "User story created"
|
msgid "User story created"
|
||||||
msgstr ""
|
msgstr "گزارش کاربر ساخته شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:109
|
#: taiga/events/events.py:109
|
||||||
msgid "User story changed"
|
msgid "User story changed"
|
||||||
msgstr ""
|
msgstr "گزارش کاربر تغییر کرد."
|
||||||
|
|
||||||
#: taiga/events/events.py:112
|
#: taiga/events/events.py:112
|
||||||
msgid "User story deleted"
|
msgid "User story deleted"
|
||||||
msgstr ""
|
msgstr "گزارش کاربر حذف شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:114
|
#: taiga/events/events.py:114
|
||||||
msgid "US #{} - {}"
|
msgid "US #{} - {}"
|
||||||
msgstr ""
|
msgstr "US #{} - {}"
|
||||||
|
|
||||||
#: taiga/events/events.py:117
|
#: taiga/events/events.py:117
|
||||||
msgid "Task created"
|
msgid "Task created"
|
||||||
msgstr ""
|
msgstr "وظیفه ساخته شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:120
|
#: taiga/events/events.py:120
|
||||||
msgid "Task changed"
|
msgid "Task changed"
|
||||||
msgstr ""
|
msgstr "وظیفه تغییر کرد."
|
||||||
|
|
||||||
#: taiga/events/events.py:123
|
#: taiga/events/events.py:123
|
||||||
msgid "Task deleted"
|
msgid "Task deleted"
|
||||||
msgstr ""
|
msgstr "وظیفه حذف شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:125
|
#: taiga/events/events.py:125
|
||||||
msgid "Task #{} - {}"
|
msgid "Task #{} - {}"
|
||||||
msgstr ""
|
msgstr "وظیفه #{} - {}"
|
||||||
|
|
||||||
#: taiga/events/events.py:128
|
#: taiga/events/events.py:128
|
||||||
msgid "Issue created"
|
msgid "Issue created"
|
||||||
msgstr ""
|
msgstr "موضوع ساخته شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:131
|
#: taiga/events/events.py:131
|
||||||
msgid "Issue changed"
|
msgid "Issue changed"
|
||||||
msgstr ""
|
msgstr "موضوع تغییر کرد."
|
||||||
|
|
||||||
#: taiga/events/events.py:134
|
#: taiga/events/events.py:134
|
||||||
msgid "Issue deleted"
|
msgid "Issue deleted"
|
||||||
msgstr ""
|
msgstr "موضوع حذف شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:136
|
#: taiga/events/events.py:136
|
||||||
msgid "Issue: #{} - {}"
|
msgid "Issue: #{} - {}"
|
||||||
msgstr ""
|
msgstr "موضوع: #{} - {}"
|
||||||
|
|
||||||
#: taiga/events/events.py:139
|
#: taiga/events/events.py:139
|
||||||
msgid "Wiki Page created"
|
msgid "Wiki Page created"
|
||||||
msgstr ""
|
msgstr "صفحه ویکی ساخته شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:142
|
#: taiga/events/events.py:142
|
||||||
msgid "Wiki Page changed"
|
msgid "Wiki Page changed"
|
||||||
msgstr ""
|
msgstr "صفحه ویکی تغییر داده شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:145
|
#: taiga/events/events.py:145
|
||||||
msgid "Wiki Page deleted"
|
msgid "Wiki Page deleted"
|
||||||
msgstr ""
|
msgstr "صفحه ویکی حذف شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:147
|
#: taiga/events/events.py:147
|
||||||
msgid "Wiki Page: {}"
|
msgid "Wiki Page: {}"
|
||||||
msgstr ""
|
msgstr "صفحه ویکی: {}"
|
||||||
|
|
||||||
#: taiga/events/events.py:150
|
#: taiga/events/events.py:150
|
||||||
msgid "Sprint created"
|
msgid "Sprint created"
|
||||||
msgstr ""
|
msgstr "سرعتی ساخته شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:153
|
#: taiga/events/events.py:153
|
||||||
msgid "Sprint changed"
|
msgid "Sprint changed"
|
||||||
msgstr ""
|
msgstr "سرعتی تغییر داده شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:156
|
#: taiga/events/events.py:156
|
||||||
msgid "Sprint deleted"
|
msgid "Sprint deleted"
|
||||||
msgstr ""
|
msgstr "سرعتی حذف شد."
|
||||||
|
|
||||||
#: taiga/events/events.py:158
|
#: taiga/events/events.py:158
|
||||||
msgid "Sprint: {}"
|
msgid "Sprint: {}"
|
||||||
msgstr ""
|
msgstr "سرعتی: {}"
|
||||||
|
|
||||||
#: taiga/export_import/api.py:127
|
#: taiga/export_import/api.py:127
|
||||||
msgid "We needed at least one role"
|
msgid "We needed at least one role"
|
||||||
|
@ -1782,27 +1784,27 @@ msgstr "کاربر میبایست قبلاً از اعضای پروژه بو
|
||||||
|
|
||||||
#: taiga/projects/api.py:642
|
#: taiga/projects/api.py:642
|
||||||
msgid "You can't delete user story due date by default"
|
msgid "You can't delete user story due date by default"
|
||||||
msgstr ""
|
msgstr "شما سر رسید گزارش کاربر را نمی توانید بصورت عادی حذف کنید."
|
||||||
|
|
||||||
#: taiga/projects/api.py:658
|
#: taiga/projects/api.py:658
|
||||||
msgid "Project already have due dates"
|
msgid "Project already have due dates"
|
||||||
msgstr ""
|
msgstr "پروژه موعد سر رسید دارد."
|
||||||
|
|
||||||
#: taiga/projects/api.py:718
|
#: taiga/projects/api.py:718
|
||||||
msgid "You can't delete task due date by default"
|
msgid "You can't delete task due date by default"
|
||||||
msgstr ""
|
msgstr "شما سر رسید وظیفه کاربر را نمی توانید بصورت عادی حذف کنید."
|
||||||
|
|
||||||
#: taiga/projects/api.py:734
|
#: taiga/projects/api.py:734
|
||||||
msgid "Project already have task due dates"
|
msgid "Project already have task due dates"
|
||||||
msgstr ""
|
msgstr "پروژه موعد سر رسید وظیفه دارد."
|
||||||
|
|
||||||
#: taiga/projects/api.py:858
|
#: taiga/projects/api.py:858
|
||||||
msgid "You can't delete issue due date by default"
|
msgid "You can't delete issue due date by default"
|
||||||
msgstr ""
|
msgstr "شما سر رسید موضوع کاربر را نمی توانید بصورت عادی حذف کنید."
|
||||||
|
|
||||||
#: taiga/projects/api.py:874
|
#: taiga/projects/api.py:874
|
||||||
msgid "Project already have issue due dates"
|
msgid "Project already have issue due dates"
|
||||||
msgstr ""
|
msgstr "پروژه موعد سر رسید موضوع دارد."
|
||||||
|
|
||||||
#: taiga/projects/api.py:1023
|
#: taiga/projects/api.py:1023
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -2051,11 +2053,11 @@ msgstr "موردی با همین نام وجود دارد."
|
||||||
|
|
||||||
#: taiga/projects/due_dates/models.py:21
|
#: taiga/projects/due_dates/models.py:21
|
||||||
msgid "due date"
|
msgid "due date"
|
||||||
msgstr ""
|
msgstr "موعد سر رسید"
|
||||||
|
|
||||||
#: taiga/projects/due_dates/models.py:24
|
#: taiga/projects/due_dates/models.py:24
|
||||||
msgid "reason for the due date"
|
msgid "reason for the due date"
|
||||||
msgstr ""
|
msgstr "دلیل موعد سر رسید."
|
||||||
|
|
||||||
#: taiga/projects/epics/api.py:94
|
#: taiga/projects/epics/api.py:94
|
||||||
msgid "You don't have permissions to set this status to this epic."
|
msgid "You don't have permissions to set this status to this epic."
|
||||||
|
@ -2212,7 +2214,7 @@ msgstr "آزاد شد"
|
||||||
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:164
|
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:164
|
||||||
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:175
|
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:175
|
||||||
msgid "Not set"
|
msgid "Not set"
|
||||||
msgstr ""
|
msgstr "تنظیم نشده"
|
||||||
|
|
||||||
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:286
|
#: taiga/projects/history/templates/emails/includes/fields_diff-html.jinja:286
|
||||||
#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:91
|
#: taiga/projects/history/templates/emails/includes/fields_diff-text.jinja:91
|
||||||
|
@ -2567,12 +2569,12 @@ msgstr "ارزش"
|
||||||
#: taiga/projects/models.py:614 taiga/projects/models.py:671
|
#: taiga/projects/models.py:614 taiga/projects/models.py:671
|
||||||
#: taiga/projects/models.py:789
|
#: taiga/projects/models.py:789
|
||||||
msgid "by default"
|
msgid "by default"
|
||||||
msgstr ""
|
msgstr "بصورت عمومی"
|
||||||
|
|
||||||
#: taiga/projects/models.py:618 taiga/projects/models.py:675
|
#: taiga/projects/models.py:618 taiga/projects/models.py:675
|
||||||
#: taiga/projects/models.py:793
|
#: taiga/projects/models.py:793
|
||||||
msgid "days to due"
|
msgid "days to due"
|
||||||
msgstr ""
|
msgstr "روز تا موعد"
|
||||||
|
|
||||||
#: taiga/projects/models.py:823
|
#: taiga/projects/models.py:823
|
||||||
msgid "default owner's role"
|
msgid "default owner's role"
|
||||||
|
@ -2592,7 +2594,7 @@ msgstr "وضعیتهای استوریهای کاربری"
|
||||||
|
|
||||||
#: taiga/projects/models.py:849
|
#: taiga/projects/models.py:849
|
||||||
msgid "us duedates"
|
msgid "us duedates"
|
||||||
msgstr ""
|
msgstr "موعد ما"
|
||||||
|
|
||||||
#: taiga/projects/models.py:850 taiga/projects/userstories/models.py:45
|
#: taiga/projects/models.py:850 taiga/projects/userstories/models.py:45
|
||||||
#: taiga/projects/userstories/models.py:78
|
#: taiga/projects/userstories/models.py:78
|
||||||
|
@ -2605,7 +2607,7 @@ msgstr "وضعیتهای وظایف"
|
||||||
|
|
||||||
#: taiga/projects/models.py:852
|
#: taiga/projects/models.py:852
|
||||||
msgid "task duedates"
|
msgid "task duedates"
|
||||||
msgstr ""
|
msgstr "موعد وظیفه ها"
|
||||||
|
|
||||||
#: taiga/projects/models.py:853
|
#: taiga/projects/models.py:853
|
||||||
msgid "issue statuses"
|
msgid "issue statuses"
|
||||||
|
@ -2617,7 +2619,7 @@ msgstr "انواع موضوعات"
|
||||||
|
|
||||||
#: taiga/projects/models.py:855
|
#: taiga/projects/models.py:855
|
||||||
msgid "issue duedates"
|
msgid "issue duedates"
|
||||||
msgstr ""
|
msgstr "موعد موضوعات"
|
||||||
|
|
||||||
#: taiga/projects/models.py:856
|
#: taiga/projects/models.py:856
|
||||||
msgid "priorities"
|
msgid "priorities"
|
||||||
|
@ -4221,7 +4223,7 @@ msgstr "؟"
|
||||||
#. Translators: User story point value (value = 0)
|
#. Translators: User story point value (value = 0)
|
||||||
#: taiga/projects/translations.py:47
|
#: taiga/projects/translations.py:47
|
||||||
msgid "0"
|
msgid "0"
|
||||||
msgstr "۰"
|
msgstr "0"
|
||||||
|
|
||||||
#. Translators: User story point value (value = 0.5)
|
#. Translators: User story point value (value = 0.5)
|
||||||
#: taiga/projects/translations.py:49
|
#: taiga/projects/translations.py:49
|
||||||
|
@ -4461,7 +4463,7 @@ msgstr "تاریخ تکمیل"
|
||||||
|
|
||||||
#: taiga/projects/userstories/models.py:102
|
#: taiga/projects/userstories/models.py:102
|
||||||
msgid "assigned users"
|
msgid "assigned users"
|
||||||
msgstr ""
|
msgstr "کاربران متصل شده"
|
||||||
|
|
||||||
#: taiga/projects/userstories/models.py:111
|
#: taiga/projects/userstories/models.py:111
|
||||||
msgid "generated from issue"
|
msgid "generated from issue"
|
||||||
|
@ -4724,11 +4726,11 @@ msgstr "تاریخ عضویت"
|
||||||
|
|
||||||
#: taiga/users/models.py:155
|
#: taiga/users/models.py:155
|
||||||
msgid "accepted terms"
|
msgid "accepted terms"
|
||||||
msgstr ""
|
msgstr "قبول شرایط"
|
||||||
|
|
||||||
#: taiga/users/models.py:156
|
#: taiga/users/models.py:156
|
||||||
msgid "new terms read"
|
msgid "new terms read"
|
||||||
msgstr ""
|
msgstr "خواندن شرایط جدید"
|
||||||
|
|
||||||
#: taiga/users/models.py:158
|
#: taiga/users/models.py:158
|
||||||
msgid "default language"
|
msgid "default language"
|
||||||
|
@ -4972,7 +4974,7 @@ msgstr "نام کاربری نامعتبر. نام کاربری دیگری ان
|
||||||
|
|
||||||
#: taiga/users/validators.py:73
|
#: taiga/users/validators.py:73
|
||||||
msgid "Read new terms has to be true'"
|
msgid "Read new terms has to be true'"
|
||||||
msgstr ""
|
msgstr "خواندن شرایط جدید باید صحیح باشد"
|
||||||
|
|
||||||
#: taiga/userstorage/api.py:53
|
#: taiga/userstorage/api.py:53
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -5017,4 +5019,4 @@ msgstr "مدت زمان"
|
||||||
|
|
||||||
#: taiga/webhooks/validators.py:42
|
#: taiga/webhooks/validators.py:42
|
||||||
msgid "Not allowed IP Address"
|
msgid "Not allowed IP Address"
|
||||||
msgstr ""
|
msgstr "آی پی آدرس غیر مجاز"
|
||||||
|
|
|
@ -10,13 +10,14 @@
|
||||||
# Shun Yanaura <metroplexity@gmail.com>, 2016
|
# Shun Yanaura <metroplexity@gmail.com>, 2016
|
||||||
# Suguru Sato <usagi.vs.tanuki@gmail.com>, 2016
|
# Suguru Sato <usagi.vs.tanuki@gmail.com>, 2016
|
||||||
# Tomonori Tanabe <tanb+github@me.com>, 2015
|
# Tomonori Tanabe <tanb+github@me.com>, 2015
|
||||||
|
# Masaki Honda <mh35jp@gmail.com>, 2018
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: taiga-back\n"
|
"Project-Id-Version: taiga-back\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||||
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
|
"PO-Revision-Date: 2018-12-28 01:50+0000\n"
|
||||||
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
|
"Last-Translator: Masaki Honda <mh35jp@gmail.com>\n"
|
||||||
"Language-Team: Japanese (http://www.transifex.com/taiga-agile-llc/taiga-back/"
|
"Language-Team: Japanese (http://www.transifex.com/taiga-agile-llc/taiga-back/"
|
||||||
"language/ja/)\n"
|
"language/ja/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -31,7 +32,7 @@ msgstr "パブリックなレジスタは無効です。"
|
||||||
|
|
||||||
#: taiga/auth/api.py:93
|
#: taiga/auth/api.py:93
|
||||||
msgid "You must accept our terms of service and privacy policy"
|
msgid "You must accept our terms of service and privacy policy"
|
||||||
msgstr ""
|
msgstr "利用規約とプライバシーポリシーに同意する必要があります"
|
||||||
|
|
||||||
#: taiga/auth/api.py:102
|
#: taiga/auth/api.py:102
|
||||||
msgid "invalid register type"
|
msgid "invalid register type"
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
# Lucas Boscaini <lucasboscaini@gmail.com>, 2017
|
# Lucas Boscaini <lucasboscaini@gmail.com>, 2017
|
||||||
# Mairieli Wessel <mairieliw@alunos.utfpr.edu.br>, 2016
|
# Mairieli Wessel <mairieliw@alunos.utfpr.edu.br>, 2016
|
||||||
# Marlon Carvalho <m.lopes@qiwi.com>, 2015
|
# Marlon Carvalho <m.lopes@qiwi.com>, 2015
|
||||||
# Michael Meneses <michael.douglas.meneses.2@gmail.com>, 2018
|
# Michael Douglas Meneses de Souza <michael.douglas.meneses.2@gmail.com>, 2018
|
||||||
# Michel Wilhelm <michelwilhelm@gmail.com>, 2016
|
# Michel Wilhelm <michelwilhelm@gmail.com>, 2016
|
||||||
# pedromvm <pedromvm@gmail.com>, 2015
|
# pedromvm <pedromvm@gmail.com>, 2015
|
||||||
# Pedro Rangel Raft <me@pedroraft.com>, 2017
|
# Pedro Rangel Raft <me@pedroraft.com>, 2017
|
||||||
|
@ -32,7 +32,8 @@ msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||||
"PO-Revision-Date: 2018-11-17 17:51+0000\n"
|
"PO-Revision-Date: 2018-11-17 17:51+0000\n"
|
||||||
"Last-Translator: Michael Meneses <michael.douglas.meneses.2@gmail.com>\n"
|
"Last-Translator: Michael Douglas Meneses de Souza <michael.douglas."
|
||||||
|
"meneses.2@gmail.com>\n"
|
||||||
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/taiga-agile-llc/"
|
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/taiga-agile-llc/"
|
||||||
"taiga-back/language/pt_BR/)\n"
|
"taiga-back/language/pt_BR/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Translators:
|
# Translators:
|
||||||
|
# Andy zhan <daliangzao189@126.com>, 2018
|
||||||
# Ares <yangzibin.cn@hotmail.com>, 2017
|
# Ares <yangzibin.cn@hotmail.com>, 2017
|
||||||
# gm l <linguangmo@gmail.com>, 2016
|
# gm l <linguangmo@gmail.com>, 2016
|
||||||
# Hanbing Yin <yin_suk@hotmail.com>, 2016
|
# Hanbing Yin <yin_suk@hotmail.com>, 2016
|
||||||
|
@ -25,8 +26,8 @@ msgstr ""
|
||||||
"Project-Id-Version: taiga-back\n"
|
"Project-Id-Version: taiga-back\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
"POT-Creation-Date: 2018-10-11 14:11+0200\n"
|
||||||
"PO-Revision-Date: 2018-10-14 17:29+0000\n"
|
"PO-Revision-Date: 2018-12-16 07:10+0000\n"
|
||||||
"Last-Translator: Alejandro Hermida <alexhermida@gmail.com>\n"
|
"Last-Translator: Andy zhan <daliangzao189@126.com>\n"
|
||||||
"Language-Team: Chinese Simplified (http://www.transifex.com/taiga-agile-llc/"
|
"Language-Team: Chinese Simplified (http://www.transifex.com/taiga-agile-llc/"
|
||||||
"taiga-back/language/zh-Hans/)\n"
|
"taiga-back/language/zh-Hans/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -4649,11 +4650,11 @@ msgstr "加入日期"
|
||||||
|
|
||||||
#: taiga/users/models.py:155
|
#: taiga/users/models.py:155
|
||||||
msgid "accepted terms"
|
msgid "accepted terms"
|
||||||
msgstr ""
|
msgstr "已接受的条款"
|
||||||
|
|
||||||
#: taiga/users/models.py:156
|
#: taiga/users/models.py:156
|
||||||
msgid "new terms read"
|
msgid "new terms read"
|
||||||
msgstr ""
|
msgstr "阅读新条款"
|
||||||
|
|
||||||
#: taiga/users/models.py:158
|
#: taiga/users/models.py:158
|
||||||
msgid "default language"
|
msgid "default language"
|
||||||
|
@ -4894,7 +4895,7 @@ msgstr "无效用户名,请尝试其他的。"
|
||||||
|
|
||||||
#: taiga/users/validators.py:73
|
#: taiga/users/validators.py:73
|
||||||
msgid "Read new terms has to be true'"
|
msgid "Read new terms has to be true'"
|
||||||
msgstr ""
|
msgstr "还没有阅读新条款"
|
||||||
|
|
||||||
#: taiga/userstorage/api.py:53
|
#: taiga/userstorage/api.py:53
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -4939,4 +4940,4 @@ msgstr "持续时间"
|
||||||
|
|
||||||
#: taiga/webhooks/validators.py:42
|
#: taiga/webhooks/validators.py:42
|
||||||
msgid "Not allowed IP Address"
|
msgid "Not allowed IP Address"
|
||||||
msgstr ""
|
msgstr "IP地址被禁用"
|
||||||
|
|
|
@ -25,6 +25,11 @@ def connect_issues_signals():
|
||||||
from taiga.projects.tagging import signals as tagging_handlers
|
from taiga.projects.tagging import signals as tagging_handlers
|
||||||
from . import signals as 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
|
# Finished date
|
||||||
signals.pre_save.connect(handlers.set_finished_date_when_edit_issue,
|
signals.pre_save.connect(handlers.set_finished_date_when_edit_issue,
|
||||||
sender=apps.get_model("issues", "Issue"),
|
sender=apps.get_model("issues", "Issue"),
|
||||||
|
@ -35,6 +40,14 @@ def connect_issues_signals():
|
||||||
sender=apps.get_model("issues", "Issue"),
|
sender=apps.get_model("issues", "Issue"),
|
||||||
dispatch_uid="tags_normalization_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():
|
def connect_issues_custom_attributes_signals():
|
||||||
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
||||||
|
@ -50,11 +63,19 @@ def connect_all_issues_signals():
|
||||||
|
|
||||||
|
|
||||||
def disconnect_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"),
|
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||||
dispatch_uid="set_finished_date_when_edit_issue")
|
dispatch_uid="set_finished_date_when_edit_issue")
|
||||||
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
signals.pre_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||||
dispatch_uid="tags_normalization_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():
|
def disconnect_issues_custom_attributes_signals():
|
||||||
signals.post_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
signals.post_save.disconnect(sender=apps.get_model("issues", "Issue"),
|
||||||
|
|
|
@ -16,9 +16,22 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# 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
|
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
|
# Signals for set finished date
|
||||||
####################################
|
####################################
|
||||||
|
@ -30,3 +43,48 @@ def set_finished_date_when_edit_issue(sender, instance, **kwargs):
|
||||||
instance.finished_date = timezone.now()
|
instance.finished_date = timezone.now()
|
||||||
elif not instance.status.is_closed and instance.finished_date:
|
elif not instance.status.is_closed and instance.finished_date:
|
||||||
instance.finished_date = None
|
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,11 +27,17 @@ from taiga.base.api.mixins import BlockedByProjectMixin
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base.utils.db import get_object_or_none
|
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 WatchedResourceMixin
|
||||||
from taiga.projects.notifications.mixins import WatchersViewSetMixin
|
from taiga.projects.notifications.mixins import WatchersViewSetMixin
|
||||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
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 serializers
|
||||||
|
from . import services
|
||||||
from . import validators
|
from . import validators
|
||||||
from . import models
|
from . import models
|
||||||
from . import permissions
|
from . import permissions
|
||||||
|
@ -142,6 +148,69 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin,
|
||||||
|
|
||||||
return response.Ok(milestone_stats)
|
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):
|
class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
|
||||||
permission_classes = (permissions.MilestoneWatchersPermission,)
|
permission_classes = (permissions.MilestoneWatchersPermission,)
|
||||||
|
|
|
@ -33,6 +33,11 @@ class MilestonePermission(TaigaResourcePermission):
|
||||||
stats_perms = HasProjectPerm('view_milestones')
|
stats_perms = HasProjectPerm('view_milestones')
|
||||||
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
|
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
|
||||||
unwatch_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):
|
class MilestoneWatchersPermission(TaigaResourcePermission):
|
||||||
enought_perms = IsProjectAdmin() | IsSuperUser()
|
enought_perms = IsProjectAdmin() | IsSuperUser()
|
||||||
|
|
|
@ -16,16 +16,29 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.utils import timezone
|
from taiga.base.utils import db
|
||||||
|
from taiga.events import events
|
||||||
from . import models
|
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
|
||||||
|
|
||||||
|
|
||||||
def calculate_milestone_is_closed(milestone):
|
def calculate_milestone_is_closed(milestone):
|
||||||
return (milestone.user_stories.all().count() > 0 and
|
all_us_closed = all([user_story.is_closed for user_story in milestone.user_stories.all()])
|
||||||
all([task.status is not None and task.status.is_closed for task in milestone.tasks.all()]) and
|
all_tasks_closed = all([task.status is not None and task.status.is_closed for task in
|
||||||
all([user_story.is_closed for user_story in milestone.user_stories.all()]))
|
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
|
||||||
|
|
||||||
|
|
||||||
def close_milestone(milestone):
|
def close_milestone(milestone):
|
||||||
|
@ -38,3 +51,137 @@ def open_milestone(milestone):
|
||||||
if milestone.closed:
|
if milestone.closed:
|
||||||
milestone.closed = False
|
milestone.closed = False
|
||||||
milestone.save(update_fields=["closed",])
|
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,15 +19,17 @@
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from taiga.base.exceptions import ValidationError
|
from taiga.base.exceptions import ValidationError
|
||||||
|
from taiga.base.api import serializers
|
||||||
from taiga.base.api import validators
|
from taiga.base.api import validators
|
||||||
from taiga.projects.validators import DuplicatedNameInProjectValidator
|
|
||||||
from taiga.projects.notifications.validators import WatchersValidator
|
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 . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class MilestoneExistsValidator:
|
class MilestoneExistsValidator:
|
||||||
def validate_sprint_id(self, attrs, source):
|
def validate_milestone_id(self, attrs, source):
|
||||||
value = attrs[source]
|
value = attrs[source]
|
||||||
if not models.Milestone.objects.filter(pk=value).exists():
|
if not models.Milestone.objects.filter(pk=value).exists():
|
||||||
msg = _("There's no milestone with that id")
|
msg = _("There's no milestone with that id")
|
||||||
|
@ -39,3 +41,28 @@ class MilestoneValidator(WatchersValidator, DuplicatedNameInProjectValidator, va
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Milestone
|
model = models.Milestone
|
||||||
read_only_fields = ("id", "created_date", "modified_date")
|
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,6 +44,10 @@ from .models import HistoryChangeNotification, Watched
|
||||||
from .squashing import squash_history_entries
|
from .squashing import squash_history_entries
|
||||||
|
|
||||||
|
|
||||||
|
def remove_lr_cr(s):
|
||||||
|
return s.replace("\n", "").replace("\r", "")
|
||||||
|
|
||||||
|
|
||||||
def notify_policy_exists(project, user) -> bool:
|
def notify_policy_exists(project, user) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if policy exists for specified project
|
Check if policy exists for specified project
|
||||||
|
@ -313,10 +317,11 @@ def send_sync_notifications(notification_id):
|
||||||
msg_id = 'taiga-system'
|
msg_id = 'taiga-system'
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
project_name = remove_lr_cr(notification.project.name)
|
||||||
format_args = {
|
format_args = {
|
||||||
"unsubscribe_url": resolve_front_url('settings-mail-notifications'),
|
"unsubscribe_url": resolve_front_url('settings-mail-notifications'),
|
||||||
"project_slug": notification.project.slug,
|
"project_slug": notification.project.slug,
|
||||||
"project_name": notification.project.name,
|
"project_name": project_name,
|
||||||
"msg_id": msg_id,
|
"msg_id": msg_id,
|
||||||
"time": int(now.timestamp()),
|
"time": int(now.timestamp()),
|
||||||
"domain": domain
|
"domain": domain
|
||||||
|
|
|
@ -91,3 +91,7 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, DueDateM
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "({1}) {0}".format(self.ref, self.subject)
|
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)
|
instance.prev = sender.objects.get(id=instance.id)
|
||||||
|
|
||||||
|
|
||||||
####################################
|
######################################
|
||||||
# Signals for close US and Milestone
|
# Signals for close Task and Milestone
|
||||||
####################################
|
######################################
|
||||||
|
|
||||||
def try_to_close_or_open_us_and_milestone_when_create_or_edit_task(sender, instance, created, **kwargs):
|
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)
|
_try_to_close_or_open_us_when_create_or_edit_task(instance)
|
||||||
|
|
|
@ -72,7 +72,6 @@ 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):
|
def try_to_close_or_open_us_and_milestone_when_create_or_edit_us(sender, instance, created, **kwargs):
|
||||||
if instance._importing:
|
if instance._importing:
|
||||||
return
|
return
|
||||||
|
|
||||||
_try_to_close_or_open_us_when_create_or_edit_us(instance)
|
_try_to_close_or_open_us_when_create_or_edit_us(instance)
|
||||||
_try_to_close_or_open_milestone_when_create_or_edit_us(instance)
|
_try_to_close_or_open_milestone_when_create_or_edit_us(instance)
|
||||||
|
|
||||||
|
|
|
@ -626,6 +626,10 @@ def create_issue(**kwargs):
|
||||||
return IssueFactory.create(**defaults)
|
return IssueFactory.create(**defaults)
|
||||||
|
|
||||||
|
|
||||||
|
class Missing:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def create_task(**kwargs):
|
def create_task(**kwargs):
|
||||||
"Create a task and along with its dependencies."
|
"Create a task and along with its dependencies."
|
||||||
owner = kwargs.pop("owner", None)
|
owner = kwargs.pop("owner", None)
|
||||||
|
@ -636,13 +640,23 @@ def create_task(**kwargs):
|
||||||
if project is None:
|
if project is None:
|
||||||
project = ProjectFactory.create(owner=owner)
|
project = ProjectFactory.create(owner=owner)
|
||||||
|
|
||||||
|
status = kwargs.pop("status", None)
|
||||||
|
milestone = kwargs.pop("milestone", None)
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
"project": project,
|
"project": project,
|
||||||
"owner": owner,
|
"owner": owner,
|
||||||
"status": TaskStatusFactory.create(project=project),
|
"status": status or TaskStatusFactory.create(project=project),
|
||||||
"milestone": MilestoneFactory.create(project=project),
|
"milestone": milestone or 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)
|
defaults.update(kwargs)
|
||||||
|
|
||||||
return TaskFactory.create(**defaults)
|
return TaskFactory.create(**defaults)
|
||||||
|
|
|
@ -39,6 +39,78 @@ import pytest
|
||||||
pytestmark = pytest.mark.django_db
|
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():
|
def test_get_issues_from_bulk():
|
||||||
data = """
|
data = """
|
||||||
Issue #1
|
Issue #1
|
||||||
|
@ -370,86 +442,63 @@ def test_api_filter_by_finished_date(client):
|
||||||
assert response.data[0]["ref"] == finished_issue.ref
|
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):
|
def test_api_filters_data(client):
|
||||||
project = f.ProjectFactory.create()
|
data = create_filter_issues_context()
|
||||||
user1 = f.UserFactory.create(is_superuser=True)
|
project = data["project"]
|
||||||
f.MembershipFactory.create(user=user1, project=project)
|
(user1, user2, user3, ) = data["users"]
|
||||||
user2 = f.UserFactory.create(is_superuser=True)
|
(status0, status1, status2, status3, ) = data["statuses"]
|
||||||
f.MembershipFactory.create(user=user2, project=project)
|
(type1, type2, ) = data["types"]
|
||||||
user3 = f.UserFactory.create(is_superuser=True)
|
(priority0, priority1, priority2, priority3, ) = data["priorities"]
|
||||||
f.MembershipFactory.create(user=user3, project=project)
|
(severity0, severity1, severity2, severity3, ) = data["severities"]
|
||||||
|
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||||
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)
|
url = reverse("issues-filters-data") + "?project={}".format(project.id)
|
||||||
|
|
||||||
client.login(user1)
|
client.login(user1)
|
||||||
|
|
||||||
## No filter
|
## No filter
|
||||||
|
|
|
@ -26,7 +26,6 @@ from urllib.parse import quote
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.projects.userstories.serializers import UserStorySerializer
|
|
||||||
|
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
|
@ -180,3 +179,211 @@ def test_api_filter_by_milestone__estimated_start_and_end(client, field_name):
|
||||||
assert number_of_milestones == expection, param
|
assert number_of_milestones == expection, param
|
||||||
if number_of_milestones > 0:
|
if number_of_milestones > 0:
|
||||||
assert response.data[0]["slug"] == milestone.slug
|
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,6 +55,20 @@ def mail():
|
||||||
return 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():
|
def test_create_retrieve_notify_policy():
|
||||||
project = f.ProjectFactory.create()
|
project = f.ProjectFactory.create()
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,62 @@ import pytest
|
||||||
pytestmark = pytest.mark.django_db
|
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():
|
def test_get_tasks_from_bulk():
|
||||||
data = """
|
data = """
|
||||||
Task #1
|
Task #1
|
||||||
|
@ -796,63 +852,45 @@ def test_api_filter_by_milestone__estimated_start_and_end(client, field_name):
|
||||||
assert response.data[0]["subject"] == task.subject
|
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):
|
def test_api_filters_data(client):
|
||||||
project = f.ProjectFactory.create()
|
data = create_tasks_fixtures()
|
||||||
user1 = f.UserFactory.create(is_superuser=True)
|
project = data["project"]
|
||||||
f.MembershipFactory.create(user=user1, project=project)
|
(user1, user2, user3, ) = data["users"]
|
||||||
user2 = f.UserFactory.create(is_superuser=True)
|
(status0, status1, status2, status3, ) = data["statuses"]
|
||||||
f.MembershipFactory.create(user=user2, project=project)
|
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||||
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)
|
url = reverse("tasks-filters-data") + "?project={}".format(project.id)
|
||||||
|
|
||||||
client.login(user1)
|
client.login(user1)
|
||||||
|
|
||||||
## No filter
|
## No filter
|
||||||
|
|
|
@ -38,6 +38,71 @@ import pytest
|
||||||
pytestmark = pytest.mark.django_db(transaction=True)
|
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():
|
def test_get_userstories_from_bulk():
|
||||||
data = "User Story #1\nUser Story #2\n"
|
data = "User Story #1\nUser Story #2\n"
|
||||||
userstories = services.get_userstories_from_bulk(data)
|
userstories = services.get_userstories_from_bulk(data)
|
||||||
|
@ -777,72 +842,14 @@ def test_api_filter_by_milestone__estimated_start_and_end(client, field_name):
|
||||||
|
|
||||||
|
|
||||||
def test_api_filters_data(client):
|
def test_api_filters_data(client):
|
||||||
project = f.ProjectFactory.create()
|
data = create_uss_fixtures()
|
||||||
user1 = f.UserFactory.create(is_superuser=True)
|
project = data["project"]
|
||||||
f.MembershipFactory.create(user=user1, project=project)
|
(user1, user2, user3, ) = data["users"]
|
||||||
user2 = f.UserFactory.create(is_superuser=True)
|
(status0, status1, status2, status3, ) = data["statuses"]
|
||||||
f.MembershipFactory.create(user=user2, project=project)
|
(epic0, epic1, epic2, ) = data["epics"]
|
||||||
user3 = f.UserFactory.create(is_superuser=True)
|
(tag0, tag1, tag2, tag3, ) = data["tags"]
|
||||||
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)
|
url = reverse("userstories-filters-data") + "?project={}".format(project.id)
|
||||||
|
|
||||||
client.login(user1)
|
client.login(user1)
|
||||||
|
|
||||||
# No filter
|
# No filter
|
||||||
|
@ -961,6 +968,37 @@ def test_api_filters_data(client):
|
||||||
assert next(filter(lambda i: i['id'] == epic2.id, response.data["epics"]))["count"] == 2
|
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):
|
def test_api_filters_data_with_assigned_users(client):
|
||||||
project = f.ProjectFactory.create()
|
project = f.ProjectFactory.create()
|
||||||
user1 = f.UserFactory.create(is_superuser=True)
|
user1 = f.UserFactory.create(is_superuser=True)
|
||||||
|
|
|
@ -0,0 +1,268 @@
|
||||||
|
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