Add assigned users to us
Also: * csv export * json export * import validator * history * Email templateremotes/origin/3.4.0rc
parent
2284ec6b6e
commit
94fcbbc55e
|
@ -237,6 +237,7 @@ class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin,
|
||||||
role_points = RolePointsExportSerializer(many=True)
|
role_points = RolePointsExportSerializer(many=True)
|
||||||
owner = UserRelatedField()
|
owner = UserRelatedField()
|
||||||
assigned_to = UserRelatedField()
|
assigned_to = UserRelatedField()
|
||||||
|
assigned_users = MethodField()
|
||||||
status = SlugRelatedField(slug_field="name")
|
status = SlugRelatedField(slug_field="name")
|
||||||
milestone = SlugRelatedField(slug_field="name")
|
milestone = SlugRelatedField(slug_field="name")
|
||||||
modified_date = DateTimeField()
|
modified_date = DateTimeField()
|
||||||
|
@ -273,6 +274,10 @@ class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin,
|
||||||
_userstories_statuses_cache[project.id] = {s.id: s.name for s in project.us_statuses.all()}
|
_userstories_statuses_cache[project.id] = {s.id: s.name for s in project.us_statuses.all()}
|
||||||
return _userstories_statuses_cache[project.id]
|
return _userstories_statuses_cache[project.id]
|
||||||
|
|
||||||
|
def get_assigned_users(self, obj):
|
||||||
|
return [user.email for user in obj.assigned_users.all()]
|
||||||
|
|
||||||
|
|
||||||
class EpicRelatedUserStoryExportSerializer(RelatedExportSerializer):
|
class EpicRelatedUserStoryExportSerializer(RelatedExportSerializer):
|
||||||
user_story = SlugRelatedField(slug_field="ref")
|
user_story = SlugRelatedField(slug_field="ref")
|
||||||
order = Field()
|
order = Field()
|
||||||
|
|
|
@ -159,6 +159,7 @@ class HistorySnapshotField(JSONField):
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class HistoryUserField(JSONField):
|
class HistoryUserField(JSONField):
|
||||||
def from_native(self, data):
|
def from_native(self, data):
|
||||||
if data is None:
|
if data is None:
|
||||||
|
|
|
@ -302,6 +302,7 @@ class UserStoryExportValidator(WatcheableObjectModelValidatorMixin):
|
||||||
role_points = RolePointsExportValidator(many=True, required=False)
|
role_points = RolePointsExportValidator(many=True, required=False)
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
assigned_to = UserRelatedField(required=False)
|
assigned_to = UserRelatedField(required=False)
|
||||||
|
assigned_users = UserRelatedField(many=True, required=False)
|
||||||
status = ProjectRelatedField(slug_field="name")
|
status = ProjectRelatedField(slug_field="name")
|
||||||
milestone = ProjectRelatedField(slug_field="name", required=False)
|
milestone = ProjectRelatedField(slug_field="name", required=False)
|
||||||
modified_date = serializers.DateTimeField(required=False)
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
|
@ -92,11 +92,14 @@ def _common_users_values(diff):
|
||||||
users.update(diff["owner"])
|
users.update(diff["owner"])
|
||||||
if "assigned_to" in diff:
|
if "assigned_to" in diff:
|
||||||
users.update(diff["assigned_to"])
|
users.update(diff["assigned_to"])
|
||||||
|
if "assigned_users" in diff:
|
||||||
|
[users.update(usrs_id) if usrs_id else None for usrs_id in diff["assigned_users"]]
|
||||||
if users:
|
if users:
|
||||||
values["users"] = _get_users_values(users)
|
values["users"] = _get_users_values(users)
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
def project_values(diff):
|
def project_values(diff):
|
||||||
values = _common_users_values(diff)
|
values = _common_users_values(diff)
|
||||||
return values
|
return values
|
||||||
|
@ -344,7 +347,7 @@ def userstory_freezer(us) -> dict:
|
||||||
"subject": us.subject,
|
"subject": us.subject,
|
||||||
"description": us.description,
|
"description": us.description,
|
||||||
"description_html": mdrender(us.project, us.description),
|
"description_html": mdrender(us.project, us.description),
|
||||||
"assigned_to": us.assigned_to_id,
|
"assigned_users": [u.id for u in us.assigned_users.all()],
|
||||||
"milestone": us.milestone_id,
|
"milestone": us.milestone_id,
|
||||||
"client_requirement": us.client_requirement,
|
"client_requirement": us.client_requirement,
|
||||||
"team_requirement": us.team_requirement,
|
"team_requirement": us.team_requirement,
|
||||||
|
|
|
@ -176,6 +176,15 @@ class HistoryEntry(models.Model):
|
||||||
(key, value) = resolve_diff_value(key)
|
(key, value) = resolve_diff_value(key)
|
||||||
elif key in users_keys:
|
elif key in users_keys:
|
||||||
value = [resolve_value("users", x) for x in self.diff[key]]
|
value = [resolve_value("users", x) for x in self.diff[key]]
|
||||||
|
elif key == "assigned_users":
|
||||||
|
diff_in, diff_out = self.diff[key]
|
||||||
|
value_in = None
|
||||||
|
value_out = None
|
||||||
|
if diff_in:
|
||||||
|
value_in = ", ".join([resolve_value("users", x) for x in diff_in])
|
||||||
|
if diff_out:
|
||||||
|
value_out = ", ".join([resolve_value("users", x) for x in diff_out])
|
||||||
|
value = [value_in, value_out]
|
||||||
elif key == "points":
|
elif key == "points":
|
||||||
points = {}
|
points = {}
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,33 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{# ASSIGNED users #}
|
||||||
|
{% elif field_name == "assigned_users" %}
|
||||||
|
<tr>
|
||||||
|
<td valign="middle" rowspan="2" class="update-row-name">
|
||||||
|
<h3>{{ verbose_name(obj_class, field_name) }}</h3>
|
||||||
|
</td>
|
||||||
|
<td valign="top" class="update-row-from">
|
||||||
|
{% if values.0 != None and values.0 != "" %}
|
||||||
|
<span>{{ _("from") }}</span><br>
|
||||||
|
<strong>{{ values.0 }}</strong>
|
||||||
|
{% else %}
|
||||||
|
<span>{{ _("from") }}</span><br>
|
||||||
|
<strong>{{ _("Unassigned") }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td valign="top">
|
||||||
|
{% if values.1 != None and values.1 != "" %}
|
||||||
|
<span>{{ _("to") }}</span><br>
|
||||||
|
<strong>{{ values.1 }}</strong>
|
||||||
|
{% else %}
|
||||||
|
<span>{{ _("to") }}</span><br>
|
||||||
|
<strong>{{ _("Unassigned") }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{# * #}
|
{# * #}
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -67,6 +67,10 @@ class UserStoryAdmin(admin.ModelAdmin):
|
||||||
and getattr(self, 'obj', None)):
|
and getattr(self, 'obj', None)):
|
||||||
kwargs["queryset"] = db_field.related.parent_model.objects.filter(
|
kwargs["queryset"] = db_field.related.parent_model.objects.filter(
|
||||||
memberships__project=self.obj.project)
|
memberships__project=self.obj.project)
|
||||||
|
elif (db_field.name in ["assigned_users"]
|
||||||
|
and getattr(self, 'obj', None)):
|
||||||
|
kwargs["queryset"] = db_field.related_model.objects.filter(
|
||||||
|
memberships__project=self.obj.project)
|
||||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,6 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
|
||||||
include_attachments=include_attachments,
|
include_attachments=include_attachments,
|
||||||
include_tasks=include_tasks,
|
include_tasks=include_tasks,
|
||||||
epic_id=epic_id)
|
epic_id=epic_id)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2018-02-13 10:14
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('userstories', '0014_auto_20160928_0540'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userstory',
|
||||||
|
name='assigned_users',
|
||||||
|
field=models.ManyToManyField(blank=True, default=None, related_name='assigned_userstories', to=settings.AUTH_USER_MODEL, verbose_name='assigned users'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -97,6 +97,9 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, Due
|
||||||
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||||
default=None, related_name="userstories_assigned_to_me",
|
default=None, related_name="userstories_assigned_to_me",
|
||||||
verbose_name=_("assigned to"))
|
verbose_name=_("assigned to"))
|
||||||
|
assigned_users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True,
|
||||||
|
default=None, related_name="assigned_userstories",
|
||||||
|
verbose_name=_("assigned users"))
|
||||||
client_requirement = models.BooleanField(default=False, null=False, blank=True,
|
client_requirement = models.BooleanField(default=False, null=False, blank=True,
|
||||||
verbose_name=_("is client requirement"))
|
verbose_name=_("is client requirement"))
|
||||||
team_requirement = models.BooleanField(default=False, null=False, blank=True,
|
team_requirement = models.BooleanField(default=False, null=False, blank=True,
|
||||||
|
|
|
@ -83,6 +83,22 @@ class UserStoryListSerializer(ProjectExtraInfoSerializerMixin,
|
||||||
epic_order = MethodField()
|
epic_order = MethodField()
|
||||||
tasks = MethodField()
|
tasks = MethodField()
|
||||||
|
|
||||||
|
assigned_users = MethodField()
|
||||||
|
|
||||||
|
# def get_assigned_users(self, obj):
|
||||||
|
# assert hasattr(obj, "assigned_users_attr"), "instance must have a assigned_users_attr attribute"
|
||||||
|
# if not obj.assigned_users_attr:
|
||||||
|
# return []
|
||||||
|
#
|
||||||
|
# return obj.assigned_users_attr
|
||||||
|
|
||||||
|
def get_assigned_users(self, obj):
|
||||||
|
"""Get the assigned of an object.
|
||||||
|
|
||||||
|
:return: User queryset object representing the assigned users
|
||||||
|
"""
|
||||||
|
return [user.id for user in obj.assigned_users.all()]
|
||||||
|
|
||||||
def get_epic_order(self, obj):
|
def get_epic_order(self, obj):
|
||||||
include_epic_order = getattr(obj, "include_epic_order", False)
|
include_epic_order = getattr(obj, "include_epic_order", False)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ from taiga.projects.notifications.utils import attach_watchers_to_queryset
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# Bulk actions
|
# Bulk actions
|
||||||
#####################################################
|
#####################################################
|
||||||
|
@ -46,7 +47,8 @@ def get_userstories_from_bulk(bulk_data, **additional_fields):
|
||||||
"""Convert `bulk_data` into a list of user stories.
|
"""Convert `bulk_data` into a list of user stories.
|
||||||
|
|
||||||
:param bulk_data: List of user stories in bulk format.
|
:param bulk_data: List of user stories in bulk format.
|
||||||
:param additional_fields: Additional fields when instantiating each user story.
|
:param additional_fields: Additional fields when instantiating each user
|
||||||
|
story.
|
||||||
|
|
||||||
:return: List of `UserStory` instances.
|
:return: List of `UserStory` instances.
|
||||||
"""
|
"""
|
||||||
|
@ -54,12 +56,14 @@ def get_userstories_from_bulk(bulk_data, **additional_fields):
|
||||||
for line in text.split_in_lines(bulk_data)]
|
for line in text.split_in_lines(bulk_data)]
|
||||||
|
|
||||||
|
|
||||||
def create_userstories_in_bulk(bulk_data, callback=None, precall=None, **additional_fields):
|
def create_userstories_in_bulk(bulk_data, callback=None, precall=None,
|
||||||
|
**additional_fields):
|
||||||
"""Create user stories from `bulk_data`.
|
"""Create user stories from `bulk_data`.
|
||||||
|
|
||||||
:param bulk_data: List of user stories in bulk format.
|
:param bulk_data: List of user stories in bulk format.
|
||||||
:param callback: Callback to execute after each user story save.
|
:param callback: Callback to execute after each user story save.
|
||||||
:param additional_fields: Additional fields when instantiating each user story.
|
:param additional_fields: Additional fields when instantiating each user
|
||||||
|
story.
|
||||||
|
|
||||||
:return: List of created `Task` instances.
|
:return: List of created `Task` instances.
|
||||||
"""
|
"""
|
||||||
|
@ -76,11 +80,13 @@ def create_userstories_in_bulk(bulk_data, callback=None, precall=None, **additio
|
||||||
return userstories
|
return userstories
|
||||||
|
|
||||||
|
|
||||||
def update_userstories_order_in_bulk(bulk_data: list, field: str, project: object,
|
def update_userstories_order_in_bulk(bulk_data: list, field: str,
|
||||||
status: object=None, milestone: object=None):
|
project: object,
|
||||||
|
status: object = None,
|
||||||
|
milestone: object = None):
|
||||||
"""
|
"""
|
||||||
Updates the order of the userstories specified adding the extra updates needed
|
Updates the order of the userstories specified adding the extra updates
|
||||||
to keep consistency.
|
needed to keep consistency.
|
||||||
`bulk_data` should be a list of dicts with the following format:
|
`bulk_data` should be a list of dicts with the following format:
|
||||||
`field` is the order field used
|
`field` is the order field used
|
||||||
|
|
||||||
|
@ -106,8 +112,8 @@ def update_userstories_order_in_bulk(bulk_data: list, field: str, project: objec
|
||||||
|
|
||||||
def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
|
def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
|
||||||
"""
|
"""
|
||||||
Update the milestone and the milestone order of some user stories adding the
|
Update the milestone and the milestone order of some user stories adding
|
||||||
extra orders needed to keep consistency.
|
the extra orders needed to keep consistency.
|
||||||
`bulk_data` should be a list of dicts with the following format:
|
`bulk_data` should be a list of dicts with the following format:
|
||||||
[{'us_id': <value>, 'order': <value>}, ...]
|
[{'us_id': <value>, 'order': <value>}, ...]
|
||||||
"""
|
"""
|
||||||
|
@ -116,7 +122,8 @@ def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
|
||||||
new_us_orders = {}
|
new_us_orders = {}
|
||||||
for e in bulk_data:
|
for e in bulk_data:
|
||||||
new_us_orders[e["us_id"]] = e["order"]
|
new_us_orders[e["us_id"]] = e["order"]
|
||||||
# The base orders where we apply the new orders must containg all the values
|
# The base orders where we apply the new orders must containg all
|
||||||
|
# the values
|
||||||
us_orders[e["us_id"]] = e["order"]
|
us_orders[e["us_id"]] = e["order"]
|
||||||
|
|
||||||
apply_order_updates(us_orders, new_us_orders)
|
apply_order_updates(us_orders, new_us_orders)
|
||||||
|
@ -128,11 +135,14 @@ def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
|
||||||
content_type="userstories.userstory",
|
content_type="userstories.userstory",
|
||||||
projectid=milestone.project.pk)
|
projectid=milestone.project.pk)
|
||||||
|
|
||||||
db.update_attr_in_bulk_for_ids(us_milestones, "milestone_id", model=models.UserStory)
|
db.update_attr_in_bulk_for_ids(us_milestones, "milestone_id",
|
||||||
|
model=models.UserStory)
|
||||||
db.update_attr_in_bulk_for_ids(us_orders, "sprint_order", models.UserStory)
|
db.update_attr_in_bulk_for_ids(us_orders, "sprint_order", models.UserStory)
|
||||||
|
|
||||||
# Updating the milestone for the tasks
|
# Updating the milestone for the tasks
|
||||||
Task.objects.filter(user_story_id__in=[e["us_id"] for e in bulk_data]).update(milestone=milestone)
|
Task.objects.filter(
|
||||||
|
user_story_id__in=[e["us_id"] for e in bulk_data]).update(
|
||||||
|
milestone=milestone)
|
||||||
|
|
||||||
return us_orders
|
return us_orders
|
||||||
|
|
||||||
|
@ -157,7 +167,8 @@ def calculate_userstory_is_closed(user_story):
|
||||||
if user_story.tasks.count() == 0:
|
if user_story.tasks.count() == 0:
|
||||||
return user_story.status is not None and user_story.status.is_closed
|
return user_story.status is not None and user_story.status.is_closed
|
||||||
|
|
||||||
if all([task.status is not None and task.status.is_closed for task in user_story.tasks.all()]):
|
if all([task.status is not None and task.status.is_closed for task in
|
||||||
|
user_story.tasks.all()]):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -183,9 +194,12 @@ def open_userstory(us):
|
||||||
|
|
||||||
def userstories_to_csv(project, queryset):
|
def userstories_to_csv(project, queryset):
|
||||||
csv_data = io.StringIO()
|
csv_data = io.StringIO()
|
||||||
fieldnames = ["ref", "subject", "description", "sprint", "sprint_estimated_start",
|
fieldnames = ["ref", "subject", "description", "sprint",
|
||||||
"sprint_estimated_finish", "owner", "owner_full_name", "assigned_to",
|
"sprint_estimated_start",
|
||||||
"assigned_to_full_name", "status", "is_closed"]
|
"sprint_estimated_finish", "owner", "owner_full_name",
|
||||||
|
"assigned_to",
|
||||||
|
"assigned_to_full_name", "assigned_users",
|
||||||
|
"assigned_users_full_name", "status", "is_closed"]
|
||||||
|
|
||||||
roles = project.roles.filter(computable=True).order_by('slug')
|
roles = project.roles.filter(computable=True).order_by('slug')
|
||||||
for role in roles:
|
for role in roles:
|
||||||
|
@ -227,12 +241,21 @@ def userstories_to_csv(project, queryset):
|
||||||
"subject": us.subject,
|
"subject": us.subject,
|
||||||
"description": us.description,
|
"description": us.description,
|
||||||
"sprint": us.milestone.name if us.milestone else None,
|
"sprint": us.milestone.name if us.milestone else None,
|
||||||
"sprint_estimated_start": us.milestone.estimated_start if us.milestone else None,
|
"sprint_estimated_start": us.milestone.estimated_start if
|
||||||
"sprint_estimated_finish": us.milestone.estimated_finish if us.milestone else None,
|
us.milestone else None,
|
||||||
|
"sprint_estimated_finish": us.milestone.estimated_finish if
|
||||||
|
us.milestone else None,
|
||||||
"owner": us.owner.username if us.owner else None,
|
"owner": us.owner.username if us.owner else None,
|
||||||
"owner_full_name": us.owner.get_full_name() if us.owner else None,
|
"owner_full_name": us.owner.get_full_name() if us.owner else None,
|
||||||
"assigned_to": us.assigned_to.username if us.assigned_to else None,
|
"assigned_to": us.assigned_to.username if us.assigned_to else None,
|
||||||
"assigned_to_full_name": us.assigned_to.get_full_name() if us.assigned_to else None,
|
"assigned_to_full_name": us.assigned_to.get_full_name() if
|
||||||
|
us.assigned_to else None,
|
||||||
|
"assigned_users": ",".join(
|
||||||
|
[assigned_user.username for assigned_user in
|
||||||
|
us.assigned_users.all()]),
|
||||||
|
"assigned_users_full_name": ",".join(
|
||||||
|
[assigned_user.get_full_name() for assigned_user in
|
||||||
|
us.assigned_users.all()]),
|
||||||
"status": us.status.name if us.status else None,
|
"status": us.status.name if us.status else None,
|
||||||
"is_closed": us.is_closed,
|
"is_closed": us.is_closed,
|
||||||
"backlog_order": us.backlog_order,
|
"backlog_order": us.backlog_order,
|
||||||
|
@ -244,7 +267,8 @@ def userstories_to_csv(project, queryset):
|
||||||
"client_requirement": us.client_requirement,
|
"client_requirement": us.client_requirement,
|
||||||
"team_requirement": us.team_requirement,
|
"team_requirement": us.team_requirement,
|
||||||
"attachments": us.attachments.count(),
|
"attachments": us.attachments.count(),
|
||||||
"generated_from_issue": us.generated_from_issue.ref if us.generated_from_issue else None,
|
"generated_from_issue": us.generated_from_issue.ref if
|
||||||
|
us.generated_from_issue else None,
|
||||||
"external_reference": us.external_reference,
|
"external_reference": us.external_reference,
|
||||||
"tasks": ",".join([str(task.ref) for task in us.tasks.all()]),
|
"tasks": ",".join([str(task.ref) for task in us.tasks.all()]),
|
||||||
"tags": ",".join(us.tags or []),
|
"tags": ",".join(us.tags or []),
|
||||||
|
@ -254,14 +278,17 @@ def userstories_to_csv(project, queryset):
|
||||||
"due_date_reason": us.due_date_reason,
|
"due_date_reason": us.due_date_reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
us_role_points_by_role_id = {us_rp.role.id: us_rp.points.value for us_rp in us.role_points.all()}
|
us_role_points_by_role_id = {us_rp.role.id: us_rp.points.value for
|
||||||
|
us_rp in us.role_points.all()}
|
||||||
for role in roles:
|
for role in roles:
|
||||||
row["{}-points".format(role.slug)] = us_role_points_by_role_id.get(role.id, 0)
|
row["{}-points".format(role.slug)] = \
|
||||||
|
us_role_points_by_role_id.get(role.id, 0)
|
||||||
|
|
||||||
row['total-points'] = us.get_total_points()
|
row['total-points'] = us.get_total_points()
|
||||||
|
|
||||||
for custom_attr in custom_attrs:
|
for custom_attr in custom_attrs:
|
||||||
value = us.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)
|
value = us.custom_attributes_values.attributes_values.get(
|
||||||
|
str(custom_attr.id), None)
|
||||||
row[custom_attr.name] = value
|
row[custom_attr.name] = value
|
||||||
|
|
||||||
writer.writerow(row)
|
writer.writerow(row)
|
||||||
|
|
|
@ -157,6 +157,7 @@ def attach_extra_info(queryset, user=None, include_attachments=False, include_ta
|
||||||
queryset = attach_total_points(queryset)
|
queryset = attach_total_points(queryset)
|
||||||
queryset = attach_role_points(queryset)
|
queryset = attach_role_points(queryset)
|
||||||
queryset = attach_epics(queryset)
|
queryset = attach_epics(queryset)
|
||||||
|
# queryset = attach_assigned_users(queryset)
|
||||||
|
|
||||||
if include_attachments:
|
if include_attachments:
|
||||||
queryset = attach_basic_attachments(queryset)
|
queryset = attach_basic_attachments(queryset)
|
||||||
|
@ -177,3 +178,22 @@ def attach_extra_info(queryset, user=None, include_attachments=False, include_ta
|
||||||
queryset = attach_is_watcher_to_queryset(queryset, user)
|
queryset = attach_is_watcher_to_queryset(queryset, user)
|
||||||
queryset = attach_total_comments_to_queryset(queryset)
|
queryset = attach_total_comments_to_queryset(queryset)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
def attach_assigned_users(queryset, as_field="assigned_users_attr"):
|
||||||
|
"""Attach assigned users as json column to each object of the queryset.
|
||||||
|
|
||||||
|
:param queryset: A Django user stories queryset object.
|
||||||
|
:param as_field: Attach assigned as an attribute with this name.
|
||||||
|
|
||||||
|
:return: Queryset object with the additional `as_field` field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = queryset.model
|
||||||
|
sql = """SELECT "userstories_userstory_assigned_users"."user_id" AS "user_id"
|
||||||
|
FROM "userstories_userstory_assigned_users"
|
||||||
|
WHERE "userstories_userstory_assigned_users"."userstory_id" = {tbl}.id"""
|
||||||
|
|
||||||
|
sql = sql.format(tbl=model._meta.db_table)
|
||||||
|
queryset = queryset.extra(select={as_field: sql})
|
||||||
|
return queryset
|
|
@ -69,6 +69,44 @@ def test_update_userstories_order_in_bulk():
|
||||||
models.UserStory)
|
models.UserStory)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_userstory_with_assign_to(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
user_watcher = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
f.MembershipFactory.create(project=project, user=user, is_admin=True)
|
||||||
|
f.MembershipFactory.create(project=project, user=user_watcher,
|
||||||
|
is_admin=True)
|
||||||
|
url = reverse("userstories-list")
|
||||||
|
|
||||||
|
data = {"subject": "Test user story", "project": project.id,
|
||||||
|
"assigned_to": user.id}
|
||||||
|
client.login(user)
|
||||||
|
response = client.json.post(url, json.dumps(data))
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert response.data["assigned_to"] == user.id
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_userstory_with_assigned_users(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
user_watcher = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
f.MembershipFactory.create(project=project, user=user, is_admin=True)
|
||||||
|
f.MembershipFactory.create(project=project, user=user_watcher,
|
||||||
|
is_admin=True)
|
||||||
|
url = reverse("userstories-list")
|
||||||
|
|
||||||
|
data = {"subject": "Test user story", "project": project.id,
|
||||||
|
"assigned_users": [user.id, user_watcher.id]}
|
||||||
|
client.login(user)
|
||||||
|
json_data = json.dumps(data)
|
||||||
|
|
||||||
|
response = client.json.post(url, json_data)
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert response.data["assigned_users"] == [user.id, user_watcher.id]
|
||||||
|
|
||||||
|
|
||||||
def test_create_userstory_with_watchers(client):
|
def test_create_userstory_with_watchers(client):
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
user_watcher = f.UserFactory.create()
|
user_watcher = f.UserFactory.create()
|
||||||
|
|
Loading…
Reference in New Issue