diff --git a/taiga/projects/custom_attributes/api.py b/taiga/projects/custom_attributes/api.py
index 43f8a2f5..7e4033ab 100644
--- a/taiga/projects/custom_attributes/api.py
+++ b/taiga/projects/custom_attributes/api.py
@@ -14,9 +14,17 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from django.utils.translation import ugettext_lazy as _
+
from taiga.base.api import ModelCrudViewSet
+from taiga.base.api.viewsets import ModelViewSet
+from taiga.base import exceptions as exc
from taiga.base import filters
+from taiga.base import response
+
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
+from taiga.projects.history.mixins import HistoryResourceMixin
+from taiga.projects.occ.mixins import OCCResourceMixin
from . import models
from . import serializers
@@ -24,6 +32,10 @@ from . import permissions
from . import services
+######################################################
+# Custom Attribute ViewSets
+#######################################################
+
class UserStoryCustomAttributeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.UserStoryCustomAttribute
serializer_class = serializers.UserStoryCustomAttributeSerializer
@@ -55,3 +67,45 @@ class IssueCustomAttributeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
bulk_update_param = "bulk_issue_custom_attributes"
bulk_update_perm = "change_issue_custom_attributes"
bulk_update_order_action = services.bulk_update_issue_custom_attribute_order
+
+
+######################################################
+# Custom Attributes Values ViewSets
+#######################################################
+
+class BaseCustomAttributesValuesViewSet(OCCResourceMixin, HistoryResourceMixin, ModelViewSet):
+ def list(self, request, *args, **kwargs):
+ return response.NotFound()
+
+ def post_delete(self, obj):
+ # NOTE: When destroy a custom attributes values object, the
+ # content_object change after and not before
+ self.persist_history_snapshot(obj, delete=True)
+ super().pre_delete(obj)
+
+ def get_object_for_snapshot(self, obj):
+ return getattr(obj, self.content_object)
+
+
+class UserStoryCustomAttributesValuesViewSet(BaseCustomAttributesValuesViewSet):
+ model = models.UserStoryCustomAttributesValues
+ serializer_class = serializers.UserStoryCustomAttributesValuesSerializer
+ permission_classes = (permissions.UserStoryCustomAttributesValuesPermission,)
+ lookup_field = "user_story_id"
+ content_object = "user_story"
+
+
+class TaskCustomAttributesValuesViewSet(BaseCustomAttributesValuesViewSet):
+ model = models.TaskCustomAttributesValues
+ serializer_class = serializers.TaskCustomAttributesValuesSerializer
+ permission_classes = (permissions.TaskCustomAttributesValuesPermission,)
+ lockup_fields = "task_id"
+ content_object = "task"
+
+
+class IssueCustomAttributesValuesViewSet(BaseCustomAttributesValuesViewSet):
+ model = models.IssueCustomAttributesValues
+ serializer_class = serializers.IssueCustomAttributesValuesSerializer
+ permission_classes = (permissions.IssueCustomAttributesValuesPermission,)
+ lockup_fields = "issue_id"
+ content_object = "issue"
diff --git a/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py b/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py
new file mode 100644
index 00000000..4c7d2461
--- /dev/null
+++ b/taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django_pgjson.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0005_auto_20150114_0954'),
+ ('userstories', '0009_remove_userstory_is_archived'),
+ ('issues', '0004_auto_20150114_0954'),
+ ('custom_attributes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='IssueCustomAttributesValues',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('version', models.IntegerField(default=1, verbose_name='version')),
+ ('values', django_pgjson.fields.JsonField(default={}, verbose_name='values')),
+ ('issue', models.OneToOneField(related_name='custom_attributes_values', to='issues.Issue', verbose_name='issue')),
+ ],
+ options={
+ 'ordering': ['id'],
+ 'verbose_name_plural': 'issue custom attributes values',
+ 'verbose_name': 'issue ustom attributes values',
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='TaskCustomAttributesValues',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('version', models.IntegerField(default=1, verbose_name='version')),
+ ('values', django_pgjson.fields.JsonField(default={}, verbose_name='values')),
+ ('task', models.OneToOneField(related_name='custom_attributes_values', to='tasks.Task', verbose_name='task')),
+ ],
+ options={
+ 'ordering': ['id'],
+ 'verbose_name_plural': 'task custom attributes values',
+ 'verbose_name': 'task ustom attributes values',
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='UserStoryCustomAttributesValues',
+ fields=[
+ ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
+ ('version', models.IntegerField(default=1, verbose_name='version')),
+ ('values', django_pgjson.fields.JsonField(default={}, verbose_name='values')),
+ ('user_story', models.OneToOneField(related_name='custom_attributes_values', to='userstories.UserStory', verbose_name='user story')),
+ ],
+ options={
+ 'ordering': ['id'],
+ 'verbose_name_plural': 'user story custom attributes values',
+ 'verbose_name': 'user story ustom attributes values',
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/taiga/projects/custom_attributes/models.py b/taiga/projects/custom_attributes/models.py
index 5efbea47..287498d4 100644
--- a/taiga/projects/custom_attributes/models.py
+++ b/taiga/projects/custom_attributes/models.py
@@ -18,9 +18,13 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
+from django_pgjson.fields import JsonField
+
+from taiga.projects.occ.mixins import OCCModelMixin
+
######################################################
-# Base Model Class
+# Custom Attribute Models
#######################################################
class AbstractCustomAttribute(models.Model):
@@ -51,11 +55,6 @@ class AbstractCustomAttribute(models.Model):
return super().save(*args, **kwargs)
-
-######################################################
-# Custom Field Models
-#######################################################
-
class UserStoryCustomAttribute(AbstractCustomAttribute):
class Meta(AbstractCustomAttribute.Meta):
verbose_name = "user story custom attribute"
@@ -72,3 +71,60 @@ class IssueCustomAttribute(AbstractCustomAttribute):
class Meta(AbstractCustomAttribute.Meta):
verbose_name = "issue custom attribute"
verbose_name_plural = "issue custom attributes"
+
+
+######################################################
+# Custom Attributes Values Models
+#######################################################
+
+class AbstractCustomAttributesValues(OCCModelMixin, models.Model):
+ values = JsonField(null=False, blank=False, default={}, verbose_name=_("values"))
+
+ class Meta:
+ abstract = True
+ ordering = ["id"]
+
+
+class UserStoryCustomAttributesValues(AbstractCustomAttributesValues):
+ user_story = models.OneToOneField("userstories.UserStory",
+ null=False, blank=False, related_name="custom_attributes_values",
+ verbose_name=_("user story"))
+
+ class Meta(AbstractCustomAttributesValues.Meta):
+ verbose_name = "user story ustom attributes values"
+ verbose_name_plural = "user story custom attributes values"
+
+ @property
+ def project(self):
+ # NOTE: This property simplifies checking permissions
+ return self.user_story.project
+
+
+class TaskCustomAttributesValues(AbstractCustomAttributesValues):
+ task = models.OneToOneField("tasks.Task",
+ null=False, blank=False, related_name="custom_attributes_values",
+ verbose_name=_("task"))
+
+ class Meta(AbstractCustomAttributesValues.Meta):
+ verbose_name = "task ustom attributes values"
+ verbose_name_plural = "task custom attributes values"
+
+ @property
+ def project(self):
+ # NOTE: This property simplifies checking permissions
+ return self.task.project
+
+
+class IssueCustomAttributesValues(AbstractCustomAttributesValues):
+ issue = models.OneToOneField("issues.Issue",
+ null=False, blank=False, related_name="custom_attributes_values",
+ verbose_name=_("issue"))
+
+ class Meta(AbstractCustomAttributesValues.Meta):
+ verbose_name = "issue ustom attributes values"
+ verbose_name_plural = "issue custom attributes values"
+
+ @property
+ def project(self):
+ # NOTE: This property simplifies checking permissions
+ return self.issue.project
diff --git a/taiga/projects/custom_attributes/permissions.py b/taiga/projects/custom_attributes/permissions.py
index 3f82f38e..617e90b0 100644
--- a/taiga/projects/custom_attributes/permissions.py
+++ b/taiga/projects/custom_attributes/permissions.py
@@ -18,9 +18,16 @@ from taiga.base.api.permissions import TaigaResourcePermission
from taiga.base.api.permissions import HasProjectPerm
from taiga.base.api.permissions import IsProjectOwner
from taiga.base.api.permissions import AllowAny
+from taiga.base.api.permissions import IsSuperUser
+######################################################
+# Custom Attribute Permissions
+#######################################################
+
class UserStoryCustomAttributePermission(TaigaResourcePermission):
+ enought_perms = IsProjectOwner() | IsSuperUser()
+ global_perms = None
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner()
update_perms = IsProjectOwner()
@@ -30,6 +37,8 @@ class UserStoryCustomAttributePermission(TaigaResourcePermission):
class TaskCustomAttributePermission(TaigaResourcePermission):
+ enought_perms = IsProjectOwner() | IsSuperUser()
+ global_perms = None
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner()
update_perms = IsProjectOwner()
@@ -39,9 +48,42 @@ class TaskCustomAttributePermission(TaigaResourcePermission):
class IssueCustomAttributePermission(TaigaResourcePermission):
+ enought_perms = IsProjectOwner() | IsSuperUser()
+ global_perms = None
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner()
update_perms = IsProjectOwner()
destroy_perms = IsProjectOwner()
list_perms = AllowAny()
bulk_update_order_perms = IsProjectOwner()
+
+
+######################################################
+# Custom Attributes Values Permissions
+#######################################################
+
+class UserStoryCustomAttributesValuesPermission(TaigaResourcePermission):
+ enought_perms = IsProjectOwner() | IsSuperUser()
+ global_perms = None
+ retrieve_perms = HasProjectPerm('view_us')
+ create_perms = HasProjectPerm('add_us')
+ update_perms = HasProjectPerm('modify_us')
+ destroy_perms = HasProjectPerm('delete_us')
+
+
+class TaskCustomAttributesValuesPermission(TaigaResourcePermission):
+ enought_perms = IsProjectOwner() | IsSuperUser()
+ global_perms = None
+ retrieve_perms = HasProjectPerm('view_tasks')
+ create_perms = HasProjectPerm('add_task')
+ update_perms = HasProjectPerm('modify_task')
+ destroy_perms = HasProjectPerm('delete_task')
+
+
+class IssueCustomAttributesValuesPermission(TaigaResourcePermission):
+ enought_perms = IsProjectOwner() | IsSuperUser()
+ global_perms = None
+ retrieve_perms = HasProjectPerm('view_issues')
+ create_perms = HasProjectPerm('add_issue')
+ update_perms = HasProjectPerm('modify_issue')
+ destroy_perms = HasProjectPerm('delete_issue')
diff --git a/taiga/projects/custom_attributes/serializers.py b/taiga/projects/custom_attributes/serializers.py
index 025607f8..40a25ccc 100644
--- a/taiga/projects/custom_attributes/serializers.py
+++ b/taiga/projects/custom_attributes/serializers.py
@@ -14,20 +14,26 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+
+from django.apps import apps
from django.utils.translation import ugettext_lazy as _
from rest_framework.serializers import ValidationError
from taiga.base.serializers import ModelSerializer
+from taiga.base.serializers import JsonField
from . import models
######################################################
-# Base Serializer Class
+# Custom Attribute Serializer
#######################################################
class BaseCustomAttributeSerializer(ModelSerializer):
+ class Meta:
+ read_only_fields = ('id', 'created_date', 'modified_date')
+
def validate(self, data):
"""
Check the name is not duplicated in the project. Check when:
@@ -49,20 +55,78 @@ class BaseCustomAttributeSerializer(ModelSerializer):
return data
-######################################################
-# Custom Field Serializers
-#######################################################
-
class UserStoryCustomAttributeSerializer(BaseCustomAttributeSerializer):
- class Meta:
+ class Meta(BaseCustomAttributeSerializer.Meta):
model = models.UserStoryCustomAttribute
class TaskCustomAttributeSerializer(BaseCustomAttributeSerializer):
- class Meta:
+ class Meta(BaseCustomAttributeSerializer.Meta):
model = models.TaskCustomAttribute
class IssueCustomAttributeSerializer(BaseCustomAttributeSerializer):
- class Meta:
+ class Meta(BaseCustomAttributeSerializer.Meta):
model = models.IssueCustomAttribute
+
+
+######################################################
+# Custom Attribute Serializer
+#######################################################
+
+
+class BaseCustomAttributesValuesSerializer:
+ values = JsonField(source="values", label="values", required=True)
+ _custom_attribute_model = None
+ _container_field = None
+
+ def validate_values(self, attrs, source):
+ # values must be a dict
+ data_values = attrs.get("values", None)
+ if self.object:
+ data_values = (data_values or self.object.values)
+
+ if type(data_values) is not dict:
+ raise ValidationError(_("Invalid content. It must be {\"key\": \"value\",...}"))
+
+ # Values keys must be in the container object project
+ data_container = attrs.get(self._container_field, None)
+ if data_container:
+ project_id = data_container.project_id
+ elif self.object:
+ project_id = getattr(self.object, self._container_field).project_id
+ else:
+ project_id = None
+
+ values_ids = list(data_values.keys())
+ qs = self._custom_attribute_model.objects.filter(project=project_id,
+ id__in=values_ids)
+ if qs.count() != len(values_ids):
+ raise ValidationError(_("It contain invalid custom fields."))
+
+ return attrs
+
+
+class UserStoryCustomAttributesValuesSerializer(BaseCustomAttributesValuesSerializer, ModelSerializer):
+ _custom_attribute_model = models.UserStoryCustomAttribute
+ _container_model = "userstories.UserStory"
+ _container_field = "user_story"
+
+ class Meta:
+ model = models.UserStoryCustomAttributesValues
+
+
+class TaskCustomAttributesValuesSerializer(BaseCustomAttributesValuesSerializer, ModelSerializer):
+ _custom_attribute_model = models.TaskCustomAttribute
+ _container_field = "task"
+
+ class Meta:
+ model = models.TaskCustomAttributesValues
+
+
+class IssueCustomAttributesValuesSerializer(BaseCustomAttributesValuesSerializer, ModelSerializer):
+ _custom_attribute_model = models.IssueCustomAttribute
+ _container_field = "issue"
+
+ class Meta:
+ model = models.IssueCustomAttributesValues
diff --git a/taiga/projects/history/freeze_impl.py b/taiga/projects/history/freeze_impl.py
index c3bc3a4f..1e67df09 100644
--- a/taiga/projects/history/freeze_impl.py
+++ b/taiga/projects/history/freeze_impl.py
@@ -14,9 +14,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from contextlib import suppress
+
from functools import partial
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ObjectDoesNotExist
+
from taiga.base.utils.iterators import as_tuple
from taiga.base.utils.iterators import as_dict
from taiga.mdrender.service import render as mdrender
@@ -181,6 +185,42 @@ def extract_attachments(obj) -> list:
"order": attach.order}
+@as_tuple
+def extract_user_story_custom_attributes(obj) -> list:
+ with suppress(ObjectDoesNotExist):
+ custom_attributes_values = obj.custom_attributes_values.values
+ for attr in obj.project.userstorycustomattributes.all():
+ with suppress(KeyError):
+ value = custom_attributes_values[str(attr.id)]
+ yield {"id": attr.id,
+ "name": attr.name,
+ "values": value}
+
+
+@as_tuple
+def extract_task_custom_attributes(obj) -> list:
+ with suppress(ObjectDoesNotExist):
+ custom_attributes_values = obj.custom_attributes_values.values
+ for attr in obj.project.taskcustomattributes.all():
+ with suppress(KeyError):
+ value = custom_attributes_values[str(attr.id)]
+ yield {"id": attr.id,
+ "name": attr.name,
+ "values": value}
+
+
+@as_tuple
+def extract_issue_custom_attributes(obj) -> list:
+ with suppress(ObjectDoesNotExist):
+ custom_attributes_values = obj.custom_attributes_values.values
+ for attr in obj.project.issuecustomattributes.all():
+ with suppress(KeyError):
+ value = custom_attributes_values[str(attr.id)]
+ yield {"id": attr.id,
+ "name": attr.name,
+ "values": value}
+
+
def project_freezer(project) -> dict:
fields = ("name",
"slug",
@@ -243,6 +283,7 @@ def userstory_freezer(us) -> dict:
"is_blocked": us.is_blocked,
"blocked_note": us.blocked_note,
"blocked_note_html": mdrender(us.project, us.blocked_note),
+ "custom_attributes": extract_user_story_custom_attributes(us),
}
return snapshot
@@ -267,6 +308,7 @@ def issue_freezer(issue) -> dict:
"is_blocked": issue.is_blocked,
"blocked_note": issue.blocked_note,
"blocked_note_html": mdrender(issue.project, issue.blocked_note),
+ "custom_attributes": extract_issue_custom_attributes(issue),
}
return snapshot
@@ -292,6 +334,7 @@ def task_freezer(task) -> dict:
"is_blocked": task.is_blocked,
"blocked_note": task.blocked_note,
"blocked_note_html": mdrender(task.project, task.blocked_note),
+ "custom_attributes": extract_task_custom_attributes(task),
}
return snapshot
diff --git a/taiga/projects/history/models.py b/taiga/projects/history/models.py
index 462e3d45..769ec789 100644
--- a/taiga/projects/history/models.py
+++ b/taiga/projects/history/models.py
@@ -197,6 +197,35 @@ class HistoryEntry(models.Model):
if attachments["new"] or attachments["changed"] or attachments["deleted"]:
value = attachments
+ elif key == "custom_attributes":
+ custom_attributes = {
+ "new": [],
+ "changed": [],
+ "deleted": [],
+ }
+
+ oldcustattrs = {x["id"]:x for x in self.diff["custom_attributes"][0]}
+ newcustattrs = {x["id"]:x for x in self.diff["custom_attributes"][1]}
+
+ for aid in set(tuple(oldcustattrs.keys()) + tuple(newcustattrs.keys())):
+ if aid in oldcustattrs and aid in newcustattrs:
+ changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid],
+ excluded_keys=("name"))
+
+ if changes:
+ change = {
+ "name": newcustattrs.get(aid, {}).get("name", ""),
+ "changes": changes
+ }
+ custom_attributes["changed"].append(change)
+ elif aid in oldcustattrs and aid not in newcustattrs:
+ custom_attributes["deleted"].append(oldcustattrs[aid])
+ elif aid not in oldcustattrs and aid in newcustattrs:
+ custom_attributes["new"].append(newcustattrs[aid])
+
+ if custom_attributes["new"] or custom_attributes["changed"] or custom_attributes["deleted"]:
+ value = custom_attributes
+
elif key in self.values:
value = [resolve_value(key, x) for x in self.diff[key]]
else:
diff --git a/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja b/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja
index 4b537805..84a2d462 100644
--- a/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja
+++ b/taiga/projects/history/templates/emails/includes/fields_diff-html.jinja
@@ -6,7 +6,8 @@
"backlog_order",
"kanban_order",
"taskboard_order",
- "us_order"
+ "us_order",
+ "custom_attributes"
] %}
{% for field_name, values in changed_fields.items() %}
@@ -80,9 +81,7 @@
{{ _("Deleted attachment") }}
- {% if att.changes.description %}
{{ att.filename|linebreaksbr }}
- {% endif %}
|
{% endfor %}
@@ -155,7 +154,6 @@
{# * #}
{% else %}
-
{{ verbose_name(obj_class, field_name) }}
@@ -172,5 +170,52 @@
|
{% endif %}
+
+ {% elif field_name == "custom_attributes" %}
+ {# CUSTOM ATTRIBUTES #}
+ {% if values.new %}
+ {% for attr in values['new']%}
+
+
+ {{ attr.name }}
+ |
+
+
+
+ {{ _("to") }}
+ {{ attr.value|linebreaksbr }}
+ |
+
+ {% endfor %}
+ {% endif %}
+ {% if values.changed %}
+ {% for attr in values['changed'] %}
+
+
+ {{ attr.name }}
+ |
+
+ {{ _("from") }}
+ {{ attr.changes.value.0|linebreaksbr }}
+ |
+
+
+
+ {{ _("to") }}
+ {{ attr.changes.value.1|linebreaksbr }}
+ |
+
+ {% endfor %}
+ {% endif %}
+ {% if values.deleted %}
+ {% for attr in values['deleted']%}
+
+
+ {{ attr.name }}
+ {{ _("-deleted-") }}
+ |
+
+ {% endfor %}
+ {% endif %}
{% endif %}
{% endfor %}
diff --git a/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja b/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja
index 206e237c..5ecbf496 100644
--- a/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja
+++ b/taiga/projects/history/templates/emails/includes/fields_diff-text.jinja
@@ -8,7 +8,8 @@
"taskboard_order",
"us_order",
"blocked_note_diff",
- "blocked_note_html"
+ "blocked_note_html",
+ "custom_attributes"
] %}
{% for field_name, values in changed_fields.items() %}
{% if field_name not in excluded_fields %}
@@ -18,6 +19,7 @@
{% for role, points in values.items() %}
* {{ role }} {{ _("to:") }} {{ points.1 }} {{ _("from:") }} {{ points.0 }}
{% endfor %}
+
{# ATTACHMENTS #}
{% elif field_name == "attachments" %}
{% if values.new %}
@@ -40,6 +42,7 @@
- {{ att.filename }}
{% endfor %}
{% endif %}
+
{# TAGS AND WATCHERS #}
{% elif field_name in ["tags", "watchers"] %}
{% set values_from = values.0 or [] %}
@@ -53,6 +56,36 @@
{% if values_removed %}
* {{ _("removed:") }} {{ ', '.join(values_removed) }}
{% endif %}
+
+ {# * #}
+ {% else %}
+ * {{ _("From:") }} {{ values.0 }}
+ * {{ _("To:") }} {{ values.1 }}
{% endif %}
+
+ {% elif field_name == "custom_attributes" %}
+ {# CUSTOM ATTRIBUTES #}
+ {% elif field_name == "attachments" %}
+ {% if values.new %}
+ {% for attr in values['new']%}
+ - {{ attr.name }}:
+ * {{ attr.value }}
+ {% endfor %}
+ {% endif %}
+
+ {% if values.changed %}
+ {% for attr in values['changed'] %}
+ - {{ attr.name }}:
+ * {{ _("From:") }} {{ attr.changes.value.0 }}
+ * {{ _("To:") }} {{ attr.changes.value.1 }}
+ {% endfor %}
+ {% endif %}
+
+ {% if values.deleted %}
+ {% for attr in values['deleted']%}
+ - {{ attr.name }}: {{ _("-deleted-") }}
+ * {{ attr.value }}
+ {% endfor %}
+ {% endif %}
{% endif %}
{% endfor %}
diff --git a/taiga/projects/issues/serializers.py b/taiga/projects/issues/serializers.py
index 711cefd9..a951c369 100644
--- a/taiga/projects/issues/serializers.py
+++ b/taiga/projects/issues/serializers.py
@@ -16,7 +16,7 @@
from rest_framework import serializers
-from taiga.base.serializers import (Serializer, TagsField, NeighborsSerializerMixin,
+from taiga.base.serializers import (Serializer, TagsField, NeighborsSerializerMixin,
PgArrayField, ModelSerializer)
from taiga.mdrender.service import render as mdrender
diff --git a/taiga/projects/tasks/serializers.py b/taiga/projects/tasks/serializers.py
index 9e2d2f4a..7ee80fa3 100644
--- a/taiga/projects/tasks/serializers.py
+++ b/taiga/projects/tasks/serializers.py
@@ -18,7 +18,7 @@ from rest_framework import serializers
from taiga.base.serializers import (Serializer, TagsField, NeighborsSerializerMixin,
PgArrayField, ModelSerializer)
-
+
from taiga.mdrender.service import render as mdrender
from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.milestones.validators import SprintExistsValidator
diff --git a/taiga/projects/userstories/models.py b/taiga/projects/userstories/models.py
index 52772ca6..c0abc9b4 100644
--- a/taiga/projects/userstories/models.py
+++ b/taiga/projects/userstories/models.py
@@ -22,10 +22,10 @@ from django.utils import timezone
from djorm_pgarray.fields import TextArrayField
+from taiga.base.tags import TaggedMixin
from taiga.projects.occ import OCCModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin
-from taiga.base.tags import TaggedMixin
class RolePoints(models.Model):
diff --git a/taiga/projects/userstories/serializers.py b/taiga/projects/userstories/serializers.py
index f90970f3..ae81cf20 100644
--- a/taiga/projects/userstories/serializers.py
+++ b/taiga/projects/userstories/serializers.py
@@ -14,15 +14,19 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-import json
from django.apps import apps
from rest_framework import serializers
-from taiga.base.serializers import (Serializer, TagsField, NeighborsSerializerMixin,
- PgArrayField, ModelSerializer)
+from taiga.base.serializers import Serializer
+from taiga.base.serializers import TagsField
+from taiga.base.serializers import NeighborsSerializerMixin
+from taiga.base.serializers import PgArrayField
+from taiga.base.serializers import ModelSerializer
+from taiga.base.utils import json
from taiga.mdrender.service import render as mdrender
-from taiga.projects.validators import ProjectExistsValidator, UserStoryStatusExistsValidator
+from taiga.projects.validators import ProjectExistsValidator
+from taiga.projects.validators import UserStoryStatusExistsValidator
from taiga.projects.userstories.validators import UserStoryExistsValidator
from taiga.projects.notifications.validators import WatchersValidator
@@ -92,7 +96,6 @@ class UserStorySerializer(WatchersValidator, ModelSerializer):
class UserStoryNeighborsSerializer(NeighborsSerializerMixin, UserStorySerializer):
-
def serialize_neighbor(self, neighbor):
return NeighborUserStorySerializer(neighbor).data
@@ -104,8 +107,7 @@ class NeighborUserStorySerializer(ModelSerializer):
depth = 0
-class UserStoriesBulkSerializer(ProjectExistsValidator, UserStoryStatusExistsValidator,
- Serializer):
+class UserStoriesBulkSerializer(ProjectExistsValidator, UserStoryStatusExistsValidator, Serializer):
project_id = serializers.IntegerField()
status_id = serializers.IntegerField(required=False)
bulk_stories = serializers.CharField()
@@ -118,8 +120,6 @@ class _UserStoryOrderBulkSerializer(UserStoryExistsValidator, Serializer):
order = serializers.IntegerField()
-class UpdateUserStoriesOrderBulkSerializer(ProjectExistsValidator,
- UserStoryStatusExistsValidator,
- Serializer):
+class UpdateUserStoriesOrderBulkSerializer(ProjectExistsValidator, UserStoryStatusExistsValidator, Serializer):
project_id = serializers.IntegerField()
bulk_stories = _UserStoryOrderBulkSerializer(many=True)
diff --git a/taiga/routers.py b/taiga/routers.py
index 86b1ba5e..8a09b80e 100644
--- a/taiga/routers.py
+++ b/taiga/routers.py
@@ -71,6 +71,9 @@ router.register(r"severities",SeverityViewSet , base_name="severities")
from taiga.projects.custom_attributes.api import UserStoryCustomAttributeViewSet
from taiga.projects.custom_attributes.api import TaskCustomAttributeViewSet
from taiga.projects.custom_attributes.api import IssueCustomAttributeViewSet
+from taiga.projects.custom_attributes.api import UserStoryCustomAttributesValuesViewSet
+from taiga.projects.custom_attributes.api import TaskCustomAttributesValuesViewSet
+from taiga.projects.custom_attributes.api import IssueCustomAttributesValuesViewSet
router.register(r"userstory-custom-attributes", UserStoryCustomAttributeViewSet,
base_name="userstory-custom-attributes")
@@ -79,6 +82,13 @@ router.register(r"task-custom-attributes", TaskCustomAttributeViewSet,
router.register(r"issue-custom-attributes", IssueCustomAttributeViewSet,
base_name="issue-custom-attributes")
+router.register(r"userstories/custom-attributes-values", UserStoryCustomAttributesValuesViewSet,
+ base_name="userstory-custom-attributes-values")
+router.register(r"tasks/custom-attributes-values", TaskCustomAttributesValuesViewSet,
+ base_name="task-custom-attributes-values")
+router.register(r"issues/custom-attributes-values", IssueCustomAttributesValuesViewSet,
+ base_name="issue-custom-attributes-values")
+
# Search
from taiga.searches.api import SearchViewSet
diff --git a/tests/factories.py b/tests/factories.py
index 0b555deb..e8f70e7d 100644
--- a/tests/factories.py
+++ b/tests/factories.py
@@ -384,6 +384,33 @@ class IssueCustomAttributeFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory")
+class UserStoryCustomAttributesValuesFactory(Factory):
+ class Meta:
+ model = "custom_attributes.UserStoryCustomAttributesValues"
+ strategy = factory.CREATE_STRATEGY
+
+ values = {}
+ user_story = factory.SubFactory("tests.factories.UserStoryFactory")
+
+
+class TaskCustomAttributesValuesFactory(Factory):
+ class Meta:
+ model = "custom_attributes.TaskCustomAttributesValues"
+ strategy = factory.CREATE_STRATEGY
+
+ values = {}
+ task = factory.SubFactory("tests.factories.TaskFactory")
+
+
+class IssueCustomAttributesValuesFactory(Factory):
+ class Meta:
+ model = "custom_attributes.IssueCustomAttributesValues"
+ strategy = factory.CREATE_STRATEGY
+
+ values = {}
+ issue = factory.SubFactory("tests.factories.IssueFactory")
+
+
# class FanFactory(Factory):
# project = factory.SubFactory("tests.factories.ProjectFactory")
# user = factory.SubFactory("tests.factories.UserFactory")
diff --git a/tests/integration/resources_permissions/test_custom_attributes_resource.py b/tests/integration/resources_permissions/test_custom_attributes_resource.py
deleted file mode 100644
index 8b84411f..00000000
--- a/tests/integration/resources_permissions/test_custom_attributes_resource.py
+++ /dev/null
@@ -1,586 +0,0 @@
-# Copyright (C) 2015 Andrey Antukh
-# Copyright (C) 2015 Jesús Espino
-# Copyright (C) 2015 David Barragán
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-
-from django.core.urlresolvers import reverse
-
-from taiga.base.utils import json
-from taiga.projects.custom_attributes import serializers
-from taiga.permissions.permissions import MEMBERS_PERMISSIONS
-
-from tests import factories as f
-from tests.utils import helper_test_http_method
-
-import pytest
-pytestmark = pytest.mark.django_db
-
-
-@pytest.fixture
-def data():
- m = type("Models", (object,), {})
- m.registered_user = f.UserFactory.create()
- m.project_member_with_perms = f.UserFactory.create()
- m.project_member_without_perms = f.UserFactory.create()
- m.project_owner = f.UserFactory.create()
- m.other_user = f.UserFactory.create()
- m.superuser = f.UserFactory.create(is_superuser=True)
-
- m.public_project = f.ProjectFactory(is_private=False,
- anon_permissions=['view_project'],
- public_permissions=['view_project'],
- owner=m.project_owner)
- m.private_project1 = f.ProjectFactory(is_private=True,
- anon_permissions=['view_project'],
- public_permissions=['view_project'],
- owner=m.project_owner)
- m.private_project2 = f.ProjectFactory(is_private=True,
- anon_permissions=[],
- public_permissions=[],
- owner=m.project_owner)
-
- m.public_membership = f.MembershipFactory(project=m.public_project,
- user=m.project_member_with_perms,
- email=m.project_member_with_perms.email,
- role__project=m.public_project,
- role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
- m.private_membership1 = f.MembershipFactory(project=m.private_project1,
- user=m.project_member_with_perms,
- email=m.project_member_with_perms.email,
- role__project=m.private_project1,
- role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
-
- f.MembershipFactory(project=m.private_project1,
- user=m.project_member_without_perms,
- email=m.project_member_without_perms.email,
- role__project=m.private_project1,
- role__permissions=[])
-
- m.private_membership2 = f.MembershipFactory(project=m.private_project2,
- user=m.project_member_with_perms,
- email=m.project_member_with_perms.email,
- role__project=m.private_project2,
- role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
- f.MembershipFactory(project=m.private_project2,
- user=m.project_member_without_perms,
- email=m.project_member_without_perms.email,
- role__project=m.private_project2,
- role__permissions=[])
-
- f.MembershipFactory(project=m.public_project,
- user=m.project_owner,
- is_owner=True)
-
- f.MembershipFactory(project=m.private_project1,
- user=m.project_owner,
- is_owner=True)
-
- f.MembershipFactory(project=m.private_project2,
- user=m.project_owner,
- is_owner=True)
-
- m.public_userstory_ca = f.UserStoryCustomAttributeFactory(project=m.public_project)
- m.private_userstory_ca1 = f.UserStoryCustomAttributeFactory(project=m.private_project1)
- m.private_userstory_ca2 = f.UserStoryCustomAttributeFactory(project=m.private_project2)
-
- m.public_task_ca = f.TaskCustomAttributeFactory(project=m.public_project)
- m.private_task_ca1 = f.TaskCustomAttributeFactory(project=m.private_project1)
- m.private_task_ca2 = f.TaskCustomAttributeFactory(project=m.private_project2)
-
- m.public_issue_ca = f.IssueCustomAttributeFactory(project=m.public_project)
- m.private_issue_ca1 = f.IssueCustomAttributeFactory(project=m.private_project1)
- m.private_issue_ca2 = f.IssueCustomAttributeFactory(project=m.private_project2)
-
- return m
-
-
-#########################################################
-# User Story Custom Fields
-#########################################################
-
-def test_userstory_status_retrieve(client, data):
- public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
- private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
- private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'get', public_url, None, users)
- assert results == [200, 200, 200, 200, 200]
- results = helper_test_http_method(client, 'get', private1_url, None, users)
- assert results == [200, 200, 200, 200, 200]
- results = helper_test_http_method(client, 'get', private2_url, None, users)
- assert results == [401, 403, 403, 200, 200]
-
-
-def test_userstory_status_update(client, data):
- public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
- private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
- private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- userstory_ca_data = serializers.UserStoryCustomAttributeSerializer(data.public_userstory_ca).data
- userstory_ca_data["name"] = "test"
- userstory_ca_data = json.dumps(userstory_ca_data)
- results = helper_test_http_method(client, 'put', public_url, userstory_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
- userstory_ca_data = serializers.UserStoryCustomAttributeSerializer(data.private_userstory_ca1).data
- userstory_ca_data["name"] = "test"
- userstory_ca_data = json.dumps(userstory_ca_data)
- results = helper_test_http_method(client, 'put', private1_url, userstory_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
- userstory_ca_data = serializers.UserStoryCustomAttributeSerializer(data.private_userstory_ca2).data
- userstory_ca_data["name"] = "test"
- userstory_ca_data = json.dumps(userstory_ca_data)
- results = helper_test_http_method(client, 'put', private2_url, userstory_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
-
-def test_userstory_status_delete(client, data):
- public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
- private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
- private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'delete', public_url, None, users)
- assert results == [401, 403, 403, 403, 204]
- results = helper_test_http_method(client, 'delete', private1_url, None, users)
- assert results == [401, 403, 403, 403, 204]
- results = helper_test_http_method(client, 'delete', private2_url, None, users)
- assert results == [401, 403, 403, 403, 204]
-
-
-def test_userstory_status_list(client, data):
- url = reverse('userstory-custom-attributes-list')
-
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.registered_user)
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.project_member_without_perms)
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.project_member_with_perms)
- response = client.json.get(url)
- assert len(response.data) == 3
- assert response.status_code == 200
-
- client.login(data.project_owner)
- response = client.json.get(url)
- assert len(response.data) == 3
- assert response.status_code == 200
-
-
-def test_userstory_status_patch(client, data):
- public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
- private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
- private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
- results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
- results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
-
-
-def test_userstory_status_action_bulk_update_order(client, data):
- url = reverse('userstory-custom-attributes-bulk-update-order')
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- post_data = json.dumps({
- "bulk_userstory_custom_attributes": [(1,2)],
- "project": data.public_project.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
- post_data = json.dumps({
- "bulk_userstory_custom_attributes": [(1,2)],
- "project": data.private_project1.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
- post_data = json.dumps({
- "bulk_userstory_custom_attributes": [(1,2)],
- "project": data.private_project2.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
-
-#########################################################
-# Task Custom Fields
-#########################################################
-
-def test_task_status_retrieve(client, data):
- public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
- private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
- private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'get', public_url, None, users)
- assert results == [200, 200, 200, 200, 200]
- results = helper_test_http_method(client, 'get', private1_url, None, users)
- assert results == [200, 200, 200, 200, 200]
- results = helper_test_http_method(client, 'get', private2_url, None, users)
- assert results == [401, 403, 403, 200, 200]
-
-
-def test_task_status_update(client, data):
- public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
- private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
- private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- task_ca_data = serializers.TaskCustomAttributeSerializer(data.public_task_ca).data
- task_ca_data["name"] = "test"
- task_ca_data = json.dumps(task_ca_data)
- results = helper_test_http_method(client, 'put', public_url, task_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
- task_ca_data = serializers.TaskCustomAttributeSerializer(data.private_task_ca1).data
- task_ca_data["name"] = "test"
- task_ca_data = json.dumps(task_ca_data)
- results = helper_test_http_method(client, 'put', private1_url, task_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
- task_ca_data = serializers.TaskCustomAttributeSerializer(data.private_task_ca2).data
- task_ca_data["name"] = "test"
- task_ca_data = json.dumps(task_ca_data)
- results = helper_test_http_method(client, 'put', private2_url, task_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
-
-def test_task_status_delete(client, data):
- public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
- private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
- private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'delete', public_url, None, users)
- assert results == [401, 403, 403, 403, 204]
- results = helper_test_http_method(client, 'delete', private1_url, None, users)
- assert results == [401, 403, 403, 403, 204]
- results = helper_test_http_method(client, 'delete', private2_url, None, users)
- assert results == [401, 403, 403, 403, 204]
-
-
-def test_task_status_list(client, data):
- url = reverse('task-custom-attributes-list')
-
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.registered_user)
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.project_member_without_perms)
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.project_member_with_perms)
- response = client.json.get(url)
- assert len(response.data) == 3
- assert response.status_code == 200
-
- client.login(data.project_owner)
- response = client.json.get(url)
- assert len(response.data) == 3
- assert response.status_code == 200
-
-
-def test_task_status_patch(client, data):
- public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
- private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
- private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
- results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
- results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
-
-
-def test_task_status_action_bulk_update_order(client, data):
- url = reverse('task-custom-attributes-bulk-update-order')
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- post_data = json.dumps({
- "bulk_task_custom_attributes": [(1,2)],
- "project": data.public_project.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
- post_data = json.dumps({
- "bulk_task_custom_attributes": [(1,2)],
- "project": data.private_project1.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
- post_data = json.dumps({
- "bulk_task_custom_attributes": [(1,2)],
- "project": data.private_project2.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
-
-#########################################################
-# Issue Custom Fields
-#########################################################
-
-def test_issue_status_retrieve(client, data):
- public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
- private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
- private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'get', public_url, None, users)
- assert results == [200, 200, 200, 200, 200]
- results = helper_test_http_method(client, 'get', private1_url, None, users)
- assert results == [200, 200, 200, 200, 200]
- results = helper_test_http_method(client, 'get', private2_url, None, users)
- assert results == [401, 403, 403, 200, 200]
-
-
-def test_issue_status_update(client, data):
- public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
- private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
- private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- issue_ca_data = serializers.IssueCustomAttributeSerializer(data.public_issue_ca).data
- issue_ca_data["name"] = "test"
- issue_ca_data = json.dumps(issue_ca_data)
- results = helper_test_http_method(client, 'put', public_url, issue_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
- issue_ca_data = serializers.IssueCustomAttributeSerializer(data.private_issue_ca1).data
- issue_ca_data["name"] = "test"
- issue_ca_data = json.dumps(issue_ca_data)
- results = helper_test_http_method(client, 'put', private1_url, issue_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
- issue_ca_data = serializers.IssueCustomAttributeSerializer(data.private_issue_ca2).data
- issue_ca_data["name"] = "test"
- issue_ca_data = json.dumps(issue_ca_data)
- results = helper_test_http_method(client, 'put', private2_url, issue_ca_data, users)
- assert results == [401, 403, 403, 403, 200]
-
-
-def test_issue_status_delete(client, data):
- public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
- private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
- private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'delete', public_url, None, users)
- assert results == [401, 403, 403, 403, 204]
- results = helper_test_http_method(client, 'delete', private1_url, None, users)
- assert results == [401, 403, 403, 403, 204]
- results = helper_test_http_method(client, 'delete', private2_url, None, users)
- assert results == [401, 403, 403, 403, 204]
-
-
-def test_issue_status_list(client, data):
- url = reverse('issue-custom-attributes-list')
-
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.registered_user)
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.project_member_without_perms)
- response = client.json.get(url)
- assert len(response.data) == 2
- assert response.status_code == 200
-
- client.login(data.project_member_with_perms)
- response = client.json.get(url)
- assert len(response.data) == 3
- assert response.status_code == 200
-
- client.login(data.project_owner)
- response = client.json.get(url)
- assert len(response.data) == 3
- assert response.status_code == 200
-
-
-def test_issue_status_patch(client, data):
- public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
- private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
- private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
- results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
- results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
- assert results == [401, 403, 403, 403, 200]
-
-
-def test_issue_status_action_bulk_update_order(client, data):
- url = reverse('issue-custom-attributes-bulk-update-order')
-
- users = [
- None,
- data.registered_user,
- data.project_member_without_perms,
- data.project_member_with_perms,
- data.project_owner
- ]
-
- post_data = json.dumps({
- "bulk_issue_custom_attributes": [(1,2)],
- "project": data.public_project.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
- post_data = json.dumps({
- "bulk_issue_custom_attributes": [(1,2)],
- "project": data.private_project1.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
-
- post_data = json.dumps({
- "bulk_issue_custom_attributes": [(1,2)],
- "project": data.private_project2.pk
- })
- results = helper_test_http_method(client, 'post', url, post_data, users)
- assert results == [401, 403, 403, 403, 204]
diff --git a/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py b/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py
new file mode 100644
index 00000000..39caad51
--- /dev/null
+++ b/tests/integration/resources_permissions/test_issues_custom_attributes_resource.py
@@ -0,0 +1,428 @@
+# Copyright (C) 2015 Andrey Antukh
+# Copyright (C) 2015 Jesús Espino
+# Copyright (C) 2015 David Barragán
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+
+from django.core.urlresolvers import reverse
+
+from taiga.base.utils import json
+from taiga.projects.custom_attributes import serializers
+from taiga.permissions.permissions import MEMBERS_PERMISSIONS
+
+from tests import factories as f
+from tests.utils import helper_test_http_method
+
+import pytest
+pytestmark = pytest.mark.django_db(transaction=True)
+
+
+@pytest.fixture
+def data():
+ m = type("Models", (object,), {})
+ m.registered_user = f.UserFactory.create()
+ m.project_member_with_perms = f.UserFactory.create()
+ m.project_member_without_perms = f.UserFactory.create()
+ m.project_owner = f.UserFactory.create()
+ m.other_user = f.UserFactory.create()
+ m.superuser = f.UserFactory.create(is_superuser=True)
+
+ m.public_project = f.ProjectFactory(is_private=False,
+ anon_permissions=['view_project'],
+ public_permissions=['view_project'],
+ owner=m.project_owner)
+ m.private_project1 = f.ProjectFactory(is_private=True,
+ anon_permissions=['view_project'],
+ public_permissions=['view_project'],
+ owner=m.project_owner)
+ m.private_project2 = f.ProjectFactory(is_private=True,
+ anon_permissions=[],
+ public_permissions=[],
+ owner=m.project_owner)
+
+ m.public_membership = f.MembershipFactory(project=m.public_project,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.public_project,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+ m.private_membership1 = f.MembershipFactory(project=m.private_project1,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.private_project1,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+
+ f.MembershipFactory(project=m.private_project1,
+ user=m.project_member_without_perms,
+ email=m.project_member_without_perms.email,
+ role__project=m.private_project1,
+ role__permissions=[])
+
+ m.private_membership2 = f.MembershipFactory(project=m.private_project2,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.private_project2,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+ f.MembershipFactory(project=m.private_project2,
+ user=m.project_member_without_perms,
+ email=m.project_member_without_perms.email,
+ role__project=m.private_project2,
+ role__permissions=[])
+
+ f.MembershipFactory(project=m.public_project,
+ user=m.project_owner,
+ is_owner=True)
+
+ f.MembershipFactory(project=m.private_project1,
+ user=m.project_owner,
+ is_owner=True)
+
+ f.MembershipFactory(project=m.private_project2,
+ user=m.project_owner,
+ is_owner=True)
+
+ m.public_issue_ca = f.IssueCustomAttributeFactory(project=m.public_project)
+ m.private_issue_ca1 = f.IssueCustomAttributeFactory(project=m.private_project1)
+ m.private_issue_ca2 = f.IssueCustomAttributeFactory(project=m.private_project2)
+
+ #m.public_issue = f.IssueFactory(project=m.public_project, owner=m.project_owner)
+ #m.private_issue1 = f.IssueFactory(project=m.private_project1, owner=m.project_owner)
+ #m.private_issue2 = f.IssueFactory(project=m.private_project2, owner=m.project_owner)
+
+ #m.public_issue_cav = f.IssueCustomAttributesValuesFactory(project=m.public_project,
+ # issue=f.IssueFactory(project=m.public_project,
+ # owner=m.project_owner),
+ # values={str(m.public_issue_ca.id):"test"})
+ #m.private_issue_cav1 = f.IssueCustomAttributesValuesFactory(project=m.private_project1,
+ # issue=f.IssueFactory(project=m.private_project1,
+ # owner=m.project_owner),
+ # values={str(m.private_issue_ca1.id):"test"})
+ #m.private_issue_cav2 = f.IssueCustomAttributesValuesFactory(project=m.private_project2,
+ # issue=f.IssueFactory(project=m.private_project2,
+ # owner=m.project_owner),
+ # values={str(m.private_issue_ca2.id):"test"})
+
+ return m
+
+
+#########################################################
+# Issue Custom Attribute
+#########################################################
+
+def test_issue_custom_attribute_retrieve(client, data):
+ public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
+ private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
+ private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'get', public_url, None, users)
+ assert results == [200, 200, 200, 200, 200]
+ results = helper_test_http_method(client, 'get', private1_url, None, users)
+ assert results == [200, 200, 200, 200, 200]
+ results = helper_test_http_method(client, 'get', private2_url, None, users)
+ assert results == [401, 403, 403, 200, 200]
+
+
+def test_issue_custom_attribute_create(client, data):
+ public_url = reverse('issue-custom-attributes-list')
+ private1_url = reverse('issue-custom-attributes-list')
+ private2_url = reverse('issue-custom-attributes-list')
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ issue_ca_data = {"name": "test-new", "project": data.public_project.id}
+ issue_ca_data = json.dumps(issue_ca_data)
+ results = helper_test_http_method(client, 'post', public_url, issue_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+ issue_ca_data = {"name": "test-new", "project": data.private_project1.id}
+ issue_ca_data = json.dumps(issue_ca_data)
+ results = helper_test_http_method(client, 'post', private1_url, issue_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+ issue_ca_data = {"name": "test-new", "project": data.private_project2.id}
+ issue_ca_data = json.dumps(issue_ca_data)
+ results = helper_test_http_method(client, 'post', private2_url, issue_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+
+def test_issue_custom_attribute_update(client, data):
+ public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
+ private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
+ private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ issue_ca_data = serializers.IssueCustomAttributeSerializer(data.public_issue_ca).data
+ issue_ca_data["name"] = "test"
+ issue_ca_data = json.dumps(issue_ca_data)
+ results = helper_test_http_method(client, 'put', public_url, issue_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+ issue_ca_data = serializers.IssueCustomAttributeSerializer(data.private_issue_ca1).data
+ issue_ca_data["name"] = "test"
+ issue_ca_data = json.dumps(issue_ca_data)
+ results = helper_test_http_method(client, 'put', private1_url, issue_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+ issue_ca_data = serializers.IssueCustomAttributeSerializer(data.private_issue_ca2).data
+ issue_ca_data["name"] = "test"
+ issue_ca_data = json.dumps(issue_ca_data)
+ results = helper_test_http_method(client, 'put', private2_url, issue_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+
+def test_issue_custom_attribute_delete(client, data):
+ public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
+ private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
+ private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'delete', public_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+ results = helper_test_http_method(client, 'delete', private1_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+ results = helper_test_http_method(client, 'delete', private2_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+
+
+def test_issue_custom_attribute_list(client, data):
+ url = reverse('issue-custom-attributes-list')
+
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.registered_user)
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.project_member_without_perms)
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.project_member_with_perms)
+ response = client.json.get(url)
+ assert len(response.data) == 3
+ assert response.status_code == 200
+
+ client.login(data.project_owner)
+ response = client.json.get(url)
+ assert len(response.data) == 3
+ assert response.status_code == 200
+
+
+def test_issue_custom_attribute_patch(client, data):
+ public_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.public_issue_ca.pk})
+ private1_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca1.pk})
+ private2_url = reverse('issue-custom-attributes-detail', kwargs={"pk": data.private_issue_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+ results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+ results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+
+
+def test_issue_custom_attribute_action_bulk_update_order(client, data):
+ url = reverse('issue-custom-attributes-bulk-update-order')
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ post_data = json.dumps({
+ "bulk_issue_custom_attributes": [(1,2)],
+ "project": data.public_project.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+ post_data = json.dumps({
+ "bulk_issue_custom_attributes": [(1,2)],
+ "project": data.private_project1.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+ post_data = json.dumps({
+ "bulk_issue_custom_attributes": [(1,2)],
+ "project": data.private_project2.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+
+#########################################################
+# Issue Custom Attributes Values
+#########################################################
+
+#def test_issue_custom_attributes_values_retrieve(client, data):
+# public_url = reverse('issue-custom-attributes-values-detail', args=[data.public_issue_cav.issue.id])
+# private1_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav1.issue.id])
+# private2_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav2.issue.id])
+# users = [
+# None,
+# data.registered_user,
+# data.project_member_without_perms,
+# data.project_member_with_perms,
+# data.project_owner
+# ]
+#
+# results = helper_test_http_method(client, 'get', public_url, None, users)
+# assert results == [200, 200, 200, 200, 200]
+# results = helper_test_http_method(client, 'get', private1_url, None, users)
+# assert results == [200, 200, 200, 200, 200]
+# results = helper_test_http_method(client, 'get', private2_url, None, users)
+# assert results == [401, 403, 403, 200, 200]
+#
+#
+#def test_issue_custom_attributes_values_update(client, data):
+# public_url = reverse('issue-custom-attributes-values-detail', args=[data.public_issue_cav.issue.id])
+# private1_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav1.issue.id])
+# private2_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav2.issue.id])
+#
+# users = [
+# None,
+# data.registered_user,
+# data.project_member_without_perms,
+# data.project_member_with_perms,
+# data.project_owner
+# ]
+#
+# issue_cav_data = serializers.IssueCustomAttributesValuesSerializer(data.public_issue_cav).data
+# issue_cav_data["values"] = '{{"{}":"test-update"}}'.format(data.public_issue_ca.id)
+# issue_cav_data = json.dumps(issue_cav_data)
+# results = helper_test_http_method(client, 'put', public_url, issue_cav_data, users)
+# assert results == [401, 403, 403, 403, 200]
+#
+# issue_cav_data = serializers.IssueCustomAttributesValuesSerializer(data.private_issue_cav1).data
+# issue_cav_data["values"] = '{{"{}":"test-update"}}'.format(data.private_issue_ca1.id)
+# issue_cav_data = json.dumps(issue_cav_data)
+# results = helper_test_http_method(client, 'put', private1_url, issue_cav_data, users)
+# assert results == [401, 403, 403, 403, 200]
+#
+# issue_cav_data = serializers.IssueCustomAttributesValuesSerializer(data.private_issue_cav2).data
+# issue_cav_data["values"] = '{{"{}":"test-update"}}'.format(data.private_issue_ca2.id)
+# issue_cav_data = json.dumps(issue_cav_data)
+# results = helper_test_http_method(client, 'put', private2_url, issue_cav_data, users)
+# assert results == [401, 403, 403, 403, 200]
+#
+#
+#def test_issue_custom_attributes_values_delete(client, data):
+# public_url = reverse('issue-custom-attributes-values-detail', args=[data.public_issue_cav.issue.id])
+# private1_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav1.issue.id])
+# private2_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav2.issue.id])
+#
+# users = [
+# None,
+# data.registered_user,
+# data.project_member_without_perms,
+# data.project_member_with_perms,
+# data.project_owner
+# ]
+#
+# results = helper_test_http_method(client, 'delete', public_url, None, users)
+# assert results == [401, 403, 403, 403, 204]
+# results = helper_test_http_method(client, 'delete', private1_url, None, users)
+# assert results == [401, 403, 403, 403, 204]
+# results = helper_test_http_method(client, 'delete', private2_url, None, users)
+# assert results == [401, 403, 403, 403, 204]
+#
+#
+#def test_issue_custom_attributes_values_list(client, data):
+# url = reverse('issue-custom-attributes-values-list')
+#
+# response = client.json.get(url)
+# assert response.status_code == 404
+#
+# client.login(data.registered_user)
+# response = client.json.get(url)
+# assert response.status_code == 404
+#
+# client.login(data.project_member_without_perms)
+# response = client.json.get(url)
+# assert response.status_code == 404
+#
+# client.login(data.project_member_with_perms)
+# response = client.json.get(url)
+# assert response.status_code == 404
+#
+# client.login(data.project_owner)
+# response = client.json.get(url)
+# assert response.status_code == 404
+#
+#
+#def test_issue_custom_attributes_values_patch(client, data):
+# public_url = reverse('issue-custom-attributes-values-detail', args=[data.public_issue_cav.issue.id])
+# private1_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav1.issue.id])
+# private2_url = reverse('issue-custom-attributes-values-detail', args=[data.private_issue_cav2.issue.id])
+#
+# users = [
+# None,
+# data.registered_user,
+# data.project_member_without_perms,
+# data.project_member_with_perms,
+# data.project_owner
+# ]
+#
+# results = helper_test_http_method(client, 'patch', public_url,
+# '{{"values": {{"{}": "test-update"}}, "version": 1}}'.format(data.public_issue_ca.id), users)
+# assert results == [401, 403, 403, 403, 200]
+# results = helper_test_http_method(client, 'patch', private1_url,
+# '{{"values": {{"{}": "test-update"}}, "version": 1}}'.format(data.private_issue_ca1.id), users)
+# assert results == [401, 403, 403, 403, 200]
+# results = helper_test_http_method(client, 'patch', private2_url,
+# '{{"values": {{"{}": "test-update"}}, "version": 1}}'.format(data.private_issue_ca2.id), users)
+# assert results == [401, 403, 403, 403, 200]
diff --git a/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py b/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py
new file mode 100644
index 00000000..317ff991
--- /dev/null
+++ b/tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py
@@ -0,0 +1,287 @@
+# Copyright (C) 2015 Andrey Antukh
+# Copyright (C) 2015 Jesús Espino
+# Copyright (C) 2015 David Barragán
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+
+from django.core.urlresolvers import reverse
+
+from taiga.base.utils import json
+from taiga.projects.custom_attributes import serializers
+from taiga.permissions.permissions import MEMBERS_PERMISSIONS
+
+from tests import factories as f
+from tests.utils import helper_test_http_method
+
+import pytest
+pytestmark = pytest.mark.django_db(transaction=True)
+
+
+@pytest.fixture
+def data():
+ m = type("Models", (object,), {})
+ m.registered_user = f.UserFactory.create()
+ m.project_member_with_perms = f.UserFactory.create()
+ m.project_member_without_perms = f.UserFactory.create()
+ m.project_owner = f.UserFactory.create()
+ m.other_user = f.UserFactory.create()
+ m.superuser = f.UserFactory.create(is_superuser=True)
+
+ m.public_project = f.ProjectFactory(is_private=False,
+ anon_permissions=['view_project'],
+ public_permissions=['view_project'],
+ owner=m.project_owner)
+ m.private_project1 = f.ProjectFactory(is_private=True,
+ anon_permissions=['view_project'],
+ public_permissions=['view_project'],
+ owner=m.project_owner)
+ m.private_project2 = f.ProjectFactory(is_private=True,
+ anon_permissions=[],
+ public_permissions=[],
+ owner=m.project_owner)
+
+ m.public_membership = f.MembershipFactory(project=m.public_project,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.public_project,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+ m.private_membership1 = f.MembershipFactory(project=m.private_project1,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.private_project1,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+
+ f.MembershipFactory(project=m.private_project1,
+ user=m.project_member_without_perms,
+ email=m.project_member_without_perms.email,
+ role__project=m.private_project1,
+ role__permissions=[])
+
+ m.private_membership2 = f.MembershipFactory(project=m.private_project2,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.private_project2,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+ f.MembershipFactory(project=m.private_project2,
+ user=m.project_member_without_perms,
+ email=m.project_member_without_perms.email,
+ role__project=m.private_project2,
+ role__permissions=[])
+
+ f.MembershipFactory(project=m.public_project,
+ user=m.project_owner,
+ is_owner=True)
+
+ f.MembershipFactory(project=m.private_project1,
+ user=m.project_owner,
+ is_owner=True)
+
+ f.MembershipFactory(project=m.private_project2,
+ user=m.project_owner,
+ is_owner=True)
+
+ m.public_task_ca = f.TaskCustomAttributeFactory(project=m.public_project)
+ m.private_task_ca1 = f.TaskCustomAttributeFactory(project=m.private_project1)
+ m.private_task_ca2 = f.TaskCustomAttributeFactory(project=m.private_project2)
+
+ return m
+
+
+#########################################################
+# Task Custom Attribute
+#########################################################
+
+def test_task_custom_attribute_retrieve(client, data):
+ public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
+ private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
+ private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'get', public_url, None, users)
+ assert results == [200, 200, 200, 200, 200]
+ results = helper_test_http_method(client, 'get', private1_url, None, users)
+ assert results == [200, 200, 200, 200, 200]
+ results = helper_test_http_method(client, 'get', private2_url, None, users)
+ assert results == [401, 403, 403, 200, 200]
+
+
+def test_task_custom_attribute_create(client, data):
+ public_url = reverse('task-custom-attributes-list')
+ private1_url = reverse('task-custom-attributes-list')
+ private2_url = reverse('task-custom-attributes-list')
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ task_ca_data = {"name": "test-new", "project": data.public_project.id}
+ task_ca_data = json.dumps(task_ca_data)
+ results = helper_test_http_method(client, 'post', public_url, task_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+ task_ca_data = {"name": "test-new", "project": data.private_project1.id}
+ task_ca_data = json.dumps(task_ca_data)
+ results = helper_test_http_method(client, 'post', private1_url, task_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+ task_ca_data = {"name": "test-new", "project": data.private_project2.id}
+ task_ca_data = json.dumps(task_ca_data)
+ results = helper_test_http_method(client, 'post', private2_url, task_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+
+def test_task_custom_attribute_update(client, data):
+ public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
+ private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
+ private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ task_ca_data = serializers.TaskCustomAttributeSerializer(data.public_task_ca).data
+ task_ca_data["name"] = "test"
+ task_ca_data = json.dumps(task_ca_data)
+ results = helper_test_http_method(client, 'put', public_url, task_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+ task_ca_data = serializers.TaskCustomAttributeSerializer(data.private_task_ca1).data
+ task_ca_data["name"] = "test"
+ task_ca_data = json.dumps(task_ca_data)
+ results = helper_test_http_method(client, 'put', private1_url, task_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+ task_ca_data = serializers.TaskCustomAttributeSerializer(data.private_task_ca2).data
+ task_ca_data["name"] = "test"
+ task_ca_data = json.dumps(task_ca_data)
+ results = helper_test_http_method(client, 'put', private2_url, task_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+
+def test_task_custom_attribute_delete(client, data):
+ public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
+ private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
+ private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'delete', public_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+ results = helper_test_http_method(client, 'delete', private1_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+ results = helper_test_http_method(client, 'delete', private2_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+
+
+def test_task_custom_attribute_list(client, data):
+ url = reverse('task-custom-attributes-list')
+
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.registered_user)
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.project_member_without_perms)
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.project_member_with_perms)
+ response = client.json.get(url)
+ assert len(response.data) == 3
+ assert response.status_code == 200
+
+ client.login(data.project_owner)
+ response = client.json.get(url)
+ assert len(response.data) == 3
+ assert response.status_code == 200
+
+
+def test_task_custom_attribute_patch(client, data):
+ public_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.public_task_ca.pk})
+ private1_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca1.pk})
+ private2_url = reverse('task-custom-attributes-detail', kwargs={"pk": data.private_task_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+ results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+ results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+
+
+def test_task_custom_attribute_action_bulk_update_order(client, data):
+ url = reverse('task-custom-attributes-bulk-update-order')
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ post_data = json.dumps({
+ "bulk_task_custom_attributes": [(1,2)],
+ "project": data.public_project.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+ post_data = json.dumps({
+ "bulk_task_custom_attributes": [(1,2)],
+ "project": data.private_project1.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+ post_data = json.dumps({
+ "bulk_task_custom_attributes": [(1,2)],
+ "project": data.private_project2.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
diff --git a/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py b/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py
new file mode 100644
index 00000000..faee7fbe
--- /dev/null
+++ b/tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py
@@ -0,0 +1,287 @@
+# Copyright (C) 2015 Andrey Antukh
+# Copyright (C) 2015 Jesús Espino
+# Copyright (C) 2015 David Barragán
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+
+from django.core.urlresolvers import reverse
+
+from taiga.base.utils import json
+from taiga.projects.custom_attributes import serializers
+from taiga.permissions.permissions import MEMBERS_PERMISSIONS
+
+from tests import factories as f
+from tests.utils import helper_test_http_method
+
+import pytest
+pytestmark = pytest.mark.django_db(transaction=True)
+
+
+@pytest.fixture
+def data():
+ m = type("Models", (object,), {})
+ m.registered_user = f.UserFactory.create()
+ m.project_member_with_perms = f.UserFactory.create()
+ m.project_member_without_perms = f.UserFactory.create()
+ m.project_owner = f.UserFactory.create()
+ m.other_user = f.UserFactory.create()
+ m.superuser = f.UserFactory.create(is_superuser=True)
+
+ m.public_project = f.ProjectFactory(is_private=False,
+ anon_permissions=['view_project'],
+ public_permissions=['view_project'],
+ owner=m.project_owner)
+ m.private_project1 = f.ProjectFactory(is_private=True,
+ anon_permissions=['view_project'],
+ public_permissions=['view_project'],
+ owner=m.project_owner)
+ m.private_project2 = f.ProjectFactory(is_private=True,
+ anon_permissions=[],
+ public_permissions=[],
+ owner=m.project_owner)
+
+ m.public_membership = f.MembershipFactory(project=m.public_project,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.public_project,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+ m.private_membership1 = f.MembershipFactory(project=m.private_project1,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.private_project1,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+
+ f.MembershipFactory(project=m.private_project1,
+ user=m.project_member_without_perms,
+ email=m.project_member_without_perms.email,
+ role__project=m.private_project1,
+ role__permissions=[])
+
+ m.private_membership2 = f.MembershipFactory(project=m.private_project2,
+ user=m.project_member_with_perms,
+ email=m.project_member_with_perms.email,
+ role__project=m.private_project2,
+ role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS)))
+ f.MembershipFactory(project=m.private_project2,
+ user=m.project_member_without_perms,
+ email=m.project_member_without_perms.email,
+ role__project=m.private_project2,
+ role__permissions=[])
+
+ f.MembershipFactory(project=m.public_project,
+ user=m.project_owner,
+ is_owner=True)
+
+ f.MembershipFactory(project=m.private_project1,
+ user=m.project_owner,
+ is_owner=True)
+
+ f.MembershipFactory(project=m.private_project2,
+ user=m.project_owner,
+ is_owner=True)
+
+ m.public_userstory_ca = f.UserStoryCustomAttributeFactory(project=m.public_project)
+ m.private_userstory_ca1 = f.UserStoryCustomAttributeFactory(project=m.private_project1)
+ m.private_userstory_ca2 = f.UserStoryCustomAttributeFactory(project=m.private_project2)
+
+ return m
+
+
+#########################################################
+# User Story Custom Attribute
+#########################################################
+
+def test_userstory_custom_attribute_retrieve(client, data):
+ public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
+ private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
+ private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'get', public_url, None, users)
+ assert results == [200, 200, 200, 200, 200]
+ results = helper_test_http_method(client, 'get', private1_url, None, users)
+ assert results == [200, 200, 200, 200, 200]
+ results = helper_test_http_method(client, 'get', private2_url, None, users)
+ assert results == [401, 403, 403, 200, 200]
+
+
+def test_userstory_custom_attribute_create(client, data):
+ public_url = reverse('userstory-custom-attributes-list')
+ private1_url = reverse('userstory-custom-attributes-list')
+ private2_url = reverse('userstory-custom-attributes-list')
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ userstory_ca_data = {"name": "test-new", "project": data.public_project.id}
+ userstory_ca_data = json.dumps(userstory_ca_data)
+ results = helper_test_http_method(client, 'post', public_url, userstory_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+ userstory_ca_data = {"name": "test-new", "project": data.private_project1.id}
+ userstory_ca_data = json.dumps(userstory_ca_data)
+ results = helper_test_http_method(client, 'post', private1_url, userstory_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+ userstory_ca_data = {"name": "test-new", "project": data.private_project2.id}
+ userstory_ca_data = json.dumps(userstory_ca_data)
+ results = helper_test_http_method(client, 'post', private2_url, userstory_ca_data, users)
+ assert results == [401, 403, 403, 403, 201]
+
+
+def test_userstory_custom_attribute_update(client, data):
+ public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
+ private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
+ private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ userstory_ca_data = serializers.UserStoryCustomAttributeSerializer(data.public_userstory_ca).data
+ userstory_ca_data["name"] = "test"
+ userstory_ca_data = json.dumps(userstory_ca_data)
+ results = helper_test_http_method(client, 'put', public_url, userstory_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+ userstory_ca_data = serializers.UserStoryCustomAttributeSerializer(data.private_userstory_ca1).data
+ userstory_ca_data["name"] = "test"
+ userstory_ca_data = json.dumps(userstory_ca_data)
+ results = helper_test_http_method(client, 'put', private1_url, userstory_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+ userstory_ca_data = serializers.UserStoryCustomAttributeSerializer(data.private_userstory_ca2).data
+ userstory_ca_data["name"] = "test"
+ userstory_ca_data = json.dumps(userstory_ca_data)
+ results = helper_test_http_method(client, 'put', private2_url, userstory_ca_data, users)
+ assert results == [401, 403, 403, 403, 200]
+
+
+def test_userstory_custom_attribute_delete(client, data):
+ public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
+ private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
+ private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'delete', public_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+ results = helper_test_http_method(client, 'delete', private1_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+ results = helper_test_http_method(client, 'delete', private2_url, None, users)
+ assert results == [401, 403, 403, 403, 204]
+
+
+def test_userstory_custom_attribute_list(client, data):
+ url = reverse('userstory-custom-attributes-list')
+
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.registered_user)
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.project_member_without_perms)
+ response = client.json.get(url)
+ assert len(response.data) == 2
+ assert response.status_code == 200
+
+ client.login(data.project_member_with_perms)
+ response = client.json.get(url)
+ assert len(response.data) == 3
+ assert response.status_code == 200
+
+ client.login(data.project_owner)
+ response = client.json.get(url)
+ assert len(response.data) == 3
+ assert response.status_code == 200
+
+
+def test_userstory_custom_attribute_patch(client, data):
+ public_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.public_userstory_ca.pk})
+ private1_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca1.pk})
+ private2_url = reverse('userstory-custom-attributes-detail', kwargs={"pk": data.private_userstory_ca2.pk})
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+ results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+ results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
+ assert results == [401, 403, 403, 403, 200]
+
+
+def test_userstory_custom_attribute_action_bulk_update_order(client, data):
+ url = reverse('userstory-custom-attributes-bulk-update-order')
+
+ users = [
+ None,
+ data.registered_user,
+ data.project_member_without_perms,
+ data.project_member_with_perms,
+ data.project_owner
+ ]
+
+ post_data = json.dumps({
+ "bulk_userstory_custom_attributes": [(1,2)],
+ "project": data.public_project.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+ post_data = json.dumps({
+ "bulk_userstory_custom_attributes": [(1,2)],
+ "project": data.private_project1.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
+
+ post_data = json.dumps({
+ "bulk_userstory_custom_attributes": [(1,2)],
+ "project": data.private_project2.pk
+ })
+ results = helper_test_http_method(client, 'post', url, post_data, users)
+ assert results == [401, 403, 403, 403, 204]
diff --git a/tests/integration/test_custom_attributes.py b/tests/integration/test_custom_attributes.py
deleted file mode 100644
index 91e0a8f6..00000000
--- a/tests/integration/test_custom_attributes.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright (C) 2015 Andrey Antukh
-# Copyright (C) 2015 Jesús Espino
-# Copyright (C) 2015 David Barragán
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-
-from django.core.urlresolvers import reverse
-from taiga.base.utils import json
-
-from .. import factories as f
-
-import pytest
-pytestmark = pytest.mark.django_db
-
-
-#########################################################
-# User Story Custom Fields
-#########################################################
-
-def test_userstory_custom_attribute_duplicate_name_error_on_create(client):
- custom_attr_1 = f.UserStoryCustomAttributeFactory()
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
-
-
- url = reverse("userstory-custom-attributes-list")
- data = {"name": custom_attr_1.name,
- "project": custom_attr_1.project.pk}
-
- client.login(member.user)
- response = client.json.post(url, json.dumps(data))
- assert response.status_code == 400
-
-
-def test_userstory_custom_attribute_duplicate_name_error_on_update(client):
- custom_attr_1 = f.UserStoryCustomAttributeFactory()
- custom_attr_2 = f.UserStoryCustomAttributeFactory(project=custom_attr_1.project)
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
-
-
- url = reverse("userstory-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
- data = {"name": custom_attr_1.name}
-
- client.login(member.user)
- response = client.json.patch(url, json.dumps(data))
- assert response.status_code == 400
-
-
-def test_userstory_custom_attribute_duplicate_name_error_on_move_between_projects(client):
- custom_attr_1 = f.UserStoryCustomAttributeFactory()
- custom_attr_2 = f.UserStoryCustomAttributeFactory(name=custom_attr_1.name)
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
- f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_2.project,
- is_owner=True)
-
-
- url = reverse("userstory-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
- data = {"project": custom_attr_1.project.pk}
-
- client.login(member.user)
- response = client.json.patch(url, json.dumps(data))
- assert response.status_code == 400
-
-
-#########################################################
-# Task Custom Fields
-#########################################################
-
-def test_task_custom_attribute_duplicate_name_error_on_create(client):
- custom_attr_1 = f.TaskCustomAttributeFactory()
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
-
-
- url = reverse("task-custom-attributes-list")
- data = {"name": custom_attr_1.name,
- "project": custom_attr_1.project.pk}
-
- client.login(member.user)
- response = client.json.post(url, json.dumps(data))
- assert response.status_code == 400
-
-
-def test_task_custom_attribute_duplicate_name_error_on_update(client):
- custom_attr_1 = f.TaskCustomAttributeFactory()
- custom_attr_2 = f.TaskCustomAttributeFactory(project=custom_attr_1.project)
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
-
-
- url = reverse("task-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
- data = {"name": custom_attr_1.name}
-
- client.login(member.user)
- response = client.json.patch(url, json.dumps(data))
- assert response.status_code == 400
-
-
-def test_task_custom_attribute_duplicate_name_error_on_move_between_projects(client):
- custom_attr_1 = f.TaskCustomAttributeFactory()
- custom_attr_2 = f.TaskCustomAttributeFactory(name=custom_attr_1.name)
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
- f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_2.project,
- is_owner=True)
-
-
- url = reverse("task-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
- data = {"project": custom_attr_1.project.pk}
-
- client.login(member.user)
- response = client.json.patch(url, json.dumps(data))
- assert response.status_code == 400
-
-
-#########################################################
-# Issue Custom Fields
-#########################################################
-
-def test_issue_custom_attribute_duplicate_name_error_on_create(client):
- custom_attr_1 = f.IssueCustomAttributeFactory()
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
-
-
- url = reverse("issue-custom-attributes-list")
- data = {"name": custom_attr_1.name,
- "project": custom_attr_1.project.pk}
-
- client.login(member.user)
- response = client.json.post(url, json.dumps(data))
- assert response.status_code == 400
-
-
-def test_issue_custom_attribute_duplicate_name_error_on_update(client):
- custom_attr_1 = f.IssueCustomAttributeFactory()
- custom_attr_2 = f.IssueCustomAttributeFactory(project=custom_attr_1.project)
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
-
-
- url = reverse("issue-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
- data = {"name": custom_attr_1.name}
-
- client.login(member.user)
- response = client.json.patch(url, json.dumps(data))
- assert response.status_code == 400
-
-
-def test_issue_custom_attribute_duplicate_name_error_on_move_between_projects(client):
- custom_attr_1 = f.IssueCustomAttributeFactory()
- custom_attr_2 = f.IssueCustomAttributeFactory(name=custom_attr_1.name)
- member = f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_1.project,
- is_owner=True)
- f.MembershipFactory(user=custom_attr_1.project.owner,
- project=custom_attr_2.project,
- is_owner=True)
-
-
- url = reverse("issue-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
- data = {"project": custom_attr_1.project.pk}
-
- client.login(member.user)
- response = client.json.patch(url, json.dumps(data))
- assert response.status_code == 400
diff --git a/tests/integration/test_custom_attributes_issues.py b/tests/integration/test_custom_attributes_issues.py
new file mode 100644
index 00000000..50e689c1
--- /dev/null
+++ b/tests/integration/test_custom_attributes_issues.py
@@ -0,0 +1,271 @@
+# Copyright (C) 2015 Andrey Antukh
+# Copyright (C) 2015 Jesús Espino
+# Copyright (C) 2015 David Barragán
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+
+from django.core.urlresolvers import reverse
+from taiga.base.utils import json
+
+from .. import factories as f
+
+import pytest
+pytestmark = pytest.mark.django_db(transaction=True)
+
+
+#########################################################
+# Issue Custom Attributes
+#########################################################
+
+def test_issue_custom_attribute_duplicate_name_error_on_create(client):
+ custom_attr_1 = f.IssueCustomAttributeFactory()
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+
+
+ url = reverse("issue-custom-attributes-list")
+ data = {"name": custom_attr_1.name,
+ "project": custom_attr_1.project.pk}
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_issue_custom_attribute_duplicate_name_error_on_update(client):
+ custom_attr_1 = f.IssueCustomAttributeFactory()
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=custom_attr_1.project)
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+
+
+ url = reverse("issue-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
+ data = {"name": custom_attr_1.name}
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_issue_custom_attribute_duplicate_name_error_on_move_between_projects(client):
+ custom_attr_1 = f.IssueCustomAttributeFactory()
+ custom_attr_2 = f.IssueCustomAttributeFactory(name=custom_attr_1.name)
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+ f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_2.project,
+ is_owner=True)
+
+
+ url = reverse("issue-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
+ data = {"project": custom_attr_1.project.pk}
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+#########################################################
+# Issue Custom Attributes Values
+#########################################################
+
+def test_issue_custom_attributes_values_list(client):
+ member = f.MembershipFactory(is_owner=True)
+
+ url = reverse("issue-custom-attributes-values-list")
+
+ client.login(member.user)
+ response = client.json.get(url)
+ assert response.status_code == 404
+
+
+def test_issue_custom_attributes_values_create(client):
+ issue = f.IssueFactory()
+ member = f.MembershipFactory(user=issue.project.owner,
+ project=issue.project,
+ is_owner=True)
+
+ custom_attr_1 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("issue-custom-attributes-values-list")
+ data = {
+ "issue": issue.id,
+ "values": {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ }
+
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 201
+ assert json.loads(response.data["values"]) == data["values"]
+ issue = issue.__class__.objects.get(id=issue.id)
+ assert issue.custom_attributes_values.values == data["values"]
+
+
+def test_issue_custom_attributes_values_create_with_error_invalid_key(client):
+ issue = f.IssueFactory()
+ member = f.MembershipFactory(user=issue.project.owner,
+ project=issue.project,
+ is_owner=True)
+
+ custom_attr_1 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=issue.project)
+
+ url = reverse("issue-custom-attributes-values-list")
+ data = {
+ "issue": issue.id,
+ "values": {
+ ct1_id: "test_1",
+ "123456": "test_2"
+ },
+ }
+
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_issue_custom_attributes_values_update(client):
+ issue = f.IssueFactory()
+ member = f.MembershipFactory(user=issue.project.owner,
+ project=issue.project,
+ is_owner=True)
+
+ custom_attr_1 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ custom_attrs_val = f.IssueCustomAttributesValuesFactory(
+ issue=issue,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ url = reverse("issue-custom-attributes-values-detail", args=[issue.id])
+ data = {
+ "values": {
+ ct1_id: "test_1_updated",
+ ct2_id: "test_2_updated"
+ },
+ "version": custom_attrs_val.version
+ }
+
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 200
+ assert json.loads(response.data["values"]) == data["values"]
+ issue = issue.__class__.objects.get(id=issue.id)
+ assert issue.custom_attributes_values.values == data["values"]
+
+
+def test_issue_custom_attributes_values_update_with_error_invalid_key(client):
+ issue = f.IssueFactory()
+ member = f.MembershipFactory(user=issue.project.owner,
+ project=issue.project,
+ is_owner=True)
+
+ custom_attr_1 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ custom_attrs_val = f.IssueCustomAttributesValuesFactory(
+ issue=issue,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ url = reverse("issue-custom-attributes-values-detail", args=[issue.id])
+ data = {
+ "values": {
+ ct1_id: "test_1_updated",
+ "123456": "test_2_updated"
+ },
+ "version": custom_attrs_val.version
+ }
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_issue_custom_attributes_values_delete(client):
+ issue = f.IssueFactory()
+ member = f.MembershipFactory(user=issue.project.owner,
+ project=issue.project,
+ is_owner=True)
+
+ custom_attr_1 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("issue-custom-attributes-values-detail", args=[issue.id])
+ custom_attrs_val = f.IssueCustomAttributesValuesFactory(
+ issue=issue,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ client.login(member.user)
+ response = client.json.delete(url)
+ assert response.status_code == 204
+ assert issue.__class__.objects.filter(id=issue.id).exists()
+ assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()
+
+
+def test_issue_custom_attributes_values_delete_us(client):
+ issue = f.IssueFactory()
+ member = f.MembershipFactory(user=issue.project.owner,
+ project=issue.project,
+ is_owner=True)
+
+ custom_attr_1 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.IssueCustomAttributeFactory(project=issue.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("issues-detail", args=[issue.id])
+ custom_attrs_val = f.IssueCustomAttributesValuesFactory(
+ issue=issue,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ client.login(member.user)
+ response = client.json.delete(url)
+ assert response.status_code == 204
+ assert not issue.__class__.objects.filter(id=issue.id).exists()
+ assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()
diff --git a/tests/integration/test_custom_attributes_tasks.py b/tests/integration/test_custom_attributes_tasks.py
new file mode 100644
index 00000000..f6786737
--- /dev/null
+++ b/tests/integration/test_custom_attributes_tasks.py
@@ -0,0 +1,268 @@
+# Copyright (C) 2015 Andrey Antukh
+# Copyright (C) 2015 Jesús Espino
+# Copyright (C) 2015 David Barragán
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+
+from django.core.urlresolvers import reverse
+from taiga.base.utils import json
+
+from .. import factories as f
+
+import pytest
+pytestmark = pytest.mark.django_db(transaction=True)
+
+
+#########################################################
+# Task Custom Attributes
+#########################################################
+
+def test_task_custom_attribute_duplicate_name_error_on_create(client):
+ custom_attr_1 = f.TaskCustomAttributeFactory()
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+
+
+ url = reverse("task-custom-attributes-list")
+ data = {"name": custom_attr_1.name,
+ "project": custom_attr_1.project.pk}
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_task_custom_attribute_duplicate_name_error_on_update(client):
+ custom_attr_1 = f.TaskCustomAttributeFactory()
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=custom_attr_1.project)
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+
+
+ url = reverse("task-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
+ data = {"name": custom_attr_1.name}
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_task_custom_attribute_duplicate_name_error_on_move_between_projects(client):
+ custom_attr_1 = f.TaskCustomAttributeFactory()
+ custom_attr_2 = f.TaskCustomAttributeFactory(name=custom_attr_1.name)
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+ f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_2.project,
+ is_owner=True)
+
+
+ url = reverse("task-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
+ data = {"project": custom_attr_1.project.pk}
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+#########################################################
+# Task Custom Attributes Values
+#########################################################
+
+def test_task_custom_attributes_values_list(client):
+ member = f.MembershipFactory(is_owner=True)
+
+ url = reverse("task-custom-attributes-values-list")
+
+ client.login(member.user)
+ response = client.json.get(url)
+ assert response.status_code == 404
+
+
+def test_task_custom_attributes_values_create(client):
+ task = f.TaskFactory()
+ member = f.MembershipFactory(user=task.project.owner,
+ project=task.project,
+ is_owner=True)
+
+ custom_attr_1 = f.TaskCustomAttributeFactory(project=task.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=task.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("task-custom-attributes-values-list")
+ data = {
+ "task": task.id,
+ "values": {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ }
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 201
+ assert json.loads(response.data["values"]) == data["values"]
+ task = task.__class__.objects.get(id=task.id)
+ assert task.custom_attributes_values.values == data["values"]
+
+
+def test_task_custom_attributes_values_create_with_error_invalid_key(client):
+ task = f.TaskFactory()
+ member = f.MembershipFactory(user=task.project.owner,
+ project=task.project,
+ is_owner=True)
+
+ custom_attr_1 = f.TaskCustomAttributeFactory(project=task.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=task.project)
+
+ url = reverse("task-custom-attributes-values-list")
+ data = {
+ "task": task.id,
+ "values": {
+ ct1_id: "test_1",
+ "123456": "test_2"
+ },
+ }
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_task_custom_attributes_values_update(client):
+ task = f.TaskFactory()
+ member = f.MembershipFactory(user=task.project.owner,
+ project=task.project,
+ is_owner=True)
+
+ custom_attr_1 = f.TaskCustomAttributeFactory(project=task.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=task.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ custom_attrs_val = f.TaskCustomAttributesValuesFactory(
+ task=task,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ url = reverse("task-custom-attributes-values-detail", args=[task.id])
+ data = {
+ "values": {
+ ct1_id: "test_1_updated",
+ ct2_id: "test_2_updated"
+ },
+ "version": custom_attrs_val.version
+ }
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 200
+ assert json.loads(response.data["values"]) == data["values"]
+ task = task.__class__.objects.get(id=task.id)
+ assert task.custom_attributes_values.values == data["values"]
+
+
+def test_task_custom_attributes_values_update_with_error_invalid_key(client):
+ task = f.TaskFactory()
+ member = f.MembershipFactory(user=task.project.owner,
+ project=task.project,
+ is_owner=True)
+
+ custom_attr_1 = f.TaskCustomAttributeFactory(project=task.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=task.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ custom_attrs_val = f.TaskCustomAttributesValuesFactory(
+ task=task,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+ url = reverse("task-custom-attributes-values-detail", args=[task.id])
+ data = {
+ "values": {
+ ct1_id: "test_1_updated",
+ "123456": "test_2_updated"
+ },
+ "version": custom_attrs_val.version
+ }
+
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_task_custom_attributes_values_delete(client):
+ task = f.TaskFactory()
+ member = f.MembershipFactory(user=task.project.owner,
+ project=task.project,
+ is_owner=True)
+
+ custom_attr_1 = f.TaskCustomAttributeFactory(project=task.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=task.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("task-custom-attributes-values-detail", args=[task.id])
+ custom_attrs_val = f.TaskCustomAttributesValuesFactory(
+ task=task,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ client.login(member.user)
+ response = client.json.delete(url)
+ assert response.status_code == 204
+ assert task.__class__.objects.filter(id=task.id).exists()
+ assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()
+
+
+def test_task_custom_attributes_values_delete_us(client):
+ task = f.TaskFactory()
+ member = f.MembershipFactory(user=task.project.owner,
+ project=task.project,
+ is_owner=True)
+
+ custom_attr_1 = f.TaskCustomAttributeFactory(project=task.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.TaskCustomAttributeFactory(project=task.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("tasks-detail", args=[task.id])
+ custom_attrs_val = f.TaskCustomAttributesValuesFactory(
+ task=task,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ client.login(member.user)
+ response = client.json.delete(url)
+ assert response.status_code == 204
+ assert not task.__class__.objects.filter(id=task.id).exists()
+ assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()
diff --git a/tests/integration/test_custom_attributes_user_stories.py b/tests/integration/test_custom_attributes_user_stories.py
new file mode 100644
index 00000000..8db3f395
--- /dev/null
+++ b/tests/integration/test_custom_attributes_user_stories.py
@@ -0,0 +1,268 @@
+# Copyright (C) 2015 Andrey Antukh
+# Copyright (C) 2015 Jesús Espino
+# Copyright (C) 2015 David Barragán
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+
+from django.core.urlresolvers import reverse
+from taiga.base.utils import json
+
+from .. import factories as f
+
+import pytest
+pytestmark = pytest.mark.django_db(transaction=True)
+
+
+#########################################################
+# User Story Custom Attributes
+#########################################################
+
+def test_userstory_custom_attribute_duplicate_name_error_on_create(client):
+ custom_attr_1 = f.UserStoryCustomAttributeFactory()
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+
+
+ url = reverse("userstory-custom-attributes-list")
+ data = {"name": custom_attr_1.name,
+ "project": custom_attr_1.project.pk}
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_userstory_custom_attribute_duplicate_name_error_on_update(client):
+ custom_attr_1 = f.UserStoryCustomAttributeFactory()
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=custom_attr_1.project)
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+
+
+ url = reverse("userstory-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
+ data = {"name": custom_attr_1.name}
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_userstory_custom_attribute_duplicate_name_error_on_move_between_projects(client):
+ custom_attr_1 = f.UserStoryCustomAttributeFactory()
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(name=custom_attr_1.name)
+ member = f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_1.project,
+ is_owner=True)
+ f.MembershipFactory(user=custom_attr_1.project.owner,
+ project=custom_attr_2.project,
+ is_owner=True)
+
+
+ url = reverse("userstory-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
+ data = {"project": custom_attr_1.project.pk}
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+#########################################################
+# User Story Custom Attributes Values
+#########################################################
+
+def test_userstory_custom_attributes_values_list(client):
+ member = f.MembershipFactory(is_owner=True)
+
+ url = reverse("userstory-custom-attributes-values-list")
+
+ client.login(member.user)
+ response = client.json.get(url)
+ assert response.status_code == 404
+
+
+def test_userstory_custom_attributes_values_create(client):
+ user_story = f.UserStoryFactory()
+ member = f.MembershipFactory(user=user_story.project.owner,
+ project=user_story.project,
+ is_owner=True)
+
+ custom_attr_1 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("userstory-custom-attributes-values-list")
+ data = {
+ "user_story": user_story.id,
+ "values": {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ }
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 201
+ assert json.loads(response.data["values"]) == data["values"]
+ user_story = user_story.__class__.objects.get(id=user_story.id)
+ assert user_story.custom_attributes_values.values == data["values"]
+
+
+def test_userstory_custom_attributes_values_create_with_error_invalid_key(client):
+ user_story = f.UserStoryFactory()
+ member = f.MembershipFactory(user=user_story.project.owner,
+ project=user_story.project,
+ is_owner=True)
+
+ custom_attr_1 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+
+ url = reverse("userstory-custom-attributes-values-list")
+ data = {
+ "user_story": user_story.id,
+ "values": {
+ ct1_id: "test_1",
+ "123456": "test_2"
+ },
+ }
+
+ client.login(member.user)
+ response = client.json.post(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_userstory_custom_attributes_values_update(client):
+ user_story = f.UserStoryFactory()
+ member = f.MembershipFactory(user=user_story.project.owner,
+ project=user_story.project,
+ is_owner=True)
+
+ custom_attr_1 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ custom_attrs_val = f.UserStoryCustomAttributesValuesFactory(
+ user_story=user_story,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ url = reverse("userstory-custom-attributes-values-detail", args=[user_story.id])
+ data = {
+ "values": {
+ ct1_id: "test_1_updated",
+ ct2_id: "test_2_updated"
+ },
+ "version": custom_attrs_val.version
+ }
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 200
+ assert json.loads(response.data["values"]) == data["values"]
+ user_story = user_story.__class__.objects.get(id=user_story.id)
+ assert user_story.custom_attributes_values.values == data["values"]
+
+
+def test_userstory_custom_attributes_values_update_with_error_invalid_key(client):
+ user_story = f.UserStoryFactory()
+ member = f.MembershipFactory(user=user_story.project.owner,
+ project=user_story.project,
+ is_owner=True)
+
+ custom_attr_1 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ custom_attrs_val = f.UserStoryCustomAttributesValuesFactory(
+ user_story=user_story,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ url = reverse("userstory-custom-attributes-values-detail", args=[user_story.id])
+ data = {
+ "values": {
+ ct1_id: "test_1_updated",
+ "123456": "test_2_updated"
+ },
+ "version": custom_attrs_val.version
+ }
+
+ client.login(member.user)
+ response = client.json.patch(url, json.dumps(data))
+ assert response.status_code == 400
+
+
+def test_userstory_custom_attributes_values_delete(client):
+ user_story = f.UserStoryFactory()
+ member = f.MembershipFactory(user=user_story.project.owner,
+ project=user_story.project,
+ is_owner=True)
+
+ custom_attr_1 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("userstory-custom-attributes-values-detail", args=[user_story.id])
+ custom_attrs_val = f.UserStoryCustomAttributesValuesFactory(
+ user_story=user_story,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ client.login(member.user)
+ response = client.json.delete(url)
+ assert response.status_code == 204
+ assert user_story.__class__.objects.filter(id=user_story.id).exists()
+ assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()
+
+
+def test_userstory_custom_attributes_values_delete_us(client):
+ user_story = f.UserStoryFactory()
+ member = f.MembershipFactory(user=user_story.project.owner,
+ project=user_story.project,
+ is_owner=True)
+
+ custom_attr_1 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct1_id = "{}".format(custom_attr_1.id)
+ custom_attr_2 = f.UserStoryCustomAttributeFactory(project=user_story.project)
+ ct2_id = "{}".format(custom_attr_2.id)
+
+ url = reverse("userstories-detail", args=[user_story.id])
+ custom_attrs_val = f.UserStoryCustomAttributesValuesFactory(
+ user_story=user_story,
+ values= {
+ ct1_id: "test_1",
+ ct2_id: "test_2"
+ },
+ )
+
+ client.login(member.user)
+ response = client.json.delete(url)
+ assert response.status_code == 204
+ assert not user_story.__class__.objects.filter(id=user_story.id).exists()
+ assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()