예제 #1
0
 def setUp(self):
     self.super()
     self.env = self._testenv.get_trac_environment()
     self.team = TeamModelManager(self.env).create(name="Team#1")
     tmm = TeamMemberModelManager(self.env)
     self.members = (tmm.create(name="Member#1", team=self.team),
                     tmm.create(name="Member#2", team=self.team,
                                      default_capacity=[4,4,4,0,0,0,0]),
                     tmm.create(name="Member#3", team=self.team,
                                      default_capacity=[0,0,0,2,2,0,0]))
     for m in self.members:
         m.save()
예제 #2
0
 def __init__(self):
     # Create an instance of Sprint Manager
     self.sm = SprintModelManager(self.env)
     self.tm = TeamModelManager(self.env)
예제 #3
0
class SprintAdminPanel(AgiloAdminPanel):
    """
    Administration panel for sprints.
    """

    _type = 'sprints'
    _label = ('Sprints', _('Sprints'))

    def __init__(self):
        # Create an instance of Sprint Manager
        self.sm = SprintModelManager(self.env)
        self.tm = TeamModelManager(self.env)

    def _parse_args(self, req):
        start = req.args.get('start')
        if start:
            start = datefmt.parse_date(start, tzinfo=req.tz)
        end = req.args.get('end')
        if end:
            end = datefmt.parse_date(end, tzinfo=req.tz)
        duration = req.args.get('duration')
        if duration:
            try:
                duration = int(duration)
            except ValueError:
                duration = None

        return start, end, duration

    def detail_save_view(self, req, cat, page, name):
        sprint = self.sm.get(name=name)
        if not sprint or not sprint.exists:
            return req.redirect(req.href.admin(cat, page))

        new_name = req.args.get('name')
        # if necessary, rename sprint
        if sprint.name != new_name:
            new_sprint = self.sm.get(name=new_name)
            if new_sprint and new_sprint.exists:
                add_warning(
                    req,
                    'A sprint with this name already exists - cannot rename.')
                return self.detail_view(req, cat, page, name)
            if '/' in new_name:
                add_warning(req, 'Please don\'t use "/" in a sprint name.')
                return self.detail_view(req, cat, page, name)

        sprint.name = new_name
        sprint.description = req.args.get('description')
        sprint.milestone = req.args.get('milestone')

        team_name = req.args.get('team')
        team = None
        if team_name:
            team = self.tm.get(name=team_name)
            if not team or not team.exists:
                add_warning(req,
                            u"Invalid team name, that team doesn't exist.")
                return self.detail_view(req, cat, page, name)
        sprint.team = team

        start, end, duration = self._parse_args(req)

        if start and start != sprint.start:
            if (end and duration) or (not end and not duration):
                add_warning(req, 'Please enter an end date OR a duration.')
                return self.detail_view(req, cat, page, name)
            sprint.start = start

        if end and end != sprint.end:
            if (start and duration) or (not start and not duration):
                add_warning(req, 'Please enter a start date OR a duration.')
                return self.detail_view(req, cat, page, name)
            sprint.end = end

        if duration and duration != sprint.duration:
            if (start and end) or (not start and not end):
                add_warning(req, 'Please enter an start date OR an end date.')
                return self.detail_view(req, cat, page, name)
            sprint.duration = duration

        self.sm.save(sprint)
        req.redirect(req.href.admin(cat, page))

    def detail_view(self, req, cat, page, name):
        sprint = self.sm.get(name=name)
        if not sprint or not sprint.exists:
            return req.redirect(req.href.admin(cat, page))

        data = {
            'view': 'detail',
            'sprint': sprint,
            'teams': self.tm.select(),
            'format_datetime': datefmt.format_datetime,
            'date_hint': datefmt.get_date_format_hint(),
            'datetime_hint': datefmt.get_datetime_format_hint(),
            'milestones': [m.name for m in Milestone.select(self.env)],
        }
        data.update(req.args)
        add_script(req, 'common/js/wikitoolbar.js')
        return 'agilo_admin_sprint.html', data

    def list_view(self, req, cat, page):
        data = {
            'view': 'list',
            'sprints': self.sm.select(),
            'format_datetime': datefmt.format_datetime,
            'date_hint': datefmt.get_date_format_hint(),
            'datetime_hint': datefmt.get_datetime_format_hint(),
            'milestones': [m.name for m in Milestone.select(self.env)],
        }
        data.update(req.args)
        return 'agilo_admin_sprint.html', data

    def list_save_view(self, req, cat, page):
        name = req.args.get('name')
        start, end, duration = self._parse_args(req)

        if req.args.get('add'):
            if not name:
                add_warning(req, 'Please enter a sprint name.')
                return self.list_view(req, cat, page)
            if '/' in name:
                add_warning(req, 'Please do not use "/" in a sprint name.')
                return self.list_view(req, cat, page)

            sprint = self.sm.create(name=name, save=False)
            if not sprint:
                # sprint already exists, redirect to it
                req.redirect(req.href.admin(cat, page, name))

            if not start and not end and not duration:
                add_warning(req, 'Not enough data to set a sprint.')
                return self.list_view(req, cat, page)

            if start:
                if (end and duration) or (not end and not duration):
                    add_warning(req, 'Please enter an end date OR a duration.')
                    return self.list_view(req, cat, page)
                sprint.start = start

            if end:
                if (start and duration) or (not start and not duration):
                    add_warning(req,
                                'Please enter an start date OR a duration.')
                    return self.list_view(req, cat, page)
                sprint.end = end

            if duration:
                if (start and end) or (not start and not end):
                    add_warning(req,
                                'Please enter an start date OR an end date.')
                    return self.list_view(req, cat, page)
                sprint.duration = duration

            sprint.milestone = req.args.get('milestone')
            self.sm.save(sprint)

        # Remove components
        if req.args.get('remove'):
            sel = req.args.get('sel')
            if not sel:
                raise TracError(_('No sprint selected'))
            if not isinstance(sel, list):
                sel = [sel]
            for name in sel:
                # TODO: relocate not closed ticket to another Sprint
                sprint = self.sm.get(name=name)
                if sprint:
                    self.sm.delete(sprint)

        req.redirect(req.href.admin(cat, page))
예제 #4
0
파일: team_test.py 프로젝트: nagyist/agilo
 def setUp(self):
     self.super()
     self.tm = TeamModelManager(self.env)
     self.tmm = TeamMemberModelManager(self.env)
     self.controller = TeamController(self.env)
예제 #5
0
파일: team_test.py 프로젝트: nagyist/agilo
class TestTeam(AgiloTestCase):
    """Tests for the team model class Team"""
    
    def setUp(self):
        self.super()
        self.tm = TeamModelManager(self.env)
        self.tmm = TeamMemberModelManager(self.env)
        self.controller = TeamController(self.env)
    
    def set_hours_for_day_on_team_member(self, hours, day, team_member):
        calendar = team_member.calendar
        calendar.set_hours_for_day(hours, day)
        calendar.save()
    
    def team_with_no_member(self):
        team = self.tm.create(name="Team#1")
        return team

    def team_with_one_member(self):
        team = self.team_with_no_member()
        member = self._add_member_to_team('Member#1', team)
        return team, member
    
    def team_with_two_members(self):
        team, member1 = self.team_with_one_member()
        member2 = self._add_member_to_team('Member#2', team)
        return team, member1, member2
    
    def _add_member_to_team(self, name, team):
        member = self.tmm.create(name=name, team=team)
        # just to make sure we have  a capacity, 1h/1h for ease of calculating test data
        member.capacity = [9] * 7
        member.save()
        return member
    
    
    def testCapacityHours(self):
        """Test the get_capacity_hours() method"""
        test_team = self.tm.create(name="Team#1")
        self.assert_true(test_team.exists)
        test_members = (self.tmm.create(name="Member#1", team=test_team),
                        self.tmm.create(name="Member#2", team=test_team, 
                                              default_capacity=[4,4,4,0,0,0,0]),
                        self.tmm.create(name="Member#3", team=test_team, 
                                              default_capacity=[0,0,0,2,2,0,0]))
        for tm in test_members:
            self.assert_not_none(tm.team)
            self.assert_equals(test_team, tm.team)
        
        # test the default constructor
        start_date = parse_date("2008-09-08T08:00:00")
        test_sprint =  self.teh.create_sprint(name="TestSprint", start=start_date, 
                                              duration=10)
        
        # test save and restore
        for member in test_members:
            self.assert_true(self.tmm.save(member))
        
        test_sprint.team = test_team
        test_sprint.save()
        
        weekly_hours = (5 * 6) + (4 + 4 + 4) + (2 + 2)
        self.assert_equals(weekly_hours, test_team.capacity().default_hours_of_capacity_per_week())
        
        sprint_hours = 2 * weekly_hours
        actual = test_team.capacity().hourly_capacities_in_sprint(test_sprint)
        capacity = sum(map(lambda each: each.capacity, actual))
        self.assert_almost_equals(sprint_hours, capacity, max_delta=.01)
    
    def testVelocityForSprint(self):
        """Tests the set velocity for a sprint as a team metric"""
        test_team = self.tm.create(name="Team#1")
        self.assert_true(test_team.save())
        self.tmm.create(name="Member#1", team=test_team).save()
        self.tmm.create(name="Member#2", team=test_team).save()
        self.tmm.create(name="Member#3", team=test_team).save()
        sprint = self.teh.create_sprint("TestSprint", team=test_team)
        us1 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '5',
                                                             Key.SPRINT: sprint.name})
        us2 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '8',
                                                             Key.SPRINT: sprint.name})
        us3 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '13',
                                                             Key.SPRINT: sprint.name})
        # we have to use the TeamController here
        cmd_store = TeamController.StoreTeamVelocityCommand(self.env,
                                                            sprint=sprint,
                                                            team=test_team,
                                                            estimated=True)
        self.assert_equals(26, self.controller.process_command(cmd_store))
        # Now close a story and check the actual velocity
        cmd_get = TeamController.GetStoredTeamVelocityCommand(self.env,
                                                              sprint=sprint.name,
                                                              team=test_team,
                                                              estimated=True)
        us3[Key.STATUS] = Status.CLOSED
        us3.save_changes('tester', 'closed US3')
        self.assert_equals(26, self.controller.process_command(cmd_get))
        cmd_store.estimated = False
        self.assert_equals(13, self.controller.process_command(cmd_store))
        cmd_get.estimated = False
        self.assert_equals(13, self.controller.process_command(cmd_get))
    
    def test_team_commitment(self):
        """Tests store and retrieval of the team commitment of a sprint"""
        test_team = self.tm.create(name="Team#1")
        sprint = self.teh.create_sprint("TestSprint", team=test_team)
        self.assert_true(test_team.exists)
        # Set the initial USP/RT ratio to 2
        tm = TeamMetrics(self.env, sprint, test_team)
        tm[Key.RT_USP_RATIO] = 2
        tm.save()
        
        tm1 = self.tmm.create(name="Member#1", team=test_team)
        self.tmm.create(name="Member#2", team=test_team)
        self.tmm.create(name="Member#3", team=test_team)
        us1 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '5',
                                                             Key.SPRINT: sprint.name})
        us2 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '8',
                                                             Key.SPRINT: sprint.name})
        t1 = self.teh.create_ticket(Type.TASK, props={Key.REMAINING_TIME: '12',
                                                      Key.OWNER: tm1.name,
                                                      Key.SPRINT: sprint.name})
        # This task is not explicitly planned for the sprint, but because it is
        # linked should be calculated
        t2 = self.teh.create_ticket(Type.TASK, props={Key.REMAINING_TIME: '8',
                                                      Key.OWNER: tm1.name})
        # Make sure there is a remaining time entry on the first day of the sprint
        RemainingTime(self.env, t1).set_remaining_time(t1[Key.REMAINING_TIME], 
                                                       sprint.start)
        RemainingTime(self.env, t2).set_remaining_time(t2[Key.REMAINING_TIME], 
                                                       sprint.start)
        us1.link_to(t1)
        us1.link_to(t2)
        us2 = self.teh.load_ticket(ticket=us2)
        self.assert_equals(Type.USER_STORY, us2.get_type())
        self.assert_not_none(us2._calculated)
        # check the estimated remaining time for us2
        self.assert_equals(8 * 2, us2[Key.ESTIMATED_REMAINING_TIME])
        
        cmd_class = TeamController.CalculateAndStoreTeamCommitmentCommand
        cmd_store_commitment = cmd_class(self.env, sprint=sprint, team=test_team)
        commitment = TeamController(self.env).process_command(cmd_store_commitment)
        self.assert_equals(12 + 8 * 2, commitment)
        cmd_get_commitment = TeamController.GetTeamCommitmentCommand(self.env,
                                                                     sprint=sprint,
                                                                     team=test_team)
        self.assert_equals(commitment, 
                         self.controller.process_command(cmd_get_commitment))
    
    def test_get_team_metrics_command_returns_none_if_no_team_given(self):
        sprint_without_team = self.teh.create_sprint('FooSprint')
        cmd = TeamController.GetTeamCommitmentCommand(self.env, sprint=sprint_without_team)
        commitment = TeamController(self.env).process_command(cmd)
        self.assert_none(commitment)
    
    def test_can_return_hourly_capacity_on_specific_day(self):
        team, member1, member2 = self.team_with_two_members()
        
        capacities = team.capacity(member1.timezone()).hourly_capacities_for_day(today())
        self.assert_length(10, capacities)
        for capacity in capacities[:-1]:
            self.assert_equals(2, capacity.capacity)
    
    def test_last_entry_of_hourly_capacity_is_zero(self):
        team, member1, member2 = self.team_with_two_members()
        
        capacities = team.capacity(member1.timezone()).hourly_capacities_for_day(today())
        self.assert_equals(0, capacities[-1].capacity)
        
    
    def test_can_return_empty_list_if_no_capacity_is_there(self):
        team, member1, member2 = self.team_with_two_members()
        self.set_hours_for_day_on_team_member(0, today(), member1)
        self.set_hours_for_day_on_team_member(0, today(), member2)
        
        capacities = team.capacity(member1.timezone()).hourly_capacities_for_day(today())
        self.assert_length(0, capacities)
    
    def test_can_combine_different_timezones(self):
        team, member1, member2 = self.team_with_two_members()
        set_user_attribute_in_session(self.env, 'tz', 'GMT', member1.name)
        set_user_attribute_in_session(self.env, 'tz', 'GMT +1:00', member2.name)
        
        capacities = team.capacity(member1.timezone()).hourly_capacities_for_day(today())
        self.assert_length(11, capacities)
        self.assert_equals(1, capacities[0].capacity)
        self.assert_equals(1, capacities[-2].capacity)
    
    def test_can_cut_off_times_that_would_be_on_next_day(self):
        team, member1, member2 = self.team_with_two_members()
        set_user_attribute_in_session(self.env, 'tz', 'GMT', member1.name)
        set_user_attribute_in_session(self.env, 'tz', 'GMT -8:00', member2.name)
        viewer_timezone = member1.timezone()
        self.set_hours_for_day_on_team_member(0, yesterday(viewer_timezone), member2)
        # 15:00 at his place is 23:00 here, so two values should be lost
        
        day = today(tz=member1.timezone())
        capacities = team.capacity(viewer_timezone).hourly_capacities_for_day(day)
        self.assert_length(16, capacities)
        self.assert_equals(1, capacities[0].capacity)
        self.assert_equals(1, capacities[-2].capacity)
        last_hour_of_member1 = datetime.combine(day, time(23, tzinfo=member1.timezone()))
        self.assert_equals(last_hour_of_member1, capacities[-2].when)
    
    def test_cuts_off_times_that_would_be_on_previous_day(self):
        team, member1, member2 = self.team_with_two_members()
        set_user_attribute_in_session(self.env, 'tz', 'GMT', member1.name)
        set_user_attribute_in_session(self.env, 'tz', 'GMT +11:00', member2.name)
        
        viewer_timezone = utc
        # don't want to get values from tomorrow
        self.set_hours_for_day_on_team_member(0, tomorrow(viewer_timezone), member2)
        # 11:00 at his place is 0:00 here, so two values should be lost
        
        capacities = team.capacity(viewer_timezone).hourly_capacities_for_day(today())
        self.assert_length(17, capacities)
        self.assert_equals(1, capacities[0].capacity)
        self.assert_equals(1, capacities[-2].capacity)
        start_of_day_for_member1 = midnight(now(tz=viewer_timezone))
        self.assert_equals(start_of_day_for_member1, capacities[0].when)
    
    def test_cuts_off_times_that_would_be_on_previous_day_for_the_viewer(self):
        team, member1 = self.team_with_one_member()
        set_user_attribute_in_session(self.env, 'tz', 'GMT +11:00', member1.name)
        
        viewer_timezone = utc
        # don't want to get values from tomorrow
        self.set_hours_for_day_on_team_member(0, tomorrow(viewer_timezone), member1)
        # 11:00 at his place is 0:00 here, so two values should be lost
        
        capacities = team.capacity(viewer_timezone).hourly_capacities_for_day(today())
        self.assert_length(8, capacities)
        self.assert_equals(1, capacities[0].capacity)
        self.assert_equals(1, capacities[-2].capacity)
        start_of_day_for_member1 = midnight(now(tz=viewer_timezone))
        self.assert_equals(start_of_day_for_member1, capacities[0].when)
    
    def test_includes_times_from_previous_day_that_get_shifted_to_today_through_the_timezone_difference(self):
        team, member1, member2 = self.team_with_two_members()
        set_user_attribute_in_session(self.env, 'tz', 'GMT', member1.name)
        set_user_attribute_in_session(self.env, 'tz', 'GMT -7:00', member2.name)
        
        day = today(tz=member1.timezone())
        yesterday = day - timedelta(days=1)
        self.set_hours_for_day_on_team_member(9, yesterday, member2)
        # member2s last hour of yesterday should happen on today for member1
        capacities = team.capacity(member1.timezone()).hourly_capacities_for_day(day)
        member1_midnight = datetime.combine(day, time(0, tzinfo=member1.timezone()))
        self.assert_equals(member1_midnight, capacities[0].when)
        self.assert_equals(1, capacities[0].capacity)
    
    def test_includes_times_from_next_day_that_get_shifted_to_today_through_the_timezone_difference(self):
        team, member1, member2 = self.team_with_two_members()
        set_user_attribute_in_session(self.env, 'tz', 'GMT', member1.name)
        set_user_attribute_in_session(self.env, 'tz', 'GMT +10:00', member2.name)
        
        day = today(tz=member1.timezone())
        tomorrow = day + timedelta(days=1)
        self.set_hours_for_day_on_team_member(9, tomorrow, member2)
        # member2s first hour of tomorrow should happen on today for member1
        capacities = team.capacity(member1.timezone()).hourly_capacities_for_day(day)
        member1_last_hour = datetime.combine(day, time(23, tzinfo=member1.timezone()))
        self.assert_equals(member1_last_hour, capacities[-2].when)
        self.assert_equals(1, capacities[-2].capacity)
    
    def test_team_with_capacity_on_every_day_has_no_empty_days(self):
        team, member1 = self.team_with_one_member()
        member1.capacity = [1] * 7
        member1.save()
        start = now() - timedelta(days=7)
        end = now()
        self.assert_length(0, team.capacity().days_without_capacity_in_interval(start, end))
    
    def _create_team_with_weekends_off(self):
        team, member1 = self.team_with_one_member()
        member1.capacity = [1] * 5 + [0, 0]
        member1.save()
        return team
    
    def test_team_not_working_on_weekends_has_no_capacity_on_weekends(self):
        team = self._create_team_with_weekends_off()
        # exactly one week so we're sure that this interval covers two non-working days
        start = now() - timedelta(days=7)
        end = now()
        days_without_capacity = team.capacity().days_without_capacity_in_interval(start, end)
        self.assert_length(2, days_without_capacity)
        self.assert_equals(0, days_without_capacity[0].hour)
        self.assert_equals(0, days_without_capacity[0].minute)
    
    def test_days_without_capacity_respect_given_timestamp(self):
        team = self._create_team_with_weekends_off()
        end = now(utc)
        start = end - timedelta(days=7)
        bangkok_tz = get_timezone('GMT +7:00')
        days_without_capacity = team.capacity(bangkok_tz).days_without_capacity_in_interval(start, end)
        self.assert_equals(bangkok_tz, days_without_capacity[0].tzinfo)
    
    def test_days_without_capacity_include_last_day(self):
        team = self._create_team_with_weekends_off()
        end = datetime(2010, 5, 23, 3, 0, 0, tzinfo=localtz) # sunday
        start = end - timedelta(days=3)
        days_without_capacity = team.capacity().days_without_capacity_in_interval(start, end)
        self.assert_length(2, days_without_capacity)
    
    def _set_default_capacity_for_member(self, default_capacity, member):
        member.capacity = [default_capacity] * 7
        member.save()
    
    def test_can_return_capacity_series_for_interval(self):
        team, member1 = self.team_with_one_member()
        self._set_default_capacity_for_member(1, member1)
        # tomorrow -> date -> 0:00 on date -> removes everything on that day
        self.assert_length(2*9 + 2, team.capacity().hourly_capacities_in_interval(yesterday(), tomorrow()))
    
    def test_return_capacity_information_until_end_of_sprint_even_if_last_values_are_zero(self):
        self.teh.disable_sprint_date_normalization()
        team, member = self.team_with_one_member()
        self._set_default_capacity_for_member(0, member)
        sprint = self.teh.create_sprint('Foo Sprint')
        sprint.start = midnight(yesterday())
        sprint.end = midnight(day_after_tomorrow())
        
        capacities = team.capacity().hourly_capacities_in_sprint(sprint)
        # +1 because the last item is from 22:00-23:00
        self.assert_length(24*3+1, capacities)
    
    def test_can_sum_remaining_capacities_in_sprint(self):
        self.teh.disable_sprint_date_normalization()
        team, member = self.team_with_one_member()
        self._set_default_capacity_for_member(9*2, member)
        
        sprint =  self.teh.create_sprint(name="SprintWithContingent", team=team,
                                         start=midnight(yesterday()), 
                                         end=midnight(tomorrow()))
        summed = team.capacity().summed_hourly_capacities_in_sprint(sprint)
        # 2*9 (no. working hours), +8 (additional hours after zero), +1 (00:00-01:00 on the last day)
        self.assert_length(9*2+8+1, summed)
        self.assert_equals(2*(9*2), summed[0].capacity)
        self.assert_equals(0, summed[-1].capacity)
    
    def test_capacity_can_take_timezone_parameter(self):
        team, member = self.team_with_one_member()
        self._set_default_capacity_for_member(0, member)
        set_user_attribute_in_session(self.env, 'tz', 'GMT', member.name)
        self.set_hours_for_day_on_team_member(9, today(member.timezone()), member)
        
        viewer_timezone = get_timezone('GMT -12:00')
        capacitator = team.capacity(viewer_timezone)
        # Need to take the member timezone for start and end, to make sure that we really cut off all
        # workhours he has on the previous day - even though they would be on the current day when
        # viewed from the viewers timezone.
        hourly_capacities = capacitator.hourly_capacities_in_interval(today(tz=member.timezone()), tomorrow(tz=member.timezone()))
        self.assert_length(7, hourly_capacities)
    
    def test_contingents_are_removed_from_capacity(self):
        team, member = self.team_with_one_member()
        start = parse_date("2008-09-08T08:00:00")
        sprint =  self.teh.create_sprint(name="SprintWithContingent", start=start, duration=10, team=team)
        self.teh.add_contingent_to_sprint('Contingent', 100, sprint)
        self._set_default_capacity_for_member(9, member)
        
        capacities = team.capacity().hourly_capacities_in_sprint(sprint)
        capacity = sum(map(lambda each: each.capacity, capacities))
        self.assert_almost_equals(108 - 100, capacity, max_delta=.01)


    def test_will_contain_second_team_member_after_invalidating(self):
        team, member = self.team_with_one_member()
        self.assert_contains(member, team.members)
        member2 = self._add_member_to_team("member2", team)
        self.assert_not_contains(member2, team.members)
        team.invalidate_team_member_cache()
        self.assert_contains(member2, team.members)

    def test_can_invalidate_empty_cache(self):
        team = self.team_with_no_member()
        team.invalidate_team_member_cache()
        self.assert_length(0, team.members)
예제 #6
0
 def __init__(self, *args, **kwargs):
     super(AgiloPreferences, self).__init__(*args, **kwargs)
     self.tmm = TeamModelManager(self.env)
     self.tmmm = TeamMemberModelManager(self.env)
예제 #7
0
class AgiloPreferences(Component):
    
    implements(IPreferencePanelProvider)
    
    def __init__(self, *args, **kwargs):
        super(AgiloPreferences, self).__init__(*args, **kwargs)
        self.tmm = TeamModelManager(self.env)
        self.tmmm = TeamMemberModelManager(self.env)
    
    #=============================================================================
    # IPreferencePanelProvider methods
    #=============================================================================
    def get_preference_panels(self, req):
        """Return a list of available preference panels.
        
        The items returned by this function must be tuple of the form
        `(panel, label)`.
        """
        if req.authname is not None and req.authname != 'anonymous' and \
                Role.TEAM_MEMBER in req.perm:
            yield ('team', _('Team'))

    def render_preference_panel(self, req, panel):
        """Process a request for a preference panel. This builds the
        Panel for the team preferences
        """
        if req.method == 'POST':
            self._do_save(req)
            req.redirect(req.href.prefs(panel or None))
       
        # Build the team_member object
        team_member = None
        if req.authname not in [None, 'anonymous'] and \
                Role.TEAM_MEMBER in req.perm:
            name = req.authname
            team_member = self.tmmm.get(name=name)
            if team_member == None:
                team_member = self.tmmm.create(name=name)
        
        # Build the calendar for the current month
        calendars = list()
        ac = AgiloCalendar(day=datetime.today())
        for cal in range(2):
            calendars.append(team_member.calendar.get_hours_for_interval(ac.get_first_day(), 
                                                                         ac.get_last_day()))
            ac = ac.next_month()
            
        return 'agilo_prefs_%s.html' % (panel or 'general'), {
            'settings': {'session': req.session, 'session_id': req.session.sid},
            'teams': self.tmm.select(),
            'team_member': team_member,
            'calendars': calendars,
        }
    
    def _do_save(self, req):
        """Saves the parameters into the object"""
        # Get the Team Member
        team_member = self.tmmm.get(name=req.authname)
        cal = team_member.calendar
        save_calendar = False
        for fieldname in sorted(req.args):
            value = req.args.get(fieldname)
            if (value is not None) and (team_member is not None):
                if fieldname == 'team':
                    team = self.tmm.get(name=value)
                    team_member.team = team
                elif fieldname.startswith('ts_'):
                    if fieldname.startswith('ts_%s_' % team_member.name):
                        ts_member, day = fieldname.rsplit('_', 1)
                        try:
                            cal.set_hours_for_day(float(value), d_ordinal=day)
                        except ValueError:
                            cal.set_hours_for_day(0.0, d_ordinal=day)
                        if not save_calendar:
                            save_calendar = True
                    elif hasattr(team_member, fieldname):
                        try:
                            if float(value) != getattr(team_member, fieldname):
                                setattr(team_member, fieldname, float(value))
                        except ValueError:
                            setattr(team_member, fieldname, 0.0)
        team_member.save()
        # The member's capacity may have changed so we have to invalidate the
        # burndown
        ChartGenerator(self.env).invalidate_cache()
        if save_calendar:
            cal.save()
예제 #8
0
파일: admin.py 프로젝트: djangsters/agilo
 def __init__(self):
     # Create an instance of Sprint Manager
     self.sm = SprintModelManager(self.env)
     self.tm = TeamModelManager(self.env)
예제 #9
0
파일: admin.py 프로젝트: djangsters/agilo
class SprintAdminPanel(AgiloAdminPanel):
    """
    Administration panel for sprints.
    """
    
    _type = 'sprints'
    _label = ('Sprints', _('Sprints'))

    def __init__(self):
        # Create an instance of Sprint Manager
        self.sm = SprintModelManager(self.env)
        self.tm = TeamModelManager(self.env)

    def _parse_args(self, req):
        start = req.args.get('start')
        if start:
            start = datefmt.parse_date(start, tzinfo=req.tz)
        end = req.args.get('end')
        if end:
            end = datefmt.parse_date(end, tzinfo=req.tz)
        duration = req.args.get('duration')
        if duration:
            try:
                duration = int(duration)
            except ValueError:
                duration = None
                
        return start, end, duration

    def detail_save_view(self, req, cat, page, name):
        sprint = self.sm.get(name=name)
        if not sprint or not sprint.exists:
            return req.redirect(req.href.admin(cat, page))

        new_name = req.args.get('name')
        # if necessary, rename sprint
        if sprint.name != new_name:
            new_sprint = self.sm.get(name=new_name)
            if new_sprint and new_sprint.exists:
                add_warning(req, 'A sprint with this name already exists - cannot rename.')
                return self.detail_view(req, cat, page, name)
            if '/' in new_name:
                add_warning(req, 'Please don\'t use "/" in a sprint name.')
                return self.detail_view(req, cat, page, name)
        
        sprint.name = new_name
        sprint.description = req.args.get('description')
        sprint.milestone = req.args.get('milestone')
        
        team_name = req.args.get('team')
        team = None
        if team_name:
            team = self.tm.get(name=team_name)
            if not team or not team.exists:
                add_warning(req, u"Invalid team name, that team doesn't exist.")
                return self.detail_view(req, cat, page, name)
        sprint.team = team
        
        start, end, duration = self._parse_args(req)
        
        if start and start != sprint.start:
            if (end and duration) or (not end and not duration):
                add_warning(req, 'Please enter an end date OR a duration.')
                return self.detail_view(req, cat, page, name)
            sprint.start = start
            
        if end and end != sprint.end:
            if (start and duration) or (not start and not duration):
                add_warning(req, 'Please enter a start date OR a duration.')
                return self.detail_view(req, cat, page, name)
            sprint.end = end
        
        if duration and duration != sprint.duration:
            if (start and end) or (not start and not end):
                add_warning(req, 'Please enter an start date OR an end date.')
                return self.detail_view(req, cat, page, name)
            sprint.duration = duration
            
        self.sm.save(sprint)
        req.redirect(req.href.admin(cat, page))

    def detail_view(self, req, cat, page, name):
        sprint = self.sm.get(name=name)
        if not sprint or not sprint.exists:
            return req.redirect(req.href.admin(cat, page))

        data = {
            'view': 'detail',
            'sprint': sprint,
            'teams': self.tm.select(),
            'format_datetime': datefmt.format_datetime,
            'date_hint': datefmt.get_date_format_hint(),
            'datetime_hint': datefmt.get_datetime_format_hint(),
            'milestones': [m.name for m in Milestone.select(self.env)],
        }
        data.update(req.args)
        add_script(req, 'common/js/wikitoolbar.js')
        return 'agilo_admin_sprint.html', data
    
    def list_view(self, req, cat, page):
        data = {
            'view': 'list',
            'sprints': self.sm.select(),
            'format_datetime' : datefmt.format_datetime,
            'date_hint' : datefmt.get_date_format_hint(),
            'datetime_hint' : datefmt.get_datetime_format_hint(),
            'milestones' : [m.name for m in Milestone.select(self.env)],
        }
        data.update(req.args)
        return 'agilo_admin_sprint.html', data

    def list_save_view(self, req, cat, page):
        name = req.args.get('name')
        start, end, duration = self._parse_args(req)

        if req.args.get('add'):
            if not name:
                add_warning(req, 'Please enter a sprint name.')
                return self.list_view(req, cat, page)
            if '/' in name:
                add_warning(req, 'Please do not use "/" in a sprint name.')
                return self.list_view(req, cat, page)

            sprint = self.sm.create(name=name, save=False)
            if not sprint:
                # sprint already exists, redirect to it
                req.redirect(req.href.admin(cat, page, name))

            if not start and not end and not duration:
                add_warning(req, 'Not enough data to set a sprint.')
                return self.list_view(req, cat, page)
                
            if start:
                if (end and duration) or (not end and not duration):
                    add_warning(req, 'Please enter an end date OR a duration.')
                    return self.list_view(req, cat, page)
                sprint.start = start
                
            if end:
                if (start and duration) or (not start and not duration):
                    add_warning(req, 'Please enter an start date OR a duration.')
                    return self.list_view(req, cat, page)
                sprint.end = end
            
            if duration:
                if (start and end) or (not start and not end):
                    add_warning(req, 'Please enter an start date OR an end date.')
                    return self.list_view(req, cat, page)
                sprint.duration = duration

            sprint.milestone = req.args.get('milestone')
            self.sm.save(sprint)

        # Remove components
        if req.args.get('remove'):
            sel = req.args.get('sel')
            if not sel:
                raise TracError(_('No sprint selected'))
            if not isinstance(sel, list):
                sel = [sel]
            for name in sel:
                # TODO: relocate not closed ticket to another Sprint
                sprint = self.sm.get(name=name)
                if sprint:
                    self.sm.delete(sprint)

        req.redirect(req.href.admin(cat, page))