From 2893213932bc9706bfff00b11ad65bdbe9564445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 5 Feb 2015 18:08:09 +0100 Subject: [PATCH] US #55: Custom fields - Create model to save the custom attributes values and update the history --- taiga/projects/custom_attributes/api.py | 54 ++ ...svalues_userstorycustomattributesvalues.py | 66 ++ taiga/projects/custom_attributes/models.py | 68 +- .../projects/custom_attributes/permissions.py | 42 ++ .../projects/custom_attributes/serializers.py | 80 ++- taiga/projects/history/freeze_impl.py | 43 ++ taiga/projects/history/models.py | 29 + .../emails/includes/fields_diff-html.jinja | 53 +- .../emails/includes/fields_diff-text.jinja | 35 +- taiga/projects/issues/serializers.py | 2 +- taiga/projects/tasks/serializers.py | 2 +- taiga/projects/userstories/models.py | 2 +- taiga/projects/userstories/serializers.py | 20 +- taiga/routers.py | 10 + tests/factories.py | 27 + .../test_custom_attributes_resource.py | 586 ------------------ .../test_issues_custom_attributes_resource.py | 428 +++++++++++++ .../test_tasks_custom_attributes_resource.py | 287 +++++++++ ..._userstories_custom_attributes_resource.py | 287 +++++++++ tests/integration/test_custom_attributes.py | 189 ------ .../test_custom_attributes_issues.py | 271 ++++++++ .../test_custom_attributes_tasks.py | 268 ++++++++ .../test_custom_attributes_user_stories.py | 268 ++++++++ 23 files changed, 2310 insertions(+), 807 deletions(-) create mode 100644 taiga/projects/custom_attributes/migrations/0002_issuecustomattributesvalues_taskcustomattributesvalues_userstorycustomattributesvalues.py delete mode 100644 tests/integration/resources_permissions/test_custom_attributes_resource.py create mode 100644 tests/integration/resources_permissions/test_issues_custom_attributes_resource.py create mode 100644 tests/integration/resources_permissions/test_tasks_custom_attributes_resource.py create mode 100644 tests/integration/resources_permissions/test_userstories_custom_attributes_resource.py delete mode 100644 tests/integration/test_custom_attributes.py create mode 100644 tests/integration/test_custom_attributes_issues.py create mode 100644 tests/integration/test_custom_attributes_tasks.py create mode 100644 tests/integration/test_custom_attributes_user_stories.py 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()