launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #07010
[Merge] lp:~salgado/launchpad/person-upcoming-work-view into lp:launchpad
Guilherme Salgado has proposed merging lp:~salgado/launchpad/person-upcoming-work-view into lp:launchpad with lp:~linaro-infrastructure/launchpad/upcoming-work-progress-bars as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~salgado/launchpad/person-upcoming-work-view/+merge/100878
This branch just shuffles code around so that the new +upcomingwork view works for people as well. There's no link to it anywhere, though, as we need to check with Dan/Huw where it should go. Even when we add a link to it, it will be behind a feature flag just like the one for teams is.
--
https://code.launchpad.net/~salgado/launchpad/person-upcoming-work-view/+merge/100878
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~salgado/launchpad/person-upcoming-work-view into lp:launchpad.
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2012-04-03 15:19:44 +0000
+++ lib/lp/registry/browser/configure.zcml 2012-04-04 20:45:04 +0000
@@ -1085,11 +1085,11 @@
template="../templates/team-mugshots.pt"
class="lp.registry.browser.team.TeamMugshotView"/>
<browser:page
- for="lp.registry.interfaces.person.ITeam"
- class="lp.registry.browser.team.TeamUpcomingWorkView"
+ for="lp.registry.interfaces.person.IPerson"
+ class="lp.registry.browser.person.PersonUpcomingWorkView"
permission="zope.Public"
name="+upcomingwork"
- template="../templates/team-upcomingwork.pt"/>
+ template="../templates/person-upcomingwork.pt"/>
<browser:page
for="lp.registry.interfaces.person.ITeam"
class="lp.registry.browser.team.TeamIndexView"
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2012-03-02 07:53:53 +0000
+++ lib/lp/registry/browser/person.py 2012-04-04 20:45:04 +0000
@@ -47,6 +47,7 @@
'PersonSpecWorkloadTableView',
'PersonSpecWorkloadView',
'PersonSpecsMenu',
+ 'PersonUpcomingWorkView',
'PersonView',
'PersonVouchersView',
'PPANavigationMenuMixIn',
@@ -63,7 +64,10 @@
import cgi
-from datetime import datetime
+from datetime import (
+ datetime,
+ timedelta,
+ )
import itertools
from itertools import chain
from operator import (
@@ -129,6 +133,7 @@
from lp.app.browser.stringformatter import FormattersAPI
from lp.app.browser.tales import (
DateTimeFormatterAPI,
+ format_link,
PersonFormatterAPI,
)
from lp.app.errors import (
@@ -144,7 +149,10 @@
LaunchpadRadioWidgetWithDescription,
)
from lp.blueprints.browser.specificationtarget import HasSpecificationsView
-from lp.blueprints.enums import SpecificationFilter
+from lp.blueprints.enums import (
+ SpecificationFilter,
+ SpecificationWorkItemStatus,
+ )
from lp.bugs.interfaces.bugtask import (
BugTaskSearchParams,
BugTaskStatus,
@@ -4423,3 +4431,289 @@
def __call__(self):
"""Render `Person` as XHTML using the webservice."""
return PersonFormatterAPI(self.person).link(None)
+
+
+class PersonUpcomingWorkView(LaunchpadView):
+ """This view displays work items and bugtasks that are due within 60 days
+ and are assigned to a person (or participants of of a team).
+ """
+
+ # We'll show bugs and work items targeted to milestones with a due date up
+ # to DAYS from now.
+ DAYS = 180
+
+ def initialize(self):
+ super(PersonUpcomingWorkView, self).initialize()
+ self.workitem_counts = {}
+ self.bugtask_counts = {}
+ self.milestones_per_date = {}
+ self.progress_per_date = {}
+ for date, containers in self.work_item_containers:
+ total_items = 0
+ total_done = 0
+ milestones = set()
+ self.bugtask_counts[date] = 0
+ self.workitem_counts[date] = 0
+ for container in containers:
+ total_items += len(container.items)
+ total_done += len(container.done_items)
+ if isinstance(container, AggregatedBugsContainer):
+ self.bugtask_counts[date] += len(container.items)
+ else:
+ self.workitem_counts[date] += len(container.items)
+ for item in container.items:
+ milestones.add(item.milestone)
+ self.milestones_per_date[date] = sorted(
+ milestones, key=attrgetter('displayname'))
+ self.progress_per_date[date] = '{0:.0f}'.format(
+ 100.0 * total_done / float(total_items))
+
+ @property
+ def label(self):
+ return self.page_title
+
+ @property
+ def page_title(self):
+ return "Upcoming work for %s" % self.context.displayname
+
+ @cachedproperty
+ def work_item_containers(self):
+ cutoff_date = datetime.today().date() + timedelta(days=self.DAYS)
+ result = getWorkItemsDueBefore(self.context, cutoff_date, self.user)
+ return sorted(result.items(), key=itemgetter(0))
+
+
+class WorkItemContainer:
+ """A container of work items, assigned to a person (or a team's
+ participatns), whose milestone is due on a certain date.
+ """
+
+ def __init__(self):
+ self._items = []
+
+ @property
+ def html_link(self):
+ raise NotImplementedError("Must be implemented in subclasses")
+
+ @property
+ def priority_title(self):
+ raise NotImplementedError("Must be implemented in subclasses")
+
+ @property
+ def target_link(self):
+ raise NotImplementedError("Must be implemented in subclasses")
+
+ @property
+ def assignee_link(self):
+ raise NotImplementedError("Must be implemented in subclasses")
+
+ @property
+ def items(self):
+ raise NotImplementedError("Must be implemented in subclasses")
+
+ @property
+ def done_items(self):
+ return [item for item in self._items if item.is_complete]
+
+ @property
+ def percent_done(self):
+ return '{0:.0f}'.format(
+ 100.0 * len(self.done_items) / len(self._items))
+
+ def append(self, item):
+ self._items.append(item)
+
+
+class SpecWorkItemContainer(WorkItemContainer):
+ """A container of SpecificationWorkItems wrapped with GenericWorkItem."""
+
+ def __init__(self, spec):
+ super(SpecWorkItemContainer, self).__init__()
+ self.spec = spec
+ self.priority = spec.priority
+ self.target = spec.target
+ self.assignee = spec.assignee
+
+ @property
+ def html_link(self):
+ return format_link(self.spec)
+
+ @property
+ def priority_title(self):
+ return self.priority.title
+
+ @property
+ def target_link(self):
+ return format_link(self.target)
+
+ @property
+ def assignee_link(self):
+ if self.assignee is None:
+ return 'Nobody'
+ return format_link(self.assignee)
+
+ @property
+ def items(self):
+ # Sort the work items by status only because they all have the same
+ # priority.
+ def sort_key(item):
+ status_order = {
+ SpecificationWorkItemStatus.POSTPONED: 5,
+ SpecificationWorkItemStatus.DONE: 4,
+ SpecificationWorkItemStatus.INPROGRESS: 3,
+ SpecificationWorkItemStatus.TODO: 2,
+ SpecificationWorkItemStatus.BLOCKED: 1,
+ }
+ return status_order[item.status]
+ return sorted(self._items, key=sort_key)
+
+
+class AggregatedBugsContainer(WorkItemContainer):
+ """A container of BugTasks wrapped with GenericWorkItem."""
+
+ @property
+ def html_link(self):
+ return 'Bugs targeted to a milestone on this date'
+
+ @property
+ def assignee_link(self):
+ return 'N/A'
+
+ @property
+ def target_link(self):
+ return 'N/A'
+
+ @property
+ def priority_title(self):
+ return 'N/A'
+
+ @property
+ def items(self):
+ def sort_key(item):
+ return (item.status.value, item.priority.value)
+ # Sort by (status, priority) in reverse order because the biggest the
+ # status/priority the more interesting it is to us.
+ return sorted(self._items, key=sort_key, reverse=True)
+
+
+class GenericWorkItem:
+ """A generic piece of work; either a BugTask or a SpecificationWorkItem.
+
+ This class wraps a BugTask or a SpecificationWorkItem to provide a
+ common API so that the template doesn't have to worry about what kind of
+ work item it's dealing with.
+ """
+
+ def __init__(self, assignee, status, priority, target, title,
+ bugtask=None, work_item=None):
+ self.assignee = assignee
+ self.status = status
+ self.priority = priority
+ self.target = target
+ self.title = title
+ self._bugtask = bugtask
+ self._work_item = work_item
+
+ @classmethod
+ def from_bugtask(cls, bugtask):
+ return cls(
+ bugtask.assignee, bugtask.status, bugtask.importance,
+ bugtask.target, bugtask.bug.description, bugtask=bugtask)
+
+ @classmethod
+ def from_workitem(cls, work_item):
+ assignee = work_item.assignee
+ if assignee is None:
+ assignee = work_item.specification.assignee
+ return cls(
+ assignee, work_item.status, work_item.specification.priority,
+ work_item.specification.target, work_item.title,
+ work_item=work_item)
+
+ @property
+ def display_title(self):
+ if self._work_item is not None:
+ return FormattersAPI(self.title).shorten(120)
+ else:
+ return format_link(self._bugtask)
+
+ @property
+ def milestone(self):
+ milestone = self.actual_workitem.milestone
+ if milestone is None:
+ assert self._work_item is not None, (
+ "BugTaks without a milestone must not be here.")
+ milestone = self._work_item.specification.milestone
+ return milestone
+
+ @property
+ def actual_workitem(self):
+ """Return the actual work item that we are wrapping.
+
+ This may be either an IBugTask or an ISpecificationWorkItem.
+ """
+ if self._work_item is not None:
+ return self._work_item
+ else:
+ return self._bugtask
+
+ @property
+ def is_complete(self):
+ return self.actual_workitem.is_complete
+
+
+def getWorkItemsDueBefore(person, cutoff_date, user):
+ """Return a dict mapping dates to lists of WorkItemContainers.
+
+ This is a grouping, by milestone due date, of all work items
+ (SpecificationWorkItems/BugTasks) assigned to this person (or any of its
+ participants, in case it's a team).
+
+ Only work items whose milestone have a due date between today and the
+ given cut-off date are included in the results.
+ """
+ workitems = person.getAssignedSpecificationWorkItemsDueBefore(cutoff_date)
+ # For every specification that has work items in the list above, create
+ # one SpecWorkItemContainer holding the work items from that spec that are
+ # targeted to the same milestone and assigned to this person (or its
+ # participants, in case it's a team).
+ containers_by_date = {}
+ containers_by_spec = {}
+ for workitem in workitems:
+ spec = workitem.specification
+ milestone = workitem.milestone
+ if milestone is None:
+ milestone = spec.milestone
+ if milestone.dateexpected not in containers_by_date:
+ containers_by_date[milestone.dateexpected] = []
+ container = containers_by_spec.get(spec)
+ if container is None:
+ container = SpecWorkItemContainer(spec)
+ containers_by_spec[spec] = container
+ containers_by_date[milestone.dateexpected].append(container)
+ container.append(GenericWorkItem.from_workitem(workitem))
+
+ # Sort our containers by priority.
+ for date in containers_by_date:
+ containers_by_date[date].sort(
+ key=attrgetter('priority'), reverse=True)
+
+ bugtasks = person.getAssignedBugTasksDueBefore(cutoff_date, user)
+ bug_containers_by_date = {}
+ # For every milestone due date, create an AggregatedBugsContainer with all
+ # the bugtasks targeted to a milestone on that date and assigned to
+ # this person (or its participants, in case it's a team).
+ for task in bugtasks:
+ dateexpected = task.milestone.dateexpected
+ container = bug_containers_by_date.get(dateexpected)
+ if container is None:
+ container = AggregatedBugsContainer()
+ bug_containers_by_date[dateexpected] = container
+ # Also append our new container to the dictionary we're going
+ # to return.
+ if dateexpected not in containers_by_date:
+ containers_by_date[dateexpected] = []
+ containers_by_date[dateexpected].append(container)
+ container.append(GenericWorkItem.from_bugtask(task))
+
+ return containers_by_date
=== modified file 'lib/lp/registry/browser/team.py'
--- lib/lp/registry/browser/team.py 2012-04-04 20:45:04 +0000
+++ lib/lp/registry/browser/team.py 2012-04-04 20:45:04 +0000
@@ -28,7 +28,6 @@
'TeamOverviewNavigationMenu',
'TeamPrivacyAdapter',
'TeamReassignmentView',
- 'TeamUpcomingWorkView',
]
@@ -38,10 +37,6 @@
timedelta,
)
import math
-from operator import (
- attrgetter,
- itemgetter,
- )
from urllib import unquote
from lazr.restful.interface import copy_field
@@ -83,11 +78,7 @@
custom_widget,
LaunchpadFormView,
)
-from lp.app.browser.stringformatter import FormattersAPI
-from lp.app.browser.tales import (
- format_link,
- PersonFormatterAPI,
- )
+from lp.app.browser.tales import PersonFormatterAPI
from lp.app.errors import UnexpectedFormData
from lp.app.validators import LaunchpadValidationError
from lp.app.validators.validation import validate_new_team_email
@@ -98,7 +89,6 @@
)
from lp.app.widgets.owner import HiddenUserWidget
from lp.app.widgets.popup import PersonPickerWidget
-from lp.blueprints.enums import SpecificationWorkItemStatus
from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
from lp.registry.browser.branding import BrandingChangeView
from lp.registry.browser.mailinglists import enabled_with_active_mailing_list
@@ -2157,288 +2147,3 @@
batch_nav = BatchNavigator(
self.context.allmembers, self.request, size=self.batch_size)
return batch_nav
-
-
-class TeamUpcomingWorkView(LaunchpadView):
- """This view displays work items and bugtasks that are due within 60 days
- and are assigned to members of a team.
- """
-
- # We'll show bugs and work items targeted to milestones with a due date up
- # to DAYS from now.
- DAYS = 180
-
- def initialize(self):
- super(TeamUpcomingWorkView, self).initialize()
- self.workitem_counts = {}
- self.bugtask_counts = {}
- self.milestones_per_date = {}
- self.progress_per_date = {}
- for date, containers in self.work_item_containers:
- total_items = 0
- total_done = 0
- milestones = set()
- self.bugtask_counts[date] = 0
- self.workitem_counts[date] = 0
- for container in containers:
- total_items += len(container.items)
- total_done += len(container.done_items)
- if isinstance(container, AggregatedBugsContainer):
- self.bugtask_counts[date] += len(container.items)
- else:
- self.workitem_counts[date] += len(container.items)
- for item in container.items:
- milestones.add(item.milestone)
- self.milestones_per_date[date] = sorted(
- milestones, key=attrgetter('displayname'))
- self.progress_per_date[date] = '{0:.0f}'.format(
- 100.0 * total_done / float(total_items))
-
- @property
- def label(self):
- return self.page_title
-
- @property
- def page_title(self):
- return "Upcoming work for %s" % self.context.displayname
-
- @cachedproperty
- def work_item_containers(self):
- cutoff_date = datetime.today().date() + timedelta(days=self.DAYS)
- result = getWorkItemsDueBefore(self.context, cutoff_date, self.user)
- return sorted(result.items(), key=itemgetter(0))
-
-
-class WorkItemContainer:
- """A container of work items, assigned to members of a team, whose
- milestone is due on a certain date.
- """
-
- def __init__(self):
- self._items = []
-
- @property
- def html_link(self):
- raise NotImplementedError("Must be implemented in subclasses")
-
- @property
- def priority_title(self):
- raise NotImplementedError("Must be implemented in subclasses")
-
- @property
- def target_link(self):
- raise NotImplementedError("Must be implemented in subclasses")
-
- @property
- def assignee_link(self):
- raise NotImplementedError("Must be implemented in subclasses")
-
- @property
- def items(self):
- raise NotImplementedError("Must be implemented in subclasses")
-
- @property
- def done_items(self):
- return [item for item in self._items if item.is_complete]
-
- @property
- def percent_done(self):
- return '{0:.0f}'.format(
- 100.0 * len(self.done_items) / len(self._items))
-
- def append(self, item):
- self._items.append(item)
-
-
-class SpecWorkItemContainer(WorkItemContainer):
- """A container of SpecificationWorkItems wrapped with GenericWorkItem."""
-
- def __init__(self, spec):
- super(SpecWorkItemContainer, self).__init__()
- self.spec = spec
- self.priority = spec.priority
- self.target = spec.target
- self.assignee = spec.assignee
-
- @property
- def html_link(self):
- return format_link(self.spec)
-
- @property
- def priority_title(self):
- return self.priority.title
-
- @property
- def target_link(self):
- return format_link(self.target)
-
- @property
- def assignee_link(self):
- if self.assignee is None:
- return 'Nobody'
- return format_link(self.assignee)
-
- @property
- def items(self):
- # Sort the work items by status only because they all have the same
- # priority.
- def sort_key(item):
- status_order = {
- SpecificationWorkItemStatus.POSTPONED: 5,
- SpecificationWorkItemStatus.DONE: 4,
- SpecificationWorkItemStatus.INPROGRESS: 3,
- SpecificationWorkItemStatus.TODO: 2,
- SpecificationWorkItemStatus.BLOCKED: 1,
- }
- return status_order[item.status]
- return sorted(self._items, key=sort_key)
-
-
-class AggregatedBugsContainer(WorkItemContainer):
- """A container of BugTasks wrapped with GenericWorkItem."""
-
- @property
- def html_link(self):
- return 'Bugs targeted to a milestone on this date'
-
- @property
- def assignee_link(self):
- return 'N/A'
-
- @property
- def target_link(self):
- return 'N/A'
-
- @property
- def priority_title(self):
- return 'N/A'
-
- @property
- def items(self):
- def sort_key(item):
- return (item.status.value, item.priority.value)
- # Sort by (status, priority) in reverse order because the biggest the
- # status/priority the more interesting it is to us.
- return sorted(self._items, key=sort_key, reverse=True)
-
-
-class GenericWorkItem:
- """A generic piece of work; either a BugTask or a SpecificationWorkItem.
-
- This class wraps a BugTask or a SpecificationWorkItem to provide a
- common API so that the template doesn't have to worry about what kind of
- work item it's dealing with.
- """
-
- def __init__(self, assignee, status, priority, target, title,
- bugtask=None, work_item=None):
- self.assignee = assignee
- self.status = status
- self.priority = priority
- self.target = target
- self.title = title
- self._bugtask = bugtask
- self._work_item = work_item
-
- @classmethod
- def from_bugtask(cls, bugtask):
- return cls(
- bugtask.assignee, bugtask.status, bugtask.importance,
- bugtask.target, bugtask.bug.description, bugtask=bugtask)
-
- @classmethod
- def from_workitem(cls, work_item):
- assignee = work_item.assignee
- if assignee is None:
- assignee = work_item.specification.assignee
- return cls(
- assignee, work_item.status, work_item.specification.priority,
- work_item.specification.target, work_item.title,
- work_item=work_item)
-
- @property
- def display_title(self):
- if self._work_item is not None:
- return FormattersAPI(self.title).shorten(120)
- else:
- return format_link(self._bugtask)
-
- @property
- def milestone(self):
- milestone = self.actual_workitem.milestone
- if milestone is None:
- assert self._work_item is not None, (
- "BugTaks without a milestone must not be here.")
- milestone = self._work_item.specification.milestone
- return milestone
-
- @property
- def actual_workitem(self):
- """Return the actual work item that we are wrapping.
-
- This may be either an IBugTask or an ISpecificationWorkItem.
- """
- if self._work_item is not None:
- return self._work_item
- else:
- return self._bugtask
-
- @property
- def is_complete(self):
- return self.actual_workitem.is_complete
-
-
-def getWorkItemsDueBefore(team, cutoff_date, user):
- """Return a dict mapping dates to lists of WorkItemContainers.
-
- This is a grouping, by milestone due date, of all work items
- (SpecificationWorkItems/BugTasks) assigned to any member of this
- team.
-
- Only work items whose milestone have a due date between today and the
- given cut-off date are included in the results.
- """
- workitems = team.getAssignedSpecificationWorkItemsDueBefore(cutoff_date)
- # For every specification that has work items in the list above, create
- # one SpecWorkItemContainer holding the work items from that spec that are
- # targeted to the same milestone and assigned to members of the given team.
- containers_by_date = {}
- containers_by_spec = {}
- for workitem in workitems:
- spec = workitem.specification
- milestone = workitem.milestone
- if milestone is None:
- milestone = spec.milestone
- if milestone.dateexpected not in containers_by_date:
- containers_by_date[milestone.dateexpected] = []
- container = containers_by_spec.get(spec)
- if container is None:
- container = SpecWorkItemContainer(spec)
- containers_by_spec[spec] = container
- containers_by_date[milestone.dateexpected].append(container)
- container.append(GenericWorkItem.from_workitem(workitem))
-
- # Sort our containers by priority.
- for date in containers_by_date:
- containers_by_date[date].sort(
- key=attrgetter('priority'), reverse=True)
-
- bugtasks = team.getAssignedBugTasksDueBefore(cutoff_date, user)
- bug_containers_by_date = {}
- # For every milestone due date, create an AggregatedBugsContainer with all
- # the bugtasks targeted to a milestone on that date and assigned to
- # members of this team.
- for task in bugtasks:
- dateexpected = task.milestone.dateexpected
- container = bug_containers_by_date.get(dateexpected)
- if container is None:
- container = AggregatedBugsContainer()
- bug_containers_by_date[dateexpected] = container
- # Also append our new container to the dictionary we're going
- # to return.
- if dateexpected not in containers_by_date:
- containers_by_date[dateexpected] = []
- containers_by_date[dateexpected].append(container)
- container.append(GenericWorkItem.from_bugtask(task))
-
- return containers_by_date
=== renamed file 'lib/lp/registry/browser/tests/test_team_upcomingwork.py' => 'lib/lp/registry/browser/tests/test_person_upcomingwork.py'
--- lib/lp/registry/browser/tests/test_team_upcomingwork.py 2012-04-04 20:45:04 +0000
+++ lib/lp/registry/browser/tests/test_person_upcomingwork.py 2012-04-04 20:45:04 +0000
@@ -15,7 +15,7 @@
SpecificationPriority,
SpecificationWorkItemStatus,
)
-from lp.registry.browser.team import (
+from lp.registry.browser.person import (
GenericWorkItem,
getWorkItemsDueBefore,
WorkItemContainer,
@@ -174,12 +174,12 @@
self.assertEqual('67', container.percent_done)
-class TestTeamUpcomingWork(BrowserTestCase):
+class TestPersonUpcomingWork(BrowserTestCase):
layer = DatabaseFunctionalLayer
def setUp(self):
- super(TestTeamUpcomingWork, self).setUp()
+ super(TestPersonUpcomingWork, self).setUp()
self.today = datetime.today().date()
self.tomorrow = self.today + timedelta(days=1)
self.today_milestone = self.factory.makeMilestone(
@@ -188,7 +188,10 @@
dateexpected=self.tomorrow)
self.team = self.factory.makeTeam()
- def test_basic(self):
+ def test_basic_for_team(self):
+ """Check that the page shows the bugs/work items assigned to members
+ of a team.
+ """
workitem1 = self.factory.makeSpecificationWorkItem(
assignee=self.team.teamowner, milestone=self.today_milestone)
workitem2 = self.factory.makeSpecificationWorkItem(
@@ -203,6 +206,8 @@
browser = self.getViewBrowser(
self.team, view_name='+upcomingwork', no_login=True)
+ # Check that the two work items and bugtasks created above are shown
+ # and grouped under the appropriate milestone date.
groups = find_tags_by_class(browser.contents, 'workitems-group')
self.assertEqual(2, len(groups))
todays_group = extract_text(groups[0])
@@ -219,6 +224,34 @@
with anonymous_logged_in():
self.assertIn(bugtask2.bug.title, tomorrows_group)
+ def test_basic_for_person(self):
+ """Check that the page shows the bugs/work items assigned to a person.
+ """
+ person = self.factory.makePerson()
+ workitem = self.factory.makeSpecificationWorkItem(
+ assignee=person, milestone=self.today_milestone)
+ bugtask = self.factory.makeBug(
+ milestone=self.tomorrow_milestone).bugtasks[0]
+ removeSecurityProxy(bugtask).assignee = person
+
+ browser = self.getViewBrowser(
+ person, view_name='+upcomingwork', no_login=True)
+
+ # Check that the two work items created above are shown and grouped
+ # under the appropriate milestone date.
+ groups = find_tags_by_class(browser.contents, 'workitems-group')
+ self.assertEqual(2, len(groups))
+ todays_group = extract_text(groups[0])
+ tomorrows_group = extract_text(groups[1])
+ self.assertStartsWith(
+ todays_group, 'Work items due in %s' % self.today)
+ self.assertIn(workitem.title, todays_group)
+
+ self.assertStartsWith(
+ tomorrows_group, 'Work items due in %s' % self.tomorrow)
+ with anonymous_logged_in():
+ self.assertIn(bugtask.bug.title, tomorrows_group)
+
def test_overall_progressbar(self):
"""Check that the per-date progress bar is present."""
# Create two work items on separate specs. One of them is done and the
@@ -272,12 +305,12 @@
self.assertEqual('0%', container2_progressbar.get('width'))
-class TestTeamUpcomingWorkView(TestCaseWithFactory):
+class TestPersonUpcomingWorkView(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
def setUp(self):
- super(TestTeamUpcomingWorkView, self).setUp()
+ super(TestPersonUpcomingWorkView, self).setUp()
self.today = datetime.today().date()
self.tomorrow = self.today + timedelta(days=1)
self.today_milestone = self.factory.makeMilestone(
=== renamed file 'lib/lp/registry/templates/team-upcomingwork.pt' => 'lib/lp/registry/templates/person-upcomingwork.pt'
--- lib/lp/registry/templates/team-upcomingwork.pt 2012-04-04 20:45:04 +0000
+++ lib/lp/registry/templates/person-upcomingwork.pt 2012-04-04 20:45:04 +0000
@@ -68,8 +68,11 @@
There are <span tal:replace="python: view.workitem_counts[date]" />
Blueprint work items and
<span tal:replace="python: view.bugtask_counts[date]" /> Bugs due
- in <span tal:content="date/fmt:date" /> which are assigned to members
- of this team.
+ in <span tal:content="date/fmt:date" /> which are assigned to
+ <tal:team condition="context/is_team">members of this team.</tal:team>
+ <tal:not-team condition="not: context/is_team">
+ <span tal:replace="context/displayname" />
+ </tal:not-team>
</p>
<table class="listing">