Hi Markus, I've added some comments to the diff below. It looks pretty good, thank you so much for doing this. Because I worked closely on you on this branch, I'm going to ask someone else to review it too. Gavin. > === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' > --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-01 19:36:23 +0000 > +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-04 11:12:28 +0000 > @@ -24,11 +24,11 @@ > > from lp.registry.interfaces.structuralsubscription import ( > IStructuralSubscription, IStructuralSubscriptionTarget) > -from lp.bugs.interfaces.bug import IBug > +from lp.bugs.interfaces.bug import IBug, IFrontPageBugAddForm > from lp.bugs.interfaces.bugbranch import IBugBranch > from lp.bugs.interfaces.bugnomination import IBugNomination > from lp.bugs.interfaces.bugtask import IBugTask > -from lp.bugs.interfaces.bugtarget import IHasBugs > +from lp.bugs.interfaces.bugtarget import IHasBugs, IBugTarget > from lp.soyuz.interfaces.build import ( > BuildStatus, IBuild) > from lp.soyuz.interfaces.buildrecords import IHasBuildRecords > @@ -69,6 +69,11 @@ > from lp.soyuz.interfaces.queue import ( > IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus) > from lp.registry.interfaces.sourcepackage import ISourcePackage > +from canonical.launchpad.interfaces.message import ( > + IIndexedMessage, IMessage, IUserToUserEmail) > + > +from lp.bugs.interfaces.bugtracker import IBugTracker > +from lp.bugs.interfaces.bugwatch import IBugWatch > > > IBranch['bug_branches'].value_type.schema = IBugBranch > @@ -312,5 +317,53 @@ > patch_reference_property( > IStructuralSubscriptionTarget, 'parent_subscription_target', > IStructuralSubscriptionTarget) > - > + > IBuildBase['buildstate'].vocabulary = BuildStatus > + > +# IHasBugs > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'assignee', IPerson) > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'bug_reporter', IPerson) > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'bug_supervisor', IPerson) > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'bug_commenter', IPerson) > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'bug_subscriber', IPerson) > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'owner', IPerson) > +patch_plain_parameter_type( > + IHasBugs, 'searchTasks', 'affected_user', IPerson) > + > +# IBugTask > +patch_reference_property(IBugTask, 'owner', IPerson) > + > +# IBugWatch > +patch_reference_property(IBugWatch, 'owner', IPerson) > + > +# IIndexedMessage > +patch_reference_property(IIndexedMessage, 'inside', IBugTask) > + > +# IMessage > +patch_reference_property(IMessage, 'owner', IPerson) > + > +# IUserToUserEmail > +patch_reference_property(IUserToUserEmail, 'sender', IPerson) > +patch_reference_property(IUserToUserEmail, 'recipient', IPerson) > + > +# IBug > +patch_plain_parameter_type( > + IBug, 'addNomination', 'target', IBugTarget) > +patch_plain_parameter_type( > + IBug, 'canBeNominatedFor', 'target', IBugTarget) > +patch_plain_parameter_type( > + IBug, 'getNominationFor', 'target', IBugTarget) > +patch_plain_parameter_type( > + IBug, 'getNominations', 'target', IBugTarget) > + > +# IFrontPageBugAddForm > +patch_reference_property(IFrontPageBugAddForm, 'bugtarget', IBugTarget) > + > +# IBugTracker > +patch_reference_property(IBugTracker, 'owner', IPerson) > That's a hell of a lot of patching. I wish there was a better way to do this :-/ Nothing for you to do, just a general gripe. > === modified file 'lib/canonical/launchpad/interfaces/message.py' > --- lib/canonical/launchpad/interfaces/message.py 2009-12-01 11:53:59 +0000 > +++ lib/canonical/launchpad/interfaces/message.py 2010-02-01 17:01:00 +0000 > @@ -27,10 +27,8 @@ > > from canonical.launchpad import _ > from canonical.launchpad.interfaces import NotFoundError > -from lp.bugs.interfaces.bugtask import IBugTask > from lp.services.job.interfaces.job import IJob > from canonical.launchpad.interfaces.librarian import ILibraryFileAlias > -from lp.registry.interfaces.person import IPerson > > from lazr.delegates import delegates > from lazr.restful.fields import CollectionField, Reference > @@ -57,7 +55,7 @@ > # add form used by MessageAddView. > content = Text(title=_("Message"), required=True, readonly=True) > owner = exported( > - Reference(title=_('Person'), schema=IPerson, > + Reference(title=_('Person'), schema=Interface, > required=False, readonly=True)) > > # Schema is really IMessage, but this cannot be declared here. It's > @@ -181,7 +179,7 @@ > > class IIndexedMessage(Interface): > """An `IMessage` decorated with its index and context.""" > - inside = Reference(title=_('Inside'), schema=IBugTask, > + inside = Reference(title=_('Inside'), schema=Interface, > description=_("The bug task which is " > "the context for this message."), > required=True, readonly=True) > @@ -227,12 +225,12 @@ > """User to user direct email communications.""" > > sender = Object( > - schema=IPerson, > + schema=Interface, > title=_("The message sender"), > required=True, readonly=True) > > recipient = Object( > - schema=IPerson, > + schema=Interface, > title=_("The message recipient"), > required=True, readonly=True) > > > === modified file 'lib/lp/bugs/doc/bugtask-search.txt' > --- lib/lp/bugs/doc/bugtask-search.txt 2010-01-23 21:42:36 +0000 > +++ lib/lp/bugs/doc/bugtask-search.txt 2010-02-02 16:57:02 +0000 > @@ -61,7 +61,18 @@ > >>> ubuntu_firefox_bugs = ubuntu_firefox.searchTasks(all_public) > >>> ubuntu_firefox_bugs.count() > 0 > True > - > + > +== Person bugs == > + > +To get all related tasks to a person call searchTasks() on the person > +pbject: s/pbj/obj/ > + > + >>> from canonical.launchpad.interfaces import IPersonSet > + >>> user = getUtility(IPersonSet).getByName('name16') > + >>> user_bugs = user.searchTasks(None, user=None) > + >>> user_bugs.count() > 0 > + True > + > == Dupes and Conjoined tasks == > > You can set flags to omit duplicates: > > === modified file 'lib/lp/bugs/interfaces/bug.py' > --- lib/lp/bugs/interfaces/bug.py 2010-01-23 21:42:36 +0000 > +++ lib/lp/bugs/interfaces/bug.py 2010-02-02 16:57:02 +0000 > @@ -33,7 +33,6 @@ > from canonical.launchpad.fields import ( > BugField, ContentNameField, DuplicateBug, PublicPersonChoice, Tag, Title) > from lp.bugs.interfaces.bugattachment import IBugAttachment > -from lp.bugs.interfaces.bugtarget import IBugTarget > from lp.bugs.interfaces.bugtask import ( > BugTaskImportance, BugTaskStatus, IBugTask) > from lp.bugs.interfaces.bugwatch import IBugWatch > @@ -587,7 +586,7 @@ > """Create an INullBugTask and return it for the given parameters.""" > > @operation_parameters( > - target=Reference(schema=IBugTarget, title=_('Target'))) > + target=Reference(schema=Interface, title=_('Target'))) > @call_with(owner=REQUEST_USER) > @export_factory_operation(Interface, []) > def addNomination(owner, target): > @@ -601,7 +600,7 @@ > """ > > @operation_parameters( > - target=Reference(schema=IBugTarget, title=_('Target'))) > + target=Reference(schema=Interface, title=_('Target'))) > @export_read_operation() > def canBeNominatedFor(target): > """Can this bug nominated for this target? > @@ -612,7 +611,7 @@ > """ > > @operation_parameters( > - target=Reference(schema=IBugTarget, title=_('Target'))) > + target=Reference(schema=Interface, title=_('Target'))) > @operation_returns_entry(Interface) > @export_read_operation() > def getNominationFor(target): > @@ -625,7 +624,7 @@ > > @operation_parameters( > target=Reference( > - schema=IBugTarget, title=_('Target'), required=False), > + schema=Interface, title=_('Target'), required=False), > nominations=List( > title=_("Nominations to search through."), > value_type=Reference(schema=Interface), # IBugNomination > @@ -895,7 +894,7 @@ > """Create a bug for any bug target.""" > > bugtarget = Reference( > - schema=IBugTarget, title=_("Where did you find the bug?"), > + schema=Interface, title=_("Where did you find the bug?"), > required=True) > > > > === modified file 'lib/lp/bugs/interfaces/bugtarget.py' > --- lib/lp/bugs/interfaces/bugtarget.py 2009-08-18 11:12:06 +0000 > +++ lib/lp/bugs/interfaces/bugtarget.py 2010-02-03 11:41:24 +0000 > @@ -25,7 +25,6 @@ > from canonical.launchpad.fields import Tag > from lp.bugs.interfaces.bugtask import ( > BugTagsSearchCombinator, IBugTask, IBugTaskSearch) > -from lp.registry.interfaces.person import IPerson > from lazr.enum import DBEnumeratedType > from lazr.restful.fields import Reference > from lazr.restful.interface import copy_field > @@ -56,11 +55,6 @@ > "A list of unassigned BugTasks for this target.") > all_bugtasks = Attribute( > "A list of all BugTasks ever reported for this target.") > - official_bug_tags = exported(List( > - title=_("Official Bug Tags"), > - description=_("The list of bug tags defined as official."), > - value_type=Tag(), > - readonly=True)) > > @call_with(search_params=None, user=REQUEST_USER) > @operation_parameters( > @@ -71,13 +65,13 @@ > search_text=copy_field(IBugTaskSearch['searchtext']), > status=copy_field(IBugTaskSearch['status']), > importance=copy_field(IBugTaskSearch['importance']), > - assignee=Reference(schema=IPerson), > - bug_reporter=Reference(schema=IPerson), > - bug_supervisor=Reference(schema=IPerson), > - bug_commenter=Reference(schema=IPerson), > - bug_subscriber=Reference(schema=IPerson), > - owner=Reference(schema=IPerson), > - affected_user=Reference(schema=IPerson), > + assignee=Reference(schema=Interface), > + bug_reporter=Reference(schema=Interface), > + bug_supervisor=Reference(schema=Interface), > + bug_commenter=Reference(schema=Interface), > + bug_subscriber=Reference(schema=Interface), > + owner=Reference(schema=Interface), > + affected_user=Reference(schema=Interface), > has_patch=copy_field(IBugTaskSearch['has_patch']), > has_cve=copy_field(IBugTaskSearch['has_cve']), > tags=copy_field(IBugTaskSearch['tag']), > @@ -278,13 +272,21 @@ > self.status = status > > > -class IOfficialBugTagTargetPublic(Interface): > +class IHasOfficialBugTags(Interface): > + """An entity that exposes a set of official bug tags.""" > + > + official_bug_tags = exported(List( > + title=_("Official Bug Tags"), > + description=_("The list of bug tags defined as official."), > + value_type=Tag(), > + readonly=True)) > + > + > +class IOfficialBugTagTargetPublic(IHasOfficialBugTags): > """Public attributes for `IOfficialBugTagTarget`.""" > > - official_bug_tags = exported(List( > - title=_("Official Bug Tags"), > - description=_("The list of bug tags defined as official."), > - value_type=Tag())) > + official_bug_tags = copy_field( > + IHasOfficialBugTags['official_bug_tags'], readonly=False) > > > class IOfficialBugTagTargetRestricted(Interface): > > === modified file 'lib/lp/bugs/interfaces/bugtask.py' > --- lib/lp/bugs/interfaces/bugtask.py 2010-02-01 22:47:12 +0000 > +++ lib/lp/bugs/interfaces/bugtask.py 2010-02-04 11:12:28 +0000 > @@ -60,7 +60,6 @@ > from lp.soyuz.interfaces.component import IComponent > from canonical.launchpad.interfaces.launchpad import IHasDateCreated, IHasBug > from lp.registry.interfaces.mentoringoffer import ICanBeMentored > -from lp.registry.interfaces.person import IPerson > from canonical.launchpad.searchbuilder import all, any, NULL > from canonical.launchpad.validators import LaunchpadValidationError > from canonical.launchpad.validators.name import name_validator > @@ -474,7 +473,7 @@ > description=_("The age of this task in seconds, a delta between " > "now and the date the bug task was created.")) > owner = exported( > - Reference(title=_("The owner"), schema=IPerson, readonly=True)) > + Reference(title=_("The owner"), schema=Interface, readonly=True)) > target = exported(Reference( > title=_('Target'), required=True, schema=Interface, # IBugTarget > readonly=True, > > === modified file 'lib/lp/bugs/interfaces/bugtracker.py' > --- lib/lp/bugs/interfaces/bugtracker.py 2009-12-14 13:51:00 +0000 > +++ lib/lp/bugs/interfaces/bugtracker.py 2010-02-01 17:01:00 +0000 > @@ -27,7 +27,6 @@ > from canonical.launchpad import _ > from canonical.launchpad.fields import ( > ContentNameField, StrippedTextLine, URIField) > -from lp.registry.interfaces.person import IPerson > from canonical.launchpad.validators import LaunchpadValidationError > from canonical.launchpad.validators.name import name_validator > > @@ -216,7 +215,7 @@ > required=False), > exported_as='base_url_aliases') > owner = exported( > - Reference(title=_('Owner'), schema=IPerson), > + Reference(title=_('Owner'), schema=Interface), > exported_as='registrant') > contactdetails = exported( > Text( > > === modified file 'lib/lp/bugs/interfaces/bugwatch.py' > --- lib/lp/bugs/interfaces/bugwatch.py 2009-12-22 16:32:42 +0000 > +++ lib/lp/bugs/interfaces/bugwatch.py 2010-02-02 16:57:02 +0000 > @@ -22,7 +22,6 @@ > from canonical.launchpad import _ > from canonical.launchpad.fields import StrippedTextLine > from canonical.launchpad.interfaces.launchpad import IHasBug > -from lp.registry.interfaces.person import IPerson > from lp.bugs.interfaces.bugtracker import IBugTracker > > from lazr.restful.declarations import ( > @@ -134,7 +133,7 @@ > exported_as='date_created') > owner = exported( > Reference(title=_('Owner'), required=True, > - readonly=True, schema=IPerson)) > + readonly=True, schema=Interface)) > > # Useful joins. > bugtasks = exported( > > === modified file 'lib/lp/registry/configure.zcml' > --- lib/lp/registry/configure.zcml 2010-01-27 19:06:49 +0000 > +++ lib/lp/registry/configure.zcml 2010-02-03 12:02:24 +0000 > @@ -905,6 +905,8 @@ > interface="lp.bugs.interfaces.bugtarget.IHasBugs"/> > + interface="lp.bugs.interfaces.bugtarget.IHasOfficialBugTags"/> > + interface="canonical.launchpad.webapp.interfaces.IAuthorization"/> > permission="launchpad.Edit" > > === modified file 'lib/lp/registry/interfaces/distribution.py' > --- lib/lp/registry/interfaces/distribution.py 2010-02-01 19:48:57 +0000 > +++ lib/lp/registry/interfaces/distribution.py 2010-02-04 11:12:28 +0000 > @@ -530,13 +530,6 @@ > """An operating system distribution.""" > export_as_webservice_entry() > > -# Patch the official_bug_tags field to make sure that it's > -# writable from the API, and not readonly like its definition > -# in IHasBugs. > -writable_obt_field = copy_field(IDistribution['official_bug_tags']) > -writable_obt_field.readonly = False > -IDistribution._v_attrs['official_bug_tags'] = writable_obt_field > - > > class IBaseDistribution(IDistribution): > """A Distribution that is the base for other Distributions.""" > > === modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py' > --- lib/lp/registry/interfaces/distributionsourcepackage.py 2009-12-05 18:37:28 +0000 > +++ lib/lp/registry/interfaces/distributionsourcepackage.py 2010-02-03 11:42:27 +0000 > @@ -21,7 +21,7 @@ > rename_parameters_as) > > from canonical.launchpad import _ > -from lp.bugs.interfaces.bugtarget import IBugTarget > +from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags > from lp.bugs.interfaces.bugtask import IBugTask > from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals > from lp.registry.interfaces.distribution import IDistribution > @@ -31,7 +31,8 @@ > > > class IDistributionSourcePackage(IBugTarget, IHasBranches, IHasMergeProposals, > - IStructuralSubscriptionTarget): > + IStructuralSubscriptionTarget, > + IHasOfficialBugTags): > """Represents a source package in a distribution. > > Create IDistributionSourcePackages by invoking > > === modified file 'lib/lp/registry/interfaces/distroseries.py' > --- lib/lp/registry/interfaces/distroseries.py 2010-01-21 17:49:38 +0000 > +++ lib/lp/registry/interfaces/distroseries.py 2010-02-03 11:39:50 +0000 > @@ -28,7 +28,8 @@ > from lp.registry.interfaces.series import SeriesStatus > from lp.registry.interfaces.structuralsubscription import ( > IStructuralSubscriptionTarget) > -from lp.bugs.interfaces.bugtarget import IBugTarget, IHasBugs > +from lp.bugs.interfaces.bugtarget import ( > + IBugTarget, IHasBugs, IHasOfficialBugTags) > from lp.soyuz.interfaces.buildrecords import IHasBuildRecords > from lp.translations.interfaces.languagepack import ILanguagePack > from canonical.launchpad.interfaces.launchpad import ( > @@ -154,7 +155,8 @@ > > class IDistroSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner, > IBugTarget, ISpecificationGoal, IHasMilestones, > - IHasBuildRecords, ISeriesMixin): > + IHasBuildRecords, ISeriesMixin, > + IHasOfficialBugTags): > """Public IDistroSeries properties.""" > > id = Attribute("The distroseries's unique number.") > > === modified file 'lib/lp/registry/interfaces/milestone.py' > --- lib/lp/registry/interfaces/milestone.py 2010-01-27 19:12:10 +0000 > +++ lib/lp/registry/interfaces/milestone.py 2010-02-03 11:43:27 +0000 > @@ -21,7 +21,7 @@ > from lp.registry.interfaces.structuralsubscription import ( > IStructuralSubscriptionTarget) > from lp.registry.interfaces.productrelease import IProductRelease > -from lp.bugs.interfaces.bugtarget import IHasBugs > +from lp.bugs.interfaces.bugtarget import IHasBugs, IHasOfficialBugTags > from lp.bugs.interfaces.bugtask import IBugTask > from canonical.launchpad import _ > from canonical.launchpad.fields import ( > @@ -70,7 +70,8 @@ > return milestone > > > -class IMilestone(IHasBugs, IStructuralSubscriptionTarget): > +class IMilestone(IHasBugs, IStructuralSubscriptionTarget, > + IHasOfficialBugTags): > """A milestone, or a targeting point for bugs and other > release-management items that need coordination. > """ > > === modified file 'lib/lp/registry/interfaces/person.py' > --- lib/lp/registry/interfaces/person.py 2010-01-15 01:37:34 +0000 > +++ lib/lp/registry/interfaces/person.py 2010-02-02 17:04:49 +0000 > @@ -96,6 +96,7 @@ > from canonical.launchpad.webapp.interfaces import NameLookupFailed > from canonical.launchpad.webapp.authorization import check_permission > > +from lp.bugs.interfaces.bugtarget import IHasBugs > > PRIVATE_TEAM_PREFIX = 'private-' > > @@ -476,7 +477,7 @@ > > class IPersonPublic(IHasBranches, IHasSpecifications, IHasMentoringOffers, > IHasMergeProposals, IHasLogo, IHasMugshot, IHasIcon, > - IHasLocation, IObjectWithLocation, IPrivacy): > + IHasLocation, IObjectWithLocation, IPrivacy, IHasBugs): > """Public attributes for a Person.""" > > id = Int(title=_('ID'), required=True, readonly=True) > @@ -1013,18 +1014,6 @@ > used between TeamMembership and Person objects. > """ > > - def searchTasks(search_params, *args): > - """Search IBugTasks with the given search parameters. > - > - :search_params: a BugTaskSearchParams object > - :args: any number of BugTaskSearchParams objects > - > - If more than one BugTaskSearchParams is given, return the union of > - IBugTasks which match any of them. > - > - Return an iterable of matching results. > - """ > - > def getLatestMaintainedPackages(): > """Return `SourcePackageRelease`s maintained by this person. > > > === modified file 'lib/lp/registry/interfaces/product.py' > --- lib/lp/registry/interfaces/product.py 2010-01-20 13:58:45 +0000 > +++ lib/lp/registry/interfaces/product.py 2010-02-03 12:10:35 +0000 > @@ -724,13 +724,6 @@ > IProject['products'].value_type = Reference(IProduct) > IProductRelease['product'].schema = IProduct > > -# Patch the official_bug_tags field to make sure that it's > -# writable from the API, and not readonly like its definition > -# in IHasBugs. > -writable_obt_field = copy_field(IProduct['official_bug_tags']) > -writable_obt_field.readonly = False > -IProduct._v_attrs['official_bug_tags'] = writable_obt_field > - > > class IProductSet(Interface): > export_as_webservice_collection(IProduct) > > === modified file 'lib/lp/registry/interfaces/productseries.py' > --- lib/lp/registry/interfaces/productseries.py 2009-12-13 11:55:40 +0000 > +++ lib/lp/registry/interfaces/productseries.py 2010-02-03 11:43:58 +0000 > @@ -24,7 +24,7 @@ > from lp.registry.interfaces.structuralsubscription import ( > IStructuralSubscriptionTarget) > from lp.code.interfaces.branch import IBranch > -from lp.bugs.interfaces.bugtarget import IBugTarget > +from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags > from lp.registry.interfaces.series import SeriesStatus > from canonical.launchpad.interfaces.launchpad import ( > IHasAppointedDriver, IHasDrivers) > @@ -93,7 +93,8 @@ > > > class IProductSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner, > - IBugTarget, ISpecificationGoal, IHasMilestones): > + IBugTarget, ISpecificationGoal, IHasMilestones, > + IHasOfficialBugTags): > """Public IProductSeries properties.""" > # XXX Mark Shuttleworth 2004-10-14: Would like to get rid of id in > # interfaces, as soon as SQLobject allows using the object directly > > === modified file 'lib/lp/registry/interfaces/project.py' > --- lib/lp/registry/interfaces/project.py 2009-12-05 18:37:28 +0000 > +++ lib/lp/registry/interfaces/project.py 2010-02-03 12:01:18 +0000 > @@ -24,7 +24,7 @@ > from lp.code.interfaces.branchvisibilitypolicy import ( > IHasBranchVisibilityPolicy) > from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals > -from lp.bugs.interfaces.bugtarget import IHasBugs > +from lp.bugs.interfaces.bugtarget import IHasBugs, IHasOfficialBugTags > from lp.registry.interfaces.karma import IKarmaContext > from canonical.launchpad.interfaces.launchpad import ( > IHasAppointedDriver, IHasDrivers, IHasIcon, IHasLogo, IHasMugshot) > @@ -64,7 +64,8 @@ > IHasDrivers, IHasBranchVisibilityPolicy, IHasIcon, IHasLogo, > IHasMentoringOffers, IHasMergeProposals, IHasMilestones, IHasMugshot, > IHasOwner, IHasSpecifications, IHasSprints, IHasTranslationGroup, > - IMakesAnnouncements, IKarmaContext, IPillar, IRootContext): > + IMakesAnnouncements, IKarmaContext, IPillar, IRootContext, > + IHasOfficialBugTags): > """Public IProject properties.""" > > id = Int(title=_('ID'), readonly=True) > > === modified file 'lib/lp/registry/interfaces/sourcepackage.py' > --- lib/lp/registry/interfaces/sourcepackage.py 2009-11-24 23:12:23 +0000 > +++ lib/lp/registry/interfaces/sourcepackage.py 2010-02-03 11:44:11 +0000 > @@ -21,7 +21,7 @@ > from lazr.enum import DBEnumeratedType, DBItem > > from canonical.launchpad import _ > -from lp.bugs.interfaces.bugtarget import IBugTarget > +from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags > from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals > from lp.soyuz.interfaces.component import IComponent > from lazr.restful.fields import Reference, ReferenceChoice > @@ -31,7 +31,8 @@ > operation_returns_entry, REQUEST_USER) > > > -class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals): > +class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals, > + IHasOfficialBugTags): > """A SourcePackage. See the MagicSourcePackage specification. This > interface preserves as much as possible of the old SourcePackage > interface from the SourcePackage table, with the new table-less > > === modified file 'lib/lp/registry/model/person.py' > --- lib/lp/registry/model/person.py 2010-01-20 22:09:26 +0000 > +++ lib/lp/registry/model/person.py 2010-02-04 11:12:28 +0000 > @@ -31,6 +31,7 @@ > import random > import re > import weakref > +import copy > > from zope.lifecycleevent import ObjectCreatedEvent > from zope.interface import alsoProvides, implementer, implements > @@ -837,6 +838,24 @@ > > def searchTasks(self, search_params, *args, **kwargs): > """See `IHasBugs`.""" > + if search_params is None and not args: In Launchpad, we check for empty lists (and tuples, dicts, sets, etc) with len(), so this would be `... and len(args) == 0`. > + # this method is called via webapi directly > + args = list() By convention - I don't think it's in the style guide, where the len() thing above is - in Launchpad we'd spell this as simply `[]`. > + for key in ('assignee', 'bug_subscriber', 'owner', 'bug_commenter'): > + if kwargs.get(key, None) is None: Something like: if key not in kwargs: is more readable, but I wonder if kwargs gets stuffed with None values by the API machinery. If so, can you add a comment to explain why it has to be done this way? Also, dict.get(key) defaults to None if the key is not found, so you don't need to be explicit here, but there's no harm. > + arguments = copy.copy(kwargs) kwargs is definitely a dict, so `kwargs.copy()` is sufficient. > + arguments[key] = self > + if key == 'owner': > + # Specify both owner and bug_reporter to try to prevent the same > + # bug (but different tasks) being displayed. > + # see `PersonRelatedBugTaskSearchListingView.searchUnbatched` Can you reformat this to be within 78 columns? > + arguments['bug_reporter'] = self > + args.append(BugTaskSearchParams.fromSearchForm(**arguments)) > + if args: > + search_params = args.pop(0) > + # all keyword arguments are considered in the > + # search parameters, use the implementation in BugTaskSet > + return getUtility(IBugTaskSet).search(search_params, *args) I've read the block above many times (from "if search_params is None and not args" to here) and I just about have a handle on why it does what it does. Please can you add some more comments explaining what it's doing? Also, please can you break it out into a separate method or module-level function and add some unit tests for it? I'm more than happy to help here. > if len(kwargs) > 0: > # if keyword arguments are supplied, use the deault > # implementation in HasBugsBase. > > === modified file 'lib/lp/registry/stories/person/xx-person-bugs.txt' > --- lib/lp/registry/stories/person/xx-person-bugs.txt 2010-01-27 17:49:20 +0000 > +++ lib/lp/registry/stories/person/xx-person-bugs.txt 2010-02-02 16:57:02 +0000 These tests would probably be better in: lib/lp/registry/stories/webservice/xx-person.txt or, better: lib/lp/bugs/stories/webservice/xx-bug.txt > @@ -164,3 +164,22 @@ > >>> anon_browser.open('http://launchpad.dev/~name12/+commentedbugs') > >>> print anon_browser.getLink('List assigned bugs').url > http://bugs.launchpad.dev/~name12/+assignedbugs > + > + > +== It is possible to get related tasks via the webservice API == > + > +Calling searchTasks() on a Person object returns a collection of tasks > +related to this person. > + > + >>> json = webservice.named_get( > + ... '/~name12', 'searchTasks' > + ... ).jsonBody() > + >>> related = json['entries'] > + >>> len(related) > 0 > + True > + >>> name12 = webservice.get("/~name12").jsonBody() > + >>> json = webservice.named_get( > + ... '/~name12', 'searchTasks', assignee=name12['self_link'] > + ... ).jsonBody() > + >>> len(json['entries']) < len(related) > + True It would be nice to see the results. There are a couple of helpers for this, both from lazr.restful.testing.webservice: pprint_entry() and pprint_collection(). Also, please try not to rely on sample data. There is a moratorium on using sample data, with the exception of page tests. However, it's pretty easy to use LaunchpadObjectFactory to get some data to test. Doctests should all have a `factory` global that you can provides `makePerson`, `makeBug`, and many other methods.