diff --git a/taiga/projects/migrations/0027_auto_20150916_1302.py b/taiga/projects/migrations/0027_auto_20150916_1302.py new file mode 100644 index 00000000..ecdb0f41 --- /dev/null +++ b/taiga/projects/migrations/0027_auto_20150916_1302.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0026_auto_20150911_1237'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='total_milestones', + field=models.IntegerField(verbose_name='total of milestones', null=True, blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='project', + name='total_story_points', + field=models.FloatField(verbose_name='total story points', null=True, blank=True), + preserve_default=True, + ), + ] diff --git a/taiga/projects/models.py b/taiga/projects/models.py index 482d25da..fe89fac8 100644 --- a/taiga/projects/models.py +++ b/taiga/projects/models.py @@ -146,9 +146,9 @@ class Project(ProjectDefaults, TaggedMixin, models.Model): members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="projects", through="Membership", verbose_name=_("members"), through_fields=("project", "user")) - total_milestones = models.IntegerField(default=0, null=False, blank=False, + total_milestones = models.IntegerField(null=True, blank=True, verbose_name=_("total of milestones")) - total_story_points = models.FloatField(default=0, verbose_name=_("total story points")) + total_story_points = models.FloatField(null=True, blank=True, verbose_name=_("total story points")) is_backlog_activated = models.BooleanField(default=True, null=False, blank=True, verbose_name=_("active backlog panel")) diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index 2f3b0ab1..9fad7b7b 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -342,16 +342,6 @@ class ProjectSerializer(WatchersValidator, LikedResourceSerializerMixin, Watched def get_notify_level(self, obj): return getattr(obj, "notify_level", None) - def validate_total_milestones(self, attrs, source): - """ - Check that total_milestones is not null, it's an optional parameter but - not nullable in the model. - """ - value = attrs[source] - if value is None: - raise serializers.ValidationError(_("Total milestones must be major or equal to zero")) - return attrs - class ProjectDetailSerializer(ProjectSerializer): us_statuses = UserStoryStatusSerializer(many=True, required=False) # User Stories diff --git a/taiga/projects/services/stats.py b/taiga/projects/services/stats.py index b7586cb5..88b2a47c 100644 --- a/taiga/projects/services/stats.py +++ b/taiga/projects/services/stats.py @@ -23,6 +23,14 @@ import copy from taiga.projects.history.models import HistoryEntry +def _get_total_story_points(project): + return (project.total_story_points if project.total_story_points is not None else + sum(project.calculated_points["defined"].values())) + +def _get_total_milestones(project): + return (project.total_milestones if project.total_milestones is not None else + project.milestones.count()) + def _get_milestones_stats_for_backlog(project): """ Get collection of stats for each millestone of project. @@ -33,8 +41,12 @@ def _get_milestones_stats_for_backlog(project): current_client_increment = 0 optimal_points_per_sprint = 0 - if project.total_story_points and project.total_milestones: - optimal_points_per_sprint = project.total_story_points / project.total_milestones + + total_story_points = _get_total_story_points(project) + total_milestones = _get_total_milestones(project) + + if total_story_points and total_milestones: + optimal_points_per_sprint = total_story_points / total_milestones future_team_increment = sum(project.future_team_increment.values()) future_client_increment = sum(project.future_client_increment.values()) @@ -50,11 +62,11 @@ def _get_milestones_stats_for_backlog(project): team_increment = 0 client_increment = 0 - for current_milestone in range(0, max(milestones_count, project.total_milestones)): - optimal_points = (project.total_story_points - + for current_milestone in range(0, max(milestones_count, total_milestones)): + optimal_points = (total_story_points - (optimal_points_per_sprint * current_milestone)) - evolution = (project.total_story_points - current_evolution + evolution = (total_story_points - current_evolution if current_evolution is not None else None) if current_milestone < milestones_count: @@ -83,8 +95,8 @@ def _get_milestones_stats_for_backlog(project): } optimal_points -= optimal_points_per_sprint - evolution = (project.total_story_points - current_evolution - if current_evolution is not None and project.total_story_points else None) + evolution = (total_story_points - current_evolution + if current_evolution is not None and total_story_points else None) yield { 'name': _('Project End'), 'optimal': optimal_points, @@ -104,6 +116,7 @@ def _count_status_object(status_obj, counting_storage): counting_storage[status_obj.id]['id'] = status_obj.id counting_storage[status_obj.id]['color'] = status_obj.color + def _count_owned_object(user_obj, counting_storage): if user_obj: if user_obj.id in counting_storage: @@ -126,6 +139,7 @@ def _count_owned_object(user_obj, counting_storage): counting_storage[0]['id'] = 0 counting_storage[0]['color'] = 'black' + def get_stats_for_project_issues(project): project_issues_stats = { 'total_issues': 0, @@ -283,6 +297,7 @@ def _get_closed_tasks_per_member_stats(project): closed_tasks = {p["assigned_to"]: p["count"] for p in closed_tasks} return closed_tasks + def get_member_stats_for_project(project): base_counters = {id: 0 for id in project.members.values_list("id", flat=True)} closed_bugs = base_counters.copy()