Epic custom attributes values
parent
f70923c064
commit
389a18026b
|
@ -38,6 +38,11 @@ class BaseCustomAttributeAdmin:
|
||||||
raw_id_fields = ["project"]
|
raw_id_fields = ["project"]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.EpicCustomAttribute)
|
||||||
|
class EpicCustomAttributeAdmin(BaseCustomAttributeAdmin, admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.UserStoryCustomAttribute)
|
@admin.register(models.UserStoryCustomAttribute)
|
||||||
class UserStoryCustomAttributeAdmin(BaseCustomAttributeAdmin, admin.ModelAdmin):
|
class UserStoryCustomAttributeAdmin(BaseCustomAttributeAdmin, admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -41,6 +41,18 @@ from . import services
|
||||||
# Custom Attribute ViewSets
|
# Custom Attribute ViewSets
|
||||||
#######################################################
|
#######################################################
|
||||||
|
|
||||||
|
class EpicCustomAttributeViewSet(BulkUpdateOrderMixin, BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
|
model = models.EpicCustomAttribute
|
||||||
|
serializer_class = serializers.EpicCustomAttributeSerializer
|
||||||
|
validator_class = validators.EpicCustomAttributeValidator
|
||||||
|
permission_classes = (permissions.EpicCustomAttributePermission,)
|
||||||
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
|
filter_fields = ("project",)
|
||||||
|
bulk_update_param = "bulk_epic_custom_attributes"
|
||||||
|
bulk_update_perm = "change_epic_custom_attributes"
|
||||||
|
bulk_update_order_action = services.bulk_update_epic_custom_attribute_order
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributeViewSet(BulkUpdateOrderMixin, BlockedByProjectMixin, ModelCrudViewSet):
|
class UserStoryCustomAttributeViewSet(BulkUpdateOrderMixin, BlockedByProjectMixin, ModelCrudViewSet):
|
||||||
model = models.UserStoryCustomAttribute
|
model = models.UserStoryCustomAttribute
|
||||||
serializer_class = serializers.UserStoryCustomAttributeSerializer
|
serializer_class = serializers.UserStoryCustomAttributeSerializer
|
||||||
|
@ -87,6 +99,20 @@ class BaseCustomAttributesValuesViewSet(OCCResourceMixin, HistoryResourceMixin,
|
||||||
return getattr(obj, self.content_object)
|
return getattr(obj, self.content_object)
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributesValuesViewSet(BaseCustomAttributesValuesViewSet):
|
||||||
|
model = models.EpicCustomAttributesValues
|
||||||
|
serializer_class = serializers.EpicCustomAttributesValuesSerializer
|
||||||
|
validator_class = validators.EpicCustomAttributesValuesValidator
|
||||||
|
permission_classes = (permissions.EpicCustomAttributesValuesPermission,)
|
||||||
|
lookup_field = "epic_id"
|
||||||
|
content_object = "epic"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = self.model.objects.all()
|
||||||
|
qs = qs.select_related("epic", "epic__project")
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributesValuesViewSet(BaseCustomAttributesValuesViewSet):
|
class UserStoryCustomAttributesValuesViewSet(BaseCustomAttributesValuesViewSet):
|
||||||
model = models.UserStoryCustomAttributesValues
|
model = models.UserStoryCustomAttributesValues
|
||||||
serializer_class = serializers.UserStoryCustomAttributesValuesSerializer
|
serializer_class = serializers.UserStoryCustomAttributesValuesSerializer
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-06-30 08:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import django_pgjson.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('epics', '0001_initial'),
|
||||||
|
('projects', '0049_auto_20160629_1443'),
|
||||||
|
('custom_attributes', '0007_auto_20160208_1751'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='issuecustomattributesvalues',
|
||||||
|
options={'ordering': ['id'], 'verbose_name': 'issue custom attributes values', 'verbose_name_plural': 'issue custom attributes values'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='taskcustomattributesvalues',
|
||||||
|
options={'ordering': ['id'], 'verbose_name': 'task custom attributes values', 'verbose_name_plural': 'task custom attributes values'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='userstorycustomattributesvalues',
|
||||||
|
options={'ordering': ['id'], 'verbose_name': 'user story custom attributes values', 'verbose_name_plural': 'user story custom attributes values'},
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EpicCustomAttribute',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=64, verbose_name='name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
|
('type', models.CharField(choices=[('text', 'Text'), ('multiline', 'Multi-Line Text'), ('date', 'Date'), ('url', 'Url')], default='text', max_length=16, verbose_name='type')),
|
||||||
|
('order', models.IntegerField(default=10000, verbose_name='order')),
|
||||||
|
('created_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created date')),
|
||||||
|
('modified_date', models.DateTimeField(verbose_name='modified date')),
|
||||||
|
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='epiccustomattributes', to='projects.Project', verbose_name='project')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'epic custom attributes',
|
||||||
|
'verbose_name': 'epic custom attribute',
|
||||||
|
'abstract': False,
|
||||||
|
'ordering': ['project', 'order', 'name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EpicCustomAttributesValues',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('version', models.IntegerField(default=1, verbose_name='version')),
|
||||||
|
('attributes_values', django_pgjson.fields.JsonField(default={}, verbose_name='values')),
|
||||||
|
('epic', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_attributes_values', to='epics.Epic', verbose_name='epic')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'epic custom attributes values',
|
||||||
|
'verbose_name': 'epic custom attributes values',
|
||||||
|
'abstract': False,
|
||||||
|
'ordering': ['id'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='epiccustomattribute',
|
||||||
|
unique_together=set([('project', 'name')]),
|
||||||
|
),
|
||||||
|
|
||||||
|
# Trigger: Clean epiccustomattributes values before remove a epiccustomattribute
|
||||||
|
migrations.RunSQL(
|
||||||
|
"""
|
||||||
|
CREATE TRIGGER "update_epiccustomvalues_after_remove_epiccustomattribute"
|
||||||
|
AFTER DELETE ON custom_attributes_epiccustomattribute
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE clean_key_in_custom_attributes_values('custom_attributes_epiccustomattributesvalues');
|
||||||
|
""",
|
||||||
|
reverse_sql="""DROP TRIGGER IF EXISTS "update_epiccustomvalues_after_remove_epiccustomattribute"
|
||||||
|
ON custom_attributes_epiccustomattribute
|
||||||
|
CASCADE;"""
|
||||||
|
),
|
||||||
|
]
|
|
@ -31,7 +31,6 @@ from . import choices
|
||||||
# Custom Attribute Models
|
# Custom Attribute Models
|
||||||
#######################################################
|
#######################################################
|
||||||
|
|
||||||
|
|
||||||
class AbstractCustomAttribute(models.Model):
|
class AbstractCustomAttribute(models.Model):
|
||||||
name = models.CharField(null=False, blank=False, max_length=64, verbose_name=_("name"))
|
name = models.CharField(null=False, blank=False, max_length=64, verbose_name=_("name"))
|
||||||
description = models.TextField(null=False, blank=True, verbose_name=_("description"))
|
description = models.TextField(null=False, blank=True, verbose_name=_("description"))
|
||||||
|
@ -63,6 +62,12 @@ class AbstractCustomAttribute(models.Model):
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttribute(AbstractCustomAttribute):
|
||||||
|
class Meta(AbstractCustomAttribute.Meta):
|
||||||
|
verbose_name = "epic custom attribute"
|
||||||
|
verbose_name_plural = "epic custom attributes"
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttribute(AbstractCustomAttribute):
|
class UserStoryCustomAttribute(AbstractCustomAttribute):
|
||||||
class Meta(AbstractCustomAttribute.Meta):
|
class Meta(AbstractCustomAttribute.Meta):
|
||||||
verbose_name = "user story custom attribute"
|
verbose_name = "user story custom attribute"
|
||||||
|
@ -93,13 +98,28 @@ class AbstractCustomAttributesValues(OCCModelMixin, models.Model):
|
||||||
ordering = ["id"]
|
ordering = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributesValues(AbstractCustomAttributesValues):
|
||||||
|
epic = models.OneToOneField("epics.Epic",
|
||||||
|
null=False, blank=False, related_name="custom_attributes_values",
|
||||||
|
verbose_name=_("epic"))
|
||||||
|
|
||||||
|
class Meta(AbstractCustomAttributesValues.Meta):
|
||||||
|
verbose_name = "epic custom attributes values"
|
||||||
|
verbose_name_plural = "epic custom attributes values"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def project(self):
|
||||||
|
# NOTE: This property simplifies checking permissions
|
||||||
|
return self.epic.project
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributesValues(AbstractCustomAttributesValues):
|
class UserStoryCustomAttributesValues(AbstractCustomAttributesValues):
|
||||||
user_story = models.OneToOneField("userstories.UserStory",
|
user_story = models.OneToOneField("userstories.UserStory",
|
||||||
null=False, blank=False, related_name="custom_attributes_values",
|
null=False, blank=False, related_name="custom_attributes_values",
|
||||||
verbose_name=_("user story"))
|
verbose_name=_("user story"))
|
||||||
|
|
||||||
class Meta(AbstractCustomAttributesValues.Meta):
|
class Meta(AbstractCustomAttributesValues.Meta):
|
||||||
verbose_name = "user story ustom attributes values"
|
verbose_name = "user story custom attributes values"
|
||||||
verbose_name_plural = "user story custom attributes values"
|
verbose_name_plural = "user story custom attributes values"
|
||||||
index_together = [("user_story",)]
|
index_together = [("user_story",)]
|
||||||
|
|
||||||
|
@ -115,7 +135,7 @@ class TaskCustomAttributesValues(AbstractCustomAttributesValues):
|
||||||
verbose_name=_("task"))
|
verbose_name=_("task"))
|
||||||
|
|
||||||
class Meta(AbstractCustomAttributesValues.Meta):
|
class Meta(AbstractCustomAttributesValues.Meta):
|
||||||
verbose_name = "task ustom attributes values"
|
verbose_name = "task custom attributes values"
|
||||||
verbose_name_plural = "task custom attributes values"
|
verbose_name_plural = "task custom attributes values"
|
||||||
index_together = [("task",)]
|
index_together = [("task",)]
|
||||||
|
|
||||||
|
@ -131,7 +151,7 @@ class IssueCustomAttributesValues(AbstractCustomAttributesValues):
|
||||||
verbose_name=_("issue"))
|
verbose_name=_("issue"))
|
||||||
|
|
||||||
class Meta(AbstractCustomAttributesValues.Meta):
|
class Meta(AbstractCustomAttributesValues.Meta):
|
||||||
verbose_name = "issue ustom attributes values"
|
verbose_name = "issue custom attributes values"
|
||||||
verbose_name_plural = "issue custom attributes values"
|
verbose_name_plural = "issue custom attributes values"
|
||||||
index_together = [("issue",)]
|
index_together = [("issue",)]
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,18 @@ from taiga.base.api.permissions import IsSuperUser
|
||||||
# Custom Attribute Permissions
|
# Custom Attribute Permissions
|
||||||
#######################################################
|
#######################################################
|
||||||
|
|
||||||
|
class EpicCustomAttributePermission(TaigaResourcePermission):
|
||||||
|
enought_perms = IsProjectAdmin() | IsSuperUser()
|
||||||
|
global_perms = None
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
create_perms = IsProjectAdmin()
|
||||||
|
update_perms = IsProjectAdmin()
|
||||||
|
partial_update_perms = IsProjectAdmin()
|
||||||
|
destroy_perms = IsProjectAdmin()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
bulk_update_order_perms = IsProjectAdmin()
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributePermission(TaigaResourcePermission):
|
class UserStoryCustomAttributePermission(TaigaResourcePermission):
|
||||||
enought_perms = IsProjectAdmin() | IsSuperUser()
|
enought_perms = IsProjectAdmin() | IsSuperUser()
|
||||||
global_perms = None
|
global_perms = None
|
||||||
|
@ -67,6 +79,14 @@ class IssueCustomAttributePermission(TaigaResourcePermission):
|
||||||
# Custom Attributes Values Permissions
|
# Custom Attributes Values Permissions
|
||||||
#######################################################
|
#######################################################
|
||||||
|
|
||||||
|
class EpicCustomAttributesValuesPermission(TaigaResourcePermission):
|
||||||
|
enought_perms = IsProjectAdmin() | IsSuperUser()
|
||||||
|
global_perms = None
|
||||||
|
retrieve_perms = HasProjectPerm('view_us')
|
||||||
|
update_perms = HasProjectPerm('modify_us')
|
||||||
|
partial_update_perms = HasProjectPerm('modify_us')
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributesValuesPermission(TaigaResourcePermission):
|
class UserStoryCustomAttributesValuesPermission(TaigaResourcePermission):
|
||||||
enought_perms = IsProjectAdmin() | IsSuperUser()
|
enought_perms = IsProjectAdmin() | IsSuperUser()
|
||||||
global_perms = None
|
global_perms = None
|
||||||
|
|
|
@ -36,6 +36,10 @@ class BaseCustomAttributeSerializer(serializers.LightSerializer):
|
||||||
modified_date = Field()
|
modified_date = Field()
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributeSerializer(BaseCustomAttributeSerializer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributeSerializer(BaseCustomAttributeSerializer):
|
class UserStoryCustomAttributeSerializer(BaseCustomAttributeSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -56,6 +60,10 @@ class BaseCustomAttributesValuesSerializer(serializers.LightSerializer):
|
||||||
version = Field()
|
version = Field()
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributesValuesSerializer(BaseCustomAttributesValuesSerializer):
|
||||||
|
epic = Field(attr="epic.id")
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributesValuesSerializer(BaseCustomAttributesValuesSerializer):
|
class UserStoryCustomAttributesValuesSerializer(BaseCustomAttributesValuesSerializer):
|
||||||
user_story = Field(attr="user_story.id")
|
user_story = Field(attr="user_story.id")
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,23 @@ from django.db import transaction
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def bulk_update_epic_custom_attribute_order(project, user, data):
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
prepare bulk_update_order as update custom_attributes_epiccustomattribute set "order" = $1
|
||||||
|
where custom_attributes_epiccustomattribute.id = $2 and
|
||||||
|
custom_attributes_epiccustomattribute.project_id = $3;
|
||||||
|
"""
|
||||||
|
cursor.execute(sql)
|
||||||
|
for id, order in data:
|
||||||
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def bulk_update_userstory_custom_attribute_order(project, user, data):
|
def bulk_update_userstory_custom_attribute_order(project, user, data):
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
|
@ -19,6 +19,12 @@
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def create_custom_attribute_value_when_create_epic(sender, instance, created, **kwargs):
|
||||||
|
if created:
|
||||||
|
models.EpicCustomAttributesValues.objects.get_or_create(epic=instance,
|
||||||
|
defaults={"attributes_values":{}})
|
||||||
|
|
||||||
|
|
||||||
def create_custom_attribute_value_when_create_user_story(sender, instance, created, **kwargs):
|
def create_custom_attribute_value_when_create_user_story(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
models.UserStoryCustomAttributesValues.objects.get_or_create(user_story=instance,
|
models.UserStoryCustomAttributesValues.objects.get_or_create(user_story=instance,
|
||||||
|
|
|
@ -66,6 +66,11 @@ class BaseCustomAttributeValidator(ModelValidator):
|
||||||
return self._validate_integrity_between_project_and_name(attrs, source)
|
return self._validate_integrity_between_project_and_name(attrs, source)
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributeValidator(BaseCustomAttributeValidator):
|
||||||
|
class Meta(BaseCustomAttributeValidator.Meta):
|
||||||
|
model = models.EpicCustomAttribute
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributeValidator(BaseCustomAttributeValidator):
|
class UserStoryCustomAttributeValidator(BaseCustomAttributeValidator):
|
||||||
class Meta(BaseCustomAttributeValidator.Meta):
|
class Meta(BaseCustomAttributeValidator.Meta):
|
||||||
model = models.UserStoryCustomAttribute
|
model = models.UserStoryCustomAttribute
|
||||||
|
@ -121,6 +126,15 @@ class BaseCustomAttributesValuesValidator(ModelValidator):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributesValuesValidator(BaseCustomAttributesValuesValidator):
|
||||||
|
_custom_attribute_model = models.EpicCustomAttribute
|
||||||
|
_container_model = "epics.Epic"
|
||||||
|
_container_field = "epic"
|
||||||
|
|
||||||
|
class Meta(BaseCustomAttributesValuesValidator.Meta):
|
||||||
|
model = models.EpicCustomAttributesValues
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributesValuesValidator(BaseCustomAttributesValuesValidator):
|
class UserStoryCustomAttributesValuesValidator(BaseCustomAttributesValuesValidator):
|
||||||
_custom_attribute_model = models.UserStoryCustomAttribute
|
_custom_attribute_model = models.UserStoryCustomAttribute
|
||||||
_container_model = "userstories.UserStory"
|
_container_model = "userstories.UserStory"
|
||||||
|
|
|
@ -30,8 +30,16 @@ def connect_epics_signals():
|
||||||
dispatch_uid="tags_normalization_epic")
|
dispatch_uid="tags_normalization_epic")
|
||||||
|
|
||||||
|
|
||||||
|
def connect_epics_custom_attributes_signals():
|
||||||
|
from taiga.projects.custom_attributes import signals as custom_attributes_handlers
|
||||||
|
signals.post_save.connect(custom_attributes_handlers.create_custom_attribute_value_when_create_epic,
|
||||||
|
sender=apps.get_model("epics", "Epic"),
|
||||||
|
dispatch_uid="create_custom_attribute_value_when_create_epic")
|
||||||
|
|
||||||
|
|
||||||
def connect_all_epics_signals():
|
def connect_all_epics_signals():
|
||||||
connect_epics_signals()
|
connect_epics_signals()
|
||||||
|
connect_epics_custom_attributes_signals()
|
||||||
|
|
||||||
|
|
||||||
def disconnect_epics_signals():
|
def disconnect_epics_signals():
|
||||||
|
@ -39,8 +47,14 @@ def disconnect_epics_signals():
|
||||||
dispatch_uid="tags_normalization")
|
dispatch_uid="tags_normalization")
|
||||||
|
|
||||||
|
|
||||||
|
def disconnect_epics_custom_attributes_signals():
|
||||||
|
signals.post_save.disconnect(sender=apps.get_model("epics", "Epic"),
|
||||||
|
dispatch_uid="create_custom_attribute_value_when_create_epic")
|
||||||
|
|
||||||
|
|
||||||
def disconnect_all_epics_signals():
|
def disconnect_all_epics_signals():
|
||||||
disconnect_epics_signals()
|
disconnect_epics_signals()
|
||||||
|
disconnect_epics_custom_attributes_signals()
|
||||||
|
|
||||||
|
|
||||||
class EpicsAppConfig(AppConfig):
|
class EpicsAppConfig(AppConfig):
|
||||||
|
|
|
@ -131,7 +131,7 @@ LOOKING_FOR_PEOPLE_PROJECTS_POSITIONS = [0, 1, 2]
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
sd = SampleDataHelper(seed=12345678901)
|
sd = SampleDataHelper(seed=12345678901)
|
||||||
|
|
||||||
@transaction.atomic
|
#@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
# Prevent events emission when sample data is running
|
# Prevent events emission when sample data is running
|
||||||
disconnect_events_signals()
|
disconnect_events_signals()
|
||||||
|
@ -193,6 +193,13 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# added custom attributes
|
# added custom attributes
|
||||||
names = set([self.sd.words(1, 3) for i in range(1, 6)])
|
names = set([self.sd.words(1, 3) for i in range(1, 6)])
|
||||||
|
for name in names:
|
||||||
|
EpicCustomAttribute.objects.create(name=name,
|
||||||
|
description=self.sd.words(3, 12),
|
||||||
|
type=self.sd.choice(TYPES_CHOICES)[0],
|
||||||
|
project=project,
|
||||||
|
order=i)
|
||||||
|
names = set([self.sd.words(1, 3) for i in range(1, 6)])
|
||||||
for name in names:
|
for name in names:
|
||||||
UserStoryCustomAttribute.objects.create(name=name,
|
UserStoryCustomAttribute.objects.create(name=name,
|
||||||
description=self.sd.words(3, 12),
|
description=self.sd.words(3, 12),
|
||||||
|
@ -511,14 +518,13 @@ class Command(BaseCommand):
|
||||||
status=self.sd.db_object_from_queryset(project.epic_statuses.filter(
|
status=self.sd.db_object_from_queryset(project.epic_statuses.filter(
|
||||||
is_closed=False)),
|
is_closed=False)),
|
||||||
tags=self.sd.words(1, 3).split(" "))
|
tags=self.sd.words(1, 3).split(" "))
|
||||||
|
epic.save()
|
||||||
|
|
||||||
# TODO: Epic custom attributes
|
custom_attributes_values = {str(ca.id): self.get_custom_attributes_value(ca.type) for ca
|
||||||
#custom_attributes_values = {str(ca.id): self.get_custom_attributes_value(ca.type) for ca
|
in project.epiccustomattributes.all() if self.sd.boolean()}
|
||||||
# in project.epiccustomattributes.all() if self.sd.boolean()}
|
if custom_attributes_values:
|
||||||
#if custom_attributes_values:
|
epic.custom_attributes_values.attributes_values = custom_attributes_values
|
||||||
# epic.custom_attributes_values.attributes_values = custom_attributes_values
|
epic.custom_attributes_values.save()
|
||||||
# epic.custom_attributes_values.save()
|
|
||||||
|
|
||||||
|
|
||||||
for i in range(self.sd.int(*NUM_ATTACHMENTS)):
|
for i in range(self.sd.int(*NUM_ATTACHMENTS)):
|
||||||
attachment = self.create_attachment(epic, i+1)
|
attachment = self.create_attachment(epic, i+1)
|
||||||
|
|
|
@ -30,6 +30,15 @@ from taiga.permissions.services import calculate_permissions
|
||||||
from taiga.permissions.services import is_project_admin, is_project_owner
|
from taiga.permissions.services import is_project_admin, is_project_owner
|
||||||
|
|
||||||
from . import services
|
from . import services
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
from .custom_attributes.serializers import EpicCustomAttributeSerializer
|
||||||
|
from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
|
||||||
|
from .custom_attributes.serializers import TaskCustomAttributeSerializer
|
||||||
|
from .custom_attributes.serializers import IssueCustomAttributeSerializer
|
||||||
|
from .likes.mixins.serializers import FanResourceSerializerMixin
|
||||||
|
from .mixins.serializers import ValidateDuplicatedNameInProjectMixin
|
||||||
|
>>>>>>> 5f3559d... Epic custom attributes values
|
||||||
from .notifications.choices import NotifyLevel
|
from .notifications.choices import NotifyLevel
|
||||||
|
|
||||||
|
|
||||||
|
@ -352,6 +361,7 @@ class ProjectSerializer(serializers.LightSerializer):
|
||||||
|
|
||||||
|
|
||||||
class ProjectDetailSerializer(ProjectSerializer):
|
class ProjectDetailSerializer(ProjectSerializer):
|
||||||
|
epic_statuses = Field(attr="epic_statuses_attr")
|
||||||
us_statuses = Field(attr="userstory_statuses_attr")
|
us_statuses = Field(attr="userstory_statuses_attr")
|
||||||
points = Field(attr="points_attr")
|
points = Field(attr="points_attr")
|
||||||
task_statuses = Field(attr="task_statuses_attr")
|
task_statuses = Field(attr="task_statuses_attr")
|
||||||
|
@ -359,6 +369,7 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
issue_types = Field(attr="issue_types_attr")
|
issue_types = Field(attr="issue_types_attr")
|
||||||
priorities = Field(attr="priorities_attr")
|
priorities = Field(attr="priorities_attr")
|
||||||
severities = Field(attr="severities_attr")
|
severities = Field(attr="severities_attr")
|
||||||
|
epic_custom_attributes = Field(attr="epic_custom_attributes_attr")
|
||||||
userstory_custom_attributes = Field(attr="userstory_custom_attributes_attr")
|
userstory_custom_attributes = Field(attr="userstory_custom_attributes_attr")
|
||||||
task_custom_attributes = Field(attr="task_custom_attributes_attr")
|
task_custom_attributes = Field(attr="task_custom_attributes_attr")
|
||||||
issue_custom_attributes = Field(attr="issue_custom_attributes_attr")
|
issue_custom_attributes = Field(attr="issue_custom_attributes_attr")
|
||||||
|
@ -385,9 +396,9 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
|
|
||||||
def to_value(self, instance):
|
def to_value(self, instance):
|
||||||
# Name attributes must be translated
|
# Name attributes must be translated
|
||||||
for attr in ["userstory_statuses_attr", "points_attr", "task_statuses_attr",
|
for attr in ["epic_statuses_attr", "userstory_statuses_attr", "points_attr", "task_statuses_attr",
|
||||||
"issue_statuses_attr", "issue_types_attr", "priorities_attr",
|
"issue_statuses_attr", "issue_types_attr", "priorities_attr", "severities_attr",
|
||||||
"severities_attr", "userstory_custom_attributes_attr",
|
"epic_custom_attributes_attr", "userstory_custom_attributes_attr",
|
||||||
"task_custom_attributes_attr", "issue_custom_attributes_attr", "roles_attr"]:
|
"task_custom_attributes_attr", "issue_custom_attributes_attr", "roles_attr"]:
|
||||||
|
|
||||||
assert hasattr(instance, attr), "instance must have a {} attribute".format(attr)
|
assert hasattr(instance, attr), "instance must have a {} attribute".format(attr)
|
||||||
|
@ -430,8 +441,10 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
return len(obj.members_attr)
|
return len(obj.members_attr)
|
||||||
|
|
||||||
def get_is_out_of_owner_limits(self, obj):
|
def get_is_out_of_owner_limits(self, obj):
|
||||||
assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
|
assert (hasattr(obj, "private_projects_same_owner_attr"),
|
||||||
assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
|
"instance must have a private_projects_same_owner_attr attribute"
|
||||||
|
assert (hasattr(obj, "public_projects_same_owner_attr"),
|
||||||
|
"instance must have a public_projects_same_owner_attr attribute"
|
||||||
return services.check_if_project_is_out_of_owner_limits(
|
return services.check_if_project_is_out_of_owner_limits(
|
||||||
obj,
|
obj,
|
||||||
current_memberships=self.get_total_memberships(obj),
|
current_memberships=self.get_total_memberships(obj),
|
||||||
|
@ -440,8 +453,10 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_is_private_extra_info(self, obj):
|
def get_is_private_extra_info(self, obj):
|
||||||
assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
|
assert (hasattr(obj, "private_projects_same_owner_attr"),
|
||||||
assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
|
"instance must have a private_projects_same_owner_attr attribute"
|
||||||
|
assert (hasattr(obj, "public_projects_same_owner_attr"),
|
||||||
|
"instance must have a public_projects_same_owner_attr attribute"
|
||||||
return services.check_if_project_privacity_can_be_changed(
|
return services.check_if_project_privacity_can_be_changed(
|
||||||
obj,
|
obj,
|
||||||
current_memberships=self.get_total_memberships(obj),
|
current_memberships=self.get_total_memberships(obj),
|
||||||
|
@ -466,6 +481,7 @@ class ProjectTemplateSerializer(serializers.LightSerializer):
|
||||||
created_date = Field()
|
created_date = Field()
|
||||||
modified_date = Field()
|
modified_date = Field()
|
||||||
default_owner_role = Field()
|
default_owner_role = Field()
|
||||||
|
is_epics_activated = Field()
|
||||||
is_backlog_activated = Field()
|
is_backlog_activated = Field()
|
||||||
is_kanban_activated = Field()
|
is_kanban_activated = Field()
|
||||||
is_wiki_activated = Field()
|
is_wiki_activated = Field()
|
||||||
|
@ -473,6 +489,7 @@ class ProjectTemplateSerializer(serializers.LightSerializer):
|
||||||
videoconferences = Field()
|
videoconferences = Field()
|
||||||
videoconferences_extra_data = Field()
|
videoconferences_extra_data = Field()
|
||||||
default_options = Field()
|
default_options = Field()
|
||||||
|
epic_statuses = Field()
|
||||||
us_statuses = Field()
|
us_statuses = Field()
|
||||||
points = Field()
|
points = Field()
|
||||||
task_statuses = Field()
|
task_statuses = Field()
|
||||||
|
|
|
@ -277,6 +277,26 @@ def attach_severities(queryset, as_field="severities_attr"):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
def attach_epic_custom_attributes(queryset, as_field="epic_custom_attributes_attr"):
|
||||||
|
"""Attach a json epic custom attributes representation to each object of the queryset.
|
||||||
|
|
||||||
|
:param queryset: A Django projects queryset object.
|
||||||
|
:param as_field: Attach the epic custom attributes as an attribute with this name.
|
||||||
|
|
||||||
|
:return: Queryset object with the additional `as_field` field.
|
||||||
|
"""
|
||||||
|
model = queryset.model
|
||||||
|
sql = """SELECT json_agg(row_to_json(custom_attributes_epiccustomattribute))
|
||||||
|
FROM custom_attributes_epiccustomattribute
|
||||||
|
WHERE
|
||||||
|
custom_attributes_epiccustomattribute.project_id = {tbl}.id
|
||||||
|
"""
|
||||||
|
|
||||||
|
sql = sql.format(tbl=model._meta.db_table)
|
||||||
|
queryset = queryset.extra(select={as_field: sql})
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
def attach_userstory_custom_attributes(queryset, as_field="userstory_custom_attributes_attr"):
|
def attach_userstory_custom_attributes(queryset, as_field="userstory_custom_attributes_attr"):
|
||||||
"""Attach a json userstory custom attributes representation to each object of the queryset.
|
"""Attach a json userstory custom attributes representation to each object of the queryset.
|
||||||
|
|
||||||
|
@ -471,6 +491,7 @@ def attach_extra_info(queryset, user=None):
|
||||||
queryset = attach_issue_types(queryset)
|
queryset = attach_issue_types(queryset)
|
||||||
queryset = attach_priorities(queryset)
|
queryset = attach_priorities(queryset)
|
||||||
queryset = attach_severities(queryset)
|
queryset = attach_severities(queryset)
|
||||||
|
queryset = attach_epic_custom_attributes(queryset)
|
||||||
queryset = attach_userstory_custom_attributes(queryset)
|
queryset = attach_userstory_custom_attributes(queryset)
|
||||||
queryset = attach_task_custom_attributes(queryset)
|
queryset = attach_task_custom_attributes(queryset)
|
||||||
queryset = attach_issue_custom_attributes(queryset)
|
queryset = attach_issue_custom_attributes(queryset)
|
||||||
|
|
|
@ -54,6 +54,7 @@ from taiga.projects.api import ProjectFansViewSet
|
||||||
from taiga.projects.api import ProjectWatchersViewSet
|
from taiga.projects.api import ProjectWatchersViewSet
|
||||||
from taiga.projects.api import MembershipViewSet
|
from taiga.projects.api import MembershipViewSet
|
||||||
from taiga.projects.api import InvitationViewSet
|
from taiga.projects.api import InvitationViewSet
|
||||||
|
from taiga.projects.api import EpicStatusViewSet
|
||||||
from taiga.projects.api import UserStoryStatusViewSet
|
from taiga.projects.api import UserStoryStatusViewSet
|
||||||
from taiga.projects.api import PointsViewSet
|
from taiga.projects.api import PointsViewSet
|
||||||
from taiga.projects.api import TaskStatusViewSet
|
from taiga.projects.api import TaskStatusViewSet
|
||||||
|
@ -69,6 +70,7 @@ router.register(r"projects/(?P<resource_id>\d+)/watchers", ProjectWatchersViewSe
|
||||||
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
|
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
|
||||||
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
router.register(r"memberships", MembershipViewSet, base_name="memberships")
|
||||||
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
||||||
|
router.register(r"epic-statuses", EpicStatusViewSet, base_name="epic-statuses")
|
||||||
router.register(r"userstory-statuses", UserStoryStatusViewSet, base_name="userstory-statuses")
|
router.register(r"userstory-statuses", UserStoryStatusViewSet, base_name="userstory-statuses")
|
||||||
router.register(r"points", PointsViewSet, base_name="points")
|
router.register(r"points", PointsViewSet, base_name="points")
|
||||||
router.register(r"task-statuses", TaskStatusViewSet, base_name="task-statuses")
|
router.register(r"task-statuses", TaskStatusViewSet, base_name="task-statuses")
|
||||||
|
@ -79,13 +81,18 @@ router.register(r"severities",SeverityViewSet , base_name="severities")
|
||||||
|
|
||||||
|
|
||||||
# Custom Attributes
|
# Custom Attributes
|
||||||
|
from taiga.projects.custom_attributes.api import EpicCustomAttributeViewSet
|
||||||
from taiga.projects.custom_attributes.api import UserStoryCustomAttributeViewSet
|
from taiga.projects.custom_attributes.api import UserStoryCustomAttributeViewSet
|
||||||
from taiga.projects.custom_attributes.api import TaskCustomAttributeViewSet
|
from taiga.projects.custom_attributes.api import TaskCustomAttributeViewSet
|
||||||
from taiga.projects.custom_attributes.api import IssueCustomAttributeViewSet
|
from taiga.projects.custom_attributes.api import IssueCustomAttributeViewSet
|
||||||
|
|
||||||
|
from taiga.projects.custom_attributes.api import EpicCustomAttributesValuesViewSet
|
||||||
from taiga.projects.custom_attributes.api import UserStoryCustomAttributesValuesViewSet
|
from taiga.projects.custom_attributes.api import UserStoryCustomAttributesValuesViewSet
|
||||||
from taiga.projects.custom_attributes.api import TaskCustomAttributesValuesViewSet
|
from taiga.projects.custom_attributes.api import TaskCustomAttributesValuesViewSet
|
||||||
from taiga.projects.custom_attributes.api import IssueCustomAttributesValuesViewSet
|
from taiga.projects.custom_attributes.api import IssueCustomAttributesValuesViewSet
|
||||||
|
|
||||||
|
router.register(r"epic-custom-attributes", EpicCustomAttributeViewSet,
|
||||||
|
base_name="epic-custom-attributes")
|
||||||
router.register(r"userstory-custom-attributes", UserStoryCustomAttributeViewSet,
|
router.register(r"userstory-custom-attributes", UserStoryCustomAttributeViewSet,
|
||||||
base_name="userstory-custom-attributes")
|
base_name="userstory-custom-attributes")
|
||||||
router.register(r"task-custom-attributes", TaskCustomAttributeViewSet,
|
router.register(r"task-custom-attributes", TaskCustomAttributeViewSet,
|
||||||
|
@ -93,6 +100,8 @@ router.register(r"task-custom-attributes", TaskCustomAttributeViewSet,
|
||||||
router.register(r"issue-custom-attributes", IssueCustomAttributeViewSet,
|
router.register(r"issue-custom-attributes", IssueCustomAttributeViewSet,
|
||||||
base_name="issue-custom-attributes")
|
base_name="issue-custom-attributes")
|
||||||
|
|
||||||
|
router.register(r"epics/custom-attributes-values", EpicCustomAttributesValuesViewSet,
|
||||||
|
base_name="epic-custom-attributes-values")
|
||||||
router.register(r"userstories/custom-attributes-values", UserStoryCustomAttributesValuesViewSet,
|
router.register(r"userstories/custom-attributes-values", UserStoryCustomAttributesValuesViewSet,
|
||||||
base_name="userstory-custom-attributes-values")
|
base_name="userstory-custom-attributes-values")
|
||||||
router.register(r"tasks/custom-attributes-values", TaskCustomAttributesValuesViewSet,
|
router.register(r"tasks/custom-attributes-values", TaskCustomAttributesValuesViewSet,
|
||||||
|
@ -114,50 +123,76 @@ router.register(r"resolver", ResolverViewSet, base_name="resolver")
|
||||||
|
|
||||||
|
|
||||||
# Attachments
|
# Attachments
|
||||||
|
from taiga.projects.attachments.api import EpicAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import TaskAttachmentViewSet
|
from taiga.projects.attachments.api import TaskAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import WikiAttachmentViewSet
|
from taiga.projects.attachments.api import WikiAttachmentViewSet
|
||||||
|
|
||||||
|
router.register(r"epics/attachments", EpicAttachmentViewSet,
|
||||||
|
base_name="epic-attachments")
|
||||||
router.register(r"userstories/attachments", UserStoryAttachmentViewSet,
|
router.register(r"userstories/attachments", UserStoryAttachmentViewSet,
|
||||||
base_name="userstory-attachments")
|
base_name="userstory-attachments")
|
||||||
router.register(r"tasks/attachments", TaskAttachmentViewSet, base_name="task-attachments")
|
router.register(r"tasks/attachments", TaskAttachmentViewSet,
|
||||||
router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments")
|
base_name="task-attachments")
|
||||||
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
router.register(r"issues/attachments", IssueAttachmentViewSet,
|
||||||
|
base_name="issue-attachments")
|
||||||
|
router.register(r"wiki/attachments", WikiAttachmentViewSet,
|
||||||
|
base_name="wiki-attachments")
|
||||||
|
|
||||||
|
|
||||||
# Project components
|
# Project components
|
||||||
from taiga.projects.milestones.api import MilestoneViewSet
|
from taiga.projects.milestones.api import MilestoneViewSet
|
||||||
from taiga.projects.milestones.api import MilestoneWatchersViewSet
|
from taiga.projects.milestones.api import MilestoneWatchersViewSet
|
||||||
|
|
||||||
from taiga.projects.userstories.api import UserStoryViewSet
|
from taiga.projects.userstories.api import UserStoryViewSet
|
||||||
from taiga.projects.userstories.api import UserStoryVotersViewSet
|
from taiga.projects.userstories.api import UserStoryVotersViewSet
|
||||||
from taiga.projects.userstories.api import UserStoryWatchersViewSet
|
from taiga.projects.userstories.api import UserStoryWatchersViewSet
|
||||||
|
|
||||||
from taiga.projects.tasks.api import TaskViewSet
|
from taiga.projects.tasks.api import TaskViewSet
|
||||||
from taiga.projects.tasks.api import TaskVotersViewSet
|
from taiga.projects.tasks.api import TaskVotersViewSet
|
||||||
from taiga.projects.tasks.api import TaskWatchersViewSet
|
from taiga.projects.tasks.api import TaskWatchersViewSet
|
||||||
|
|
||||||
from taiga.projects.issues.api import IssueViewSet
|
from taiga.projects.issues.api import IssueViewSet
|
||||||
from taiga.projects.issues.api import IssueVotersViewSet
|
from taiga.projects.issues.api import IssueVotersViewSet
|
||||||
from taiga.projects.issues.api import IssueWatchersViewSet
|
from taiga.projects.issues.api import IssueWatchersViewSet
|
||||||
|
|
||||||
from taiga.projects.wiki.api import WikiViewSet
|
from taiga.projects.wiki.api import WikiViewSet
|
||||||
from taiga.projects.wiki.api import WikiLinkViewSet
|
from taiga.projects.wiki.api import WikiLinkViewSet
|
||||||
from taiga.projects.wiki.api import WikiWatchersViewSet
|
from taiga.projects.wiki.api import WikiWatchersViewSet
|
||||||
|
|
||||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
router.register(r"milestones", MilestoneViewSet,
|
||||||
router.register(r"milestones/(?P<resource_id>\d+)/watchers", MilestoneWatchersViewSet, base_name="milestone-watchers")
|
base_name="milestones")
|
||||||
router.register(r"userstories", UserStoryViewSet, base_name="userstories")
|
router.register(r"milestones/(?P<resource_id>\d+)/watchers", MilestoneWatchersViewSet,
|
||||||
router.register(r"userstories/(?P<resource_id>\d+)/voters", UserStoryVotersViewSet, base_name="userstory-voters")
|
base_name="milestone-watchers")
|
||||||
router.register(r"userstories/(?P<resource_id>\d+)/watchers", UserStoryWatchersViewSet, base_name="userstory-watchers")
|
|
||||||
router.register(r"tasks", TaskViewSet, base_name="tasks")
|
|
||||||
router.register(r"tasks/(?P<resource_id>\d+)/voters", TaskVotersViewSet, base_name="task-voters")
|
|
||||||
router.register(r"tasks/(?P<resource_id>\d+)/watchers", TaskWatchersViewSet, base_name="task-watchers")
|
|
||||||
router.register(r"issues", IssueViewSet, base_name="issues")
|
|
||||||
router.register(r"issues/(?P<resource_id>\d+)/voters", IssueVotersViewSet, base_name="issue-voters")
|
|
||||||
router.register(r"issues/(?P<resource_id>\d+)/watchers", IssueWatchersViewSet, base_name="issue-watchers")
|
|
||||||
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
|
||||||
router.register(r"wiki/(?P<resource_id>\d+)/watchers", WikiWatchersViewSet, base_name="wiki-watchers")
|
|
||||||
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
|
|
||||||
|
|
||||||
|
router.register(r"userstories", UserStoryViewSet,
|
||||||
|
base_name="userstories")
|
||||||
|
router.register(r"userstories/(?P<resource_id>\d+)/voters", UserStoryVotersViewSet,
|
||||||
|
base_name="userstory-voters")
|
||||||
|
router.register(r"userstories/(?P<resource_id>\d+)/watchers", UserStoryWatchersViewSet,
|
||||||
|
base_name="userstory-watchers")
|
||||||
|
|
||||||
|
router.register(r"tasks", TaskViewSet,
|
||||||
|
base_name="tasks")
|
||||||
|
router.register(r"tasks/(?P<resource_id>\d+)/voters", TaskVotersViewSet,
|
||||||
|
base_name="task-voters")
|
||||||
|
router.register(r"tasks/(?P<resource_id>\d+)/watchers", TaskWatchersViewSet,
|
||||||
|
base_name="task-watchers")
|
||||||
|
|
||||||
|
router.register(r"issues", IssueViewSet,
|
||||||
|
base_name="issues")
|
||||||
|
router.register(r"issues/(?P<resource_id>\d+)/voters", IssueVotersViewSet,
|
||||||
|
base_name="issue-voters")
|
||||||
|
router.register(r"issues/(?P<resource_id>\d+)/watchers", IssueWatchersViewSet,
|
||||||
|
base_name="issue-watchers")
|
||||||
|
|
||||||
|
router.register(r"wiki", WikiViewSet,
|
||||||
|
base_name="wiki")
|
||||||
|
router.register(r"wiki/(?P<resource_id>\d+)/watchers", WikiWatchersViewSet,
|
||||||
|
base_name="wiki-watchers")
|
||||||
|
router.register(r"wiki-links", WikiLinkViewSet,
|
||||||
|
base_name="wiki-links")
|
||||||
|
|
||||||
|
|
||||||
# History & Components
|
# History & Components
|
||||||
|
@ -223,11 +258,11 @@ router.register(r"exporter", ProjectExporterViewSet, base_name="exporter")
|
||||||
|
|
||||||
# External apps
|
# External apps
|
||||||
from taiga.external_apps.api import Application, ApplicationToken
|
from taiga.external_apps.api import Application, ApplicationToken
|
||||||
|
|
||||||
router.register(r"applications", Application, base_name="applications")
|
router.register(r"applications", Application, base_name="applications")
|
||||||
router.register(r"application-tokens", ApplicationToken, base_name="application-tokens")
|
router.register(r"application-tokens", ApplicationToken, base_name="application-tokens")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Stats
|
# Stats
|
||||||
# - see taiga.stats.routers and taiga.stats.apps
|
# - see taiga.stats.routers and taiga.stats.apps
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ class ProjectTemplateFactory(Factory):
|
||||||
slug = settings.DEFAULT_PROJECT_TEMPLATE
|
slug = settings.DEFAULT_PROJECT_TEMPLATE
|
||||||
description = factory.Sequence(lambda n: "Description {}".format(n))
|
description = factory.Sequence(lambda n: "Description {}".format(n))
|
||||||
|
|
||||||
|
epic_statuses = []
|
||||||
us_statuses = []
|
us_statuses = []
|
||||||
points = []
|
points = []
|
||||||
task_statuses = []
|
task_statuses = []
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
|
||||||
|
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from django.db.transaction import atomic
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from taiga.base.utils import json
|
||||||
|
|
||||||
|
from .. import factories as f
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################
|
||||||
|
# Epic Custom Attributes
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
def test_epic_custom_attribute_duplicate_name_error_on_create(client):
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory()
|
||||||
|
member = f.MembershipFactory(user=custom_attr_1.project.owner,
|
||||||
|
project=custom_attr_1.project,
|
||||||
|
is_admin=True)
|
||||||
|
|
||||||
|
|
||||||
|
url = reverse("epic-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_epic_custom_attribute_duplicate_name_error_on_update(client):
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory()
|
||||||
|
custom_attr_2 = f.EpicCustomAttributeFactory(project=custom_attr_1.project)
|
||||||
|
member = f.MembershipFactory(user=custom_attr_1.project.owner,
|
||||||
|
project=custom_attr_1.project,
|
||||||
|
is_admin=True)
|
||||||
|
|
||||||
|
|
||||||
|
url = reverse("epic-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_epic_custom_attribute_duplicate_name_error_on_move_between_projects(client):
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory()
|
||||||
|
custom_attr_2 = f.EpicCustomAttributeFactory(name=custom_attr_1.name)
|
||||||
|
member = f.MembershipFactory(user=custom_attr_1.project.owner,
|
||||||
|
project=custom_attr_1.project,
|
||||||
|
is_admin=True)
|
||||||
|
f.MembershipFactory(user=custom_attr_1.project.owner,
|
||||||
|
project=custom_attr_2.project,
|
||||||
|
is_admin=True)
|
||||||
|
|
||||||
|
url = reverse("epic-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
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################
|
||||||
|
# Epic Custom Attributes Values
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
def test_epic_custom_attributes_values_when_create_us(client):
|
||||||
|
epic = f.EpicFactory()
|
||||||
|
assert epic.custom_attributes_values.attributes_values == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_epic_custom_attributes_values_update(client):
|
||||||
|
epic = f.EpicFactory()
|
||||||
|
member = f.MembershipFactory(user=epic.project.owner,
|
||||||
|
project=epic.project,
|
||||||
|
is_admin=True)
|
||||||
|
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct1_id = "{}".format(custom_attr_1.id)
|
||||||
|
custom_attr_2 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct2_id = "{}".format(custom_attr_2.id)
|
||||||
|
|
||||||
|
custom_attrs_val = epic.custom_attributes_values
|
||||||
|
|
||||||
|
url = reverse("epic-custom-attributes-values-detail", args=[epic.id])
|
||||||
|
data = {
|
||||||
|
"attributes_values": {
|
||||||
|
ct1_id: "test_1_updated",
|
||||||
|
ct2_id: "test_2_updated"
|
||||||
|
},
|
||||||
|
"version": custom_attrs_val.version
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert epic.custom_attributes_values.attributes_values == {}
|
||||||
|
client.login(member.user)
|
||||||
|
response = client.json.patch(url, json.dumps(data))
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.data["attributes_values"] == data["attributes_values"]
|
||||||
|
epic = epic.__class__.objects.get(id=epic.id)
|
||||||
|
assert epic.custom_attributes_values.attributes_values == data["attributes_values"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_epic_custom_attributes_values_update_with_error_invalid_key(client):
|
||||||
|
epic = f.EpicFactory()
|
||||||
|
member = f.MembershipFactory(user=epic.project.owner,
|
||||||
|
project=epic.project,
|
||||||
|
is_admin=True)
|
||||||
|
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct1_id = "{}".format(custom_attr_1.id)
|
||||||
|
custom_attr_2 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
|
||||||
|
custom_attrs_val = epic.custom_attributes_values
|
||||||
|
|
||||||
|
url = reverse("epic-custom-attributes-values-detail", args=[epic.id])
|
||||||
|
data = {
|
||||||
|
"attributes_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_epic_custom_attributes_values_delete_epic(client):
|
||||||
|
epic = f.EpicFactory()
|
||||||
|
member = f.MembershipFactory(user=epic.project.owner,
|
||||||
|
project=epic.project,
|
||||||
|
is_admin=True)
|
||||||
|
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct1_id = "{}".format(custom_attr_1.id)
|
||||||
|
custom_attr_2 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct2_id = "{}".format(custom_attr_2.id)
|
||||||
|
|
||||||
|
custom_attrs_val = epic.custom_attributes_values
|
||||||
|
|
||||||
|
url = reverse("epics-detail", args=[epic.id])
|
||||||
|
|
||||||
|
client.login(member.user)
|
||||||
|
response = client.json.delete(url)
|
||||||
|
assert response.status_code == 204
|
||||||
|
assert not epic.__class__.objects.filter(id=epic.id).exists()
|
||||||
|
assert not custom_attrs_val.__class__.objects.filter(id=custom_attrs_val.id).exists()
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################
|
||||||
|
# Test tristres triggers :-P
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
def test_trigger_update_epiccustomvalues_afeter_remove_epiccustomattribute(client):
|
||||||
|
epic = f.EpicFactory()
|
||||||
|
member = f.MembershipFactory(user=epic.project.owner,
|
||||||
|
project=epic.project,
|
||||||
|
is_admin=True)
|
||||||
|
custom_attr_1 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct1_id = "{}".format(custom_attr_1.id)
|
||||||
|
custom_attr_2 = f.EpicCustomAttributeFactory(project=epic.project)
|
||||||
|
ct2_id = "{}".format(custom_attr_2.id)
|
||||||
|
|
||||||
|
custom_attrs_val = epic.custom_attributes_values
|
||||||
|
custom_attrs_val.attributes_values = {ct1_id: "test_1", ct2_id: "test_2"}
|
||||||
|
custom_attrs_val.save()
|
||||||
|
|
||||||
|
assert ct1_id in custom_attrs_val.attributes_values.keys()
|
||||||
|
assert ct2_id in custom_attrs_val.attributes_values.keys()
|
||||||
|
|
||||||
|
url = reverse("epic-custom-attributes-detail", kwargs={"pk": custom_attr_2.pk})
|
||||||
|
client.login(member.user)
|
||||||
|
response = client.json.delete(url)
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
custom_attrs_val = custom_attrs_val.__class__.objects.get(id=custom_attrs_val.id)
|
||||||
|
assert not custom_attr_2.__class__.objects.filter(pk=custom_attr_2.pk).exists()
|
||||||
|
assert ct1_id in custom_attrs_val.attributes_values.keys()
|
||||||
|
assert ct2_id not in custom_attrs_val.attributes_values.keys()
|
Loading…
Reference in New Issue