launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06620
[Merge] lp:~stevenk/launchpad/force-ibug-into-line into lp:launchpad
Steve Kowalik has proposed merging lp:~stevenk/launchpad/force-ibug-into-line into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #949672 in Launchpad itself: "IBug needs to be a better interface"
https://bugs.launchpad.net/launchpad/+bug/949672
For more details, see:
https://code.launchpad.net/~stevenk/launchpad/force-ibug-into-line/+merge/96513
Force IBug into towing the line.
This branch massively cleans up the ZCML related to IBug, and blows it apart into four separate classes: IBugPublic, IBugView, IBugEdit and IBugAdmin.
I have tried my best to keep this branch small, but the size just can't be helped due to how large the ZCML changes are, and how big IBug is.
I have also added a missing method to IBugView which was implemented in the model, listed in the ZCML, but did not appear in the old IBug.
--
https://code.launchpad.net/~stevenk/launchpad/force-ibug-into-line/+merge/96513
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/force-ibug-into-line into lp:launchpad.
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2012-02-24 04:00:24 +0000
+++ lib/lp/bugs/configure.zcml 2012-03-08 05:47:22 +0000
@@ -716,171 +716,22 @@
<class
class="lp.bugs.model.bug.Bug">
- <implements
- interface="lp.bugs.interfaces.bug.IBug"/>
<allow
- attributes="
- id
- private
- userCanView"/>
+ interface="lp.bugs.interfaces.bug.IBugPublic"/>
<require
permission="launchpad.View"
- attributes="
- bugtasks
- default_bugtask
- affected_pillars
- permits_expiration
- can_expire
- subscriptions
- syncUpdate
- date_last_updated
- getActivityForDateRange
- getBugTask
- getDirectSubscribers
- getDirectSubscribersWithDetails
- getDirectSubscriptions
- getIndirectSubscribers
- is_complete
- maybeConfirmBugtasks
- official_tags
- who_made_private
- date_made_private
- userCanSetCommentVisibility
- personIsDirectSubscriber
- personIsAlsoNotifiedSubscriber
- personIsSubscribedToDuplicate
- shouldConfirmBugtasks
- heat
- heat_last_updated
- has_patches
- latest_patch
- latest_patch_uploaded
- datecreated
- name
- title
- description
- ownerID
- owner
- duplicateof
- communityscore
- communitytimestamp
- hits
- hitstimestamp
- activityscore
- activitytimestamp
- displayname
- activity
- initial_message
- linked_branches
- getVisibleLinkedBranches
- watches
- has_cves
- cves
- cve_links
- duplicates
- attachments
- attachments_unpopulated
- questions
- specifications
- followup_subject
- isSubscribed
- messages
- getBugNotificationRecipients
- hasBranch
- security_related
- tags
- getMessagesForView
- isSubscribedToDupes
- getSubscribersFromDuplicates
- getSubscriptionsFromDuplicates
- getSubscribersForPerson
- getSubscriptionForPerson
- getSubscriptionInfo
- indexed_messages
- _indexed_messages
- getAlsoNotifiedSubscribers
- getBugWatch
- canBeNominatedFor
- getNominationFor
- getNominations
- date_last_message
- number_of_duplicates
- message_count
- comment_count
- getQuestionCreatedFromBug
- canBeAQuestion
- getBugTasksByPackageName
- users_affected_count
- users_unaffected_count
- users_affected
- users_unaffected
- users_affected_count_with_dupes
- other_users_affected_count_with_dupes
- users_affected_with_dupes
- bug_messages
- isUserAffected
- getHWSubmissions
- isExpirable
- isMuted"/>
+ interface="lp.bugs.interfaces.bug.IBugView"/>
<require
permission="launchpad.Edit"
- attributes="
- addAttachment
- addChange
- addCommentNotification
- addNomination
- addTask
- addWatch
- convertToQuestion
- expireNotifications
- findCvesInText
- linkAttachment
- linkBranch
- linkCVE
- linkCVEAndReturnNothing
- linkHWSubmission
- linkMessage
- markAsDuplicate
- markUserAffected
- mute
- newMessage
- removeWatch
- setCommentVisibility
- setPrivate
- setSecurityRelated
- setPrivacyAndSecurityRelated
- setStatus
- subscribe
- unlinkBranch
- unlinkCVE
- unlinkHWSubmission
- unmute
- unsubscribe
- unsubscribeFromDupes
- updateHeat"
+ interface="lp.bugs.interfaces.bug.IBugEdit"
set_attributes="
- activity initial_message
- activityscore
- activitytimestamp
- communityscore
- communitytimestamp
- date_last_updated
- date_made_private
- datecreated
- hits
- hitstimestamp
- name
- owner
- ownerID
- security_related
- tags
- title description
- who_made_private"/>
+ date_last_updated date_made_private datecreated
+ name owner ownerID security_related tags title
+ description who_made_private"/>
<require
permission="launchpad.Admin"
- attributes="
- setHeat"
- set_attributes="heat_last_updated" />
+ interface="lp.bugs.interfaces.bug.IBugAdmin"
+ set_attributes="heat_last_updated"/>
</class>
<adapter
for="lp.bugs.interfaces.bug.IBug"
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2012-02-20 22:21:36 +0000
+++ lib/lp/bugs/interfaces/bug.py 2012-03-08 05:47:22 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
# pylint: disable-msg=E0211,E0213,E0602
@@ -12,10 +12,14 @@
'CreatedBugWithNoBugTasksError',
'IBug',
'IBugAddForm',
+ 'IBugAdmin',
'IBugBecameQuestionEvent',
'IBugDelta',
+ 'IBugEdit',
'IBugMute',
+ 'IBugPublic',
'IBugSet',
+ 'IBugView',
'IFileBugData',
'IFrontPageBugAddForm',
'IProjectGroupBugAddForm',
@@ -195,17 +199,25 @@
return subject_field
-class IBug(IPrivacy, IHasLinkedBranches):
- """The core bug entry."""
- export_as_webservice_entry()
-
+class IBugPublic(IPrivacy):
+ """Public attributes for a Bug."""
id = exported(
Int(title=_('Bug ID'), required=True, readonly=True))
- datecreated = exported(
- Datetime(title=_('Date Created'), required=True, readonly=True),
- exported_as='date_created')
- date_last_updated = exported(
- Datetime(title=_('Date Last Updated'), required=True, readonly=True))
+ # This is redefined from IPrivacy.private because the attribute is
+ # read-only. The value is guarded by setPrivate().
+ private = exported(
+ Bool(title=_("This bug report should be private"), required=False,
+ description=_("Private bug reports are visible only to "
+ "their subscribers."),
+ default=False,
+ readonly=True))
+
+ def userCanView(user):
+ """Return True if `user` can see this IBug, false otherwise."""
+
+
+class IBugView(IHasLinkedBranches):
+ """IBug attributes that require launchpad.View permission."""
name = exported(
BugNameField(
title=_('Nickname'), required=False,
@@ -225,37 +237,6 @@
ownerID = Int(title=_('Owner'), required=True, readonly=True)
owner = exported(
Reference(IPerson, title=_("The owner's IPerson"), readonly=True))
- duplicateof = exported(
- DuplicateBug(title=_('Duplicate Of'), required=False, readonly=True),
- exported_as='duplicate_of')
- # This is redefined from IPrivacy.private because the attribute is
- # read-only. The value is guarded by setPrivate().
- private = exported(
- Bool(title=_("This bug report should be private"), required=False,
- description=_("Private bug reports are visible only to "
- "their subscribers."),
- default=False,
- readonly=True))
- date_made_private = exported(
- Datetime(title=_('Date Made Private'), required=False, readonly=True))
- who_made_private = exported(
- PublicPersonChoice(
- title=_('Who Made Private'), required=False,
- vocabulary='ValidPersonOrTeam',
- description=_("The person who set this bug private."),
- readonly=True))
- security_related = exported(
- Bool(title=_("This bug is a security vulnerability."),
- required=False, default=False, readonly=True))
- displayname = TextLine(title=_("Text of the form 'Bug #X"),
- readonly=True)
- activity = exported(
- doNotSnapshot(CollectionField(
- title=_('Log of activity that has occurred on this bug.'),
- value_type=Reference(schema=IBugActivity),
- readonly=True)))
- initial_message = Attribute(
- "The message that was specified when creating the bug")
bugtasks = exported(
CollectionField(
title=_('BugTasks on this bug, sorted upstream, then '
@@ -266,8 +247,62 @@
default_bugtask = Reference(
title=_("The first bug task to have been filed."),
schema=IBugTask)
+ duplicateof = exported(
+ DuplicateBug(title=_('Duplicate Of'), required=False, readonly=True),
+ exported_as='duplicate_of')
+ datecreated = exported(
+ Datetime(title=_('Date Created'), required=True, readonly=True),
+ exported_as='date_created')
+ displayname = TextLine(title=_("Text of the form 'Bug #X"),
+ readonly=True)
+ activity = exported(
+ doNotSnapshot(CollectionField(
+ title=_('Log of activity that has occurred on this bug.'),
+ value_type=Reference(schema=IBugActivity),
+ readonly=True)))
affected_pillars = Attribute(
'The "pillars", products or distributions, affected by this bug.')
+ permits_expiration = Bool(
+ title=_("Does the bug's state permit expiration?"),
+ description=_(
+ "Expiration is permitted when the bug is not valid anywhere, "
+ "a message was sent to the bug reporter, and the bug is "
+ "associated with pillars that have enabled bug expiration."),
+ readonly=True)
+ can_expire = exported(
+ Bool(
+ title=_("Can the Incomplete bug expire? "
+ "Expiration may happen when the bug permits expiration, "
+ "and a bugtask cannot be confirmed."),
+ readonly=True),
+ ('devel', dict(exported=False)), exported=True)
+ subscriptions = exported(
+ doNotSnapshot(CollectionField(
+ title=_('Subscriptions'),
+ value_type=Reference(schema=Interface),
+ readonly=True)))
+ date_last_updated = exported(
+ Datetime(title=_('Date Last Updated'), required=True, readonly=True))
+ is_complete = Bool(
+ title=_("Is Complete?"),
+ description=_(
+ "True or False depending on whether this bug is considered "
+ "completely addressed. A bug in Launchpad is completely "
+ "addressed when there are no tasks that are still open for "
+ "the bug."),
+ readonly=True)
+ official_tags = Attribute("The official bug tags relevant to this bug.")
+ who_made_private = exported(
+ PublicPersonChoice(
+ title=_('Who Made Private'), required=False,
+ vocabulary='ValidPersonOrTeam',
+ description=_("The person who set this bug private."),
+ readonly=True))
+ date_made_private = exported(
+ Datetime(title=_('Date Made Private'), required=False, readonly=True))
+ heat = exported(
+ Int(title=_("The 'heat' of the bug"),
+ required=False, readonly=True))
watches = exported(
CollectionField(
title=_("All bug watches associated with this bug."),
@@ -281,11 +316,6 @@
readonly=True))
has_cves = Bool(title=u"True if the bug has cve entries.")
cve_links = Attribute('Links between this bug and CVE entries.')
- subscriptions = exported(
- doNotSnapshot(CollectionField(
- title=_('Subscriptions'),
- value_type=Reference(schema=Interface),
- readonly=True)))
duplicates = exported(
CollectionField(
title=_("MultiJoin of bugs which are dupes of this one."),
@@ -305,6 +335,17 @@
title=_("List of bug attachments."),
value_type=Reference(schema=IBugAttachment),
readonly=True)))
+ security_related = exported(
+ Bool(title=_("This bug is a security vulnerability."),
+ required=False, default=False, readonly=True))
+ has_patches = Attribute("Does this bug have any patches?")
+ latest_patch_uploaded = exported(
+ Datetime(
+ title=_('Date when the most recent patch was uploaded.'),
+ required=False, readonly=True))
+ latest_patch = Attribute("The most recent patch of this bug.")
+ initial_message = Attribute(
+ "The message that was specified when creating the bug")
questions = Attribute("List of questions related to this bug.")
specifications = Attribute("List of related specifications.")
linked_branches = exported(
@@ -318,28 +359,12 @@
description=_("Space-separated keywords for classifying "
"this bug report."),
value_type=Tag(), required=False))
- is_complete = Bool(
- title=_("Is Complete?"),
- description=_(
- "True or False depending on whether this bug is considered "
- "completely addressed. A bug in Launchpad is completely "
- "addressed when there are no tasks that are still open for "
- "the bug."),
- readonly=True)
- permits_expiration = Bool(
- title=_("Does the bug's state permit expiration?"),
- description=_(
- "Expiration is permitted when the bug is not valid anywhere, "
- "a message was sent to the bug reporter, and the bug is "
- "associated with pillars that have enabled bug expiration."),
- readonly=True)
- can_expire = exported(
- Bool(
- title=_("Can the Incomplete bug expire? "
- "Expiration may happen when the bug permits expiration, "
- "and a bugtask cannot be confirmed."),
- readonly=True),
- ('devel', dict(exported=False)), exported=True)
+ messages = doNotSnapshot(CollectionField(
+ title=_("The messages related to this object, in reverse "
+ "order of creation (so newest first)."),
+ readonly=True,
+ value_type=Reference(schema=IMessage)))
+ followup_subject = Attribute("The likely subject of the next message.")
date_last_message = exported(
Datetime(title=_("Date of last bug message"),
required=False, readonly=True))
@@ -384,26 +409,12 @@
title=_('Users affected (including duplicates)'),
value_type=Reference(schema=IPerson),
readonly=True)))
-
- heat = exported(
- Int(title=_("The 'heat' of the bug"),
- required=False, readonly=True))
- heat_last_updated = Datetime(
- title=_('Heat Last Updated'), required=False, readonly=True)
-
# Adding related BugMessages provides a hook for getting at
# BugMessage.message.visible when building bug comments.
bug_messages = Attribute('The bug messages related to this object.')
comment_count = Attribute(
"The number of comments on this bug, not including the initial "
"comment.")
-
- messages = doNotSnapshot(CollectionField(
- title=_("The messages related to this object, in reverse "
- "order of creation (so newest first)."),
- readonly=True,
- value_type=Reference(schema=IMessage)))
-
indexed_messages = doNotSnapshot(exported(
CollectionField(
title=_("The messages related to this object, in reverse "
@@ -422,18 +433,8 @@
None to prevent lazy evaluation triggering database lookups.
"""
- followup_subject = Attribute("The likely subject of the next message.")
-
- has_patches = Attribute("Does this bug have any patches?")
-
- latest_patch_uploaded = exported(
- Datetime(
- title=_('Date when the most recent patch was uploaded.'),
- required=False, readonly=True))
-
- latest_patch = Attribute("The most recent patch of this bug.")
-
- official_tags = Attribute("The official bug tags relevant to this bug.")
+ def hasBranch(branch):
+ """Is this branch linked to this bug?"""
@accessor_for(linked_branches)
@call_with(user=REQUEST_USER)
@@ -442,48 +443,6 @@
def getVisibleLinkedBranches(user):
"""Rertun the linked to this bug that are visible by `user`."""
- @operation_parameters(
- subject=optional_message_subject_field(),
- content=copy_field(IMessage['content']))
- @call_with(owner=REQUEST_USER)
- @export_factory_operation(IMessage, [])
- def newMessage(owner, subject, content):
- """Create a new message, and link it to this object."""
-
- # The level actually uses BugNotificationLevel as its vocabulary,
- # but due to circular import problems we fix that in
- # _schema_circular_imports.py rather than here.
- @operation_parameters(
- person=Reference(IPerson, title=_('Person'), required=True),
- level=Choice(
- vocabulary=DBEnumeratedType, required=False,
- title=_('Level')))
- @call_with(subscribed_by=REQUEST_USER, suppress_notify=False)
- @export_write_operation()
- def subscribe(person, subscribed_by, suppress_notify=True, level=None):
- """Subscribe `person` to the bug.
-
- :param person: the subscriber.
- :param subscribed_by: the person who created the subscription.
- :param suppress_notify: a flag to suppress notify call.
- :param level: The BugNotificationLevel for the new subscription.
- :return: an `IBugSubscription`.
- """
-
- @operation_parameters(
- person=Reference(IPerson, title=_('Person'), required=False))
- @call_with(unsubscribed_by=REQUEST_USER)
- @export_write_operation()
- def unsubscribe(person, unsubscribed_by):
- """Remove this person's subscription to this bug."""
-
- @operation_parameters(
- person=Reference(IPerson, title=_('Person'), required=False))
- @call_with(unsubscribed_by=REQUEST_USER)
- @export_write_operation()
- def unsubscribeFromDupes(person, unsubscribed_by):
- """Remove this person's subscription from all dupes of this bug."""
-
def isSubscribed(person):
"""Is person subscribed to this bug?
@@ -506,24 +465,6 @@
:returns: True if the user has muted all email from this bug.
"""
- @operation_parameters(
- person=Reference(IPerson, title=_('Person'), required=False))
- @call_with(muted_by=REQUEST_USER)
- @export_write_operation()
- @operation_for_version('devel')
- def mute(person, muted_by):
- """Add a muted subscription for `person`."""
-
- @operation_parameters(
- person=Reference(IPerson, title=_('Person'), required=False))
- @call_with(unmuted_by=REQUEST_USER)
- @export_write_operation()
- @operation_for_version('devel')
- def unmute(person, unmuted_by):
- """Remove a muted subscription for `person`.
-
- Returns previously muted direct subscription, if any."""
-
def getDirectSubscriptions():
"""A sequence of IBugSubscriptions directly linked to this bug."""
@@ -597,118 +538,6 @@
True to include the master bug's subscribers as recipients.
"""
- def addCommentNotification(message, recipients=None, activity=None):
- """Add a bug comment notification.
-
- If a BugActivity instance is provided as an `activity`, it is linked
- to the notification."""
-
- def addChange(change, recipients=None):
- """Record a change to the bug.
-
- :param change: An `IBugChange` instance from which to take the
- change data.
- :param recipients: A set of `IBugNotificationRecipient`s to whom
- to send notifications about this change. If None is passed
- the default list of recipients for the bug will be used.
- """
-
- def expireNotifications():
- """Expire any pending notifications that have not been emailed.
-
- This will mark any notifications related to this bug as having
- been emailed. The intent is to prevent large quantities of
- bug mail being generated during bulk imports or changes.
- """
-
- @call_with(owner=REQUEST_USER)
- @rename_parameters_as(
- bugtracker='bug_tracker', remotebug='remote_bug')
- @export_factory_operation(
- IBugWatch, ['bugtracker', 'remotebug'])
- def addWatch(bugtracker, remotebug, owner):
- """Create a new watch for this bug on the given remote bug and bug
- tracker, owned by the person given as the owner.
- """
-
- def removeWatch(bug_watch, owner):
- """Remove a bug watch from the bug."""
-
- @call_with(owner=REQUEST_USER)
- @operation_parameters(target=copy_field(IBugTask['target']))
- @export_factory_operation(IBugTask, [])
- def addTask(owner, target):
- """Create a new bug task on this bug.
-
- :raises IllegalTarget: if the bug task cannot be added to the bug.
- """
-
- def hasBranch(branch):
- """Is this branch linked to this bug?"""
-
- @call_with(owner=REQUEST_USER)
- @operation_parameters(
- data=Bytes(constraint=attachment_size_constraint),
- comment=Text(), filename=TextLine(), is_patch=Bool(),
- content_type=TextLine(), description=Text())
- @export_factory_operation(IBugAttachment, [])
- def addAttachment(owner, data, comment, filename, is_patch=False,
- content_type=None, description=None):
- """Attach a file to this bug.
-
- :owner: An IPerson.
- :data: A file-like object, or a `str`.
- :description: A brief description of the attachment.
- :comment: An IMessage or string.
- :filename: A string.
- :is_patch: A boolean.
- """
-
- def linkAttachment(owner, file_alias, comment, is_patch=False,
- description=None):
- """Link an `ILibraryFileAlias` to this bug.
-
- :owner: An IPerson.
- :file_alias: The `ILibraryFileAlias` to link to this bug.
- :description: A brief description of the attachment.
- :comment: An IMessage or string.
- :is_patch: A boolean.
-
- This method should only be called by addAttachment() and
- FileBugViewBase.submit_bug_action, otherwise
- we may get inconsistent settings of bug.private and
- file_alias.restricted.
- """
-
- def linkCVE(cve, user):
- """Ensure that this CVE is linked to this bug."""
-
- # XXX intellectronica 2008-11-06 Bug #294858:
- # We use this method to suppress the return value
- # from linkCVE, which we don't want to export.
- # In the future we'll have a decorator which does that for us.
- @call_with(user=REQUEST_USER)
- @operation_parameters(cve=Reference(ICve, title=_('CVE'), required=True))
- @export_operation_as('linkCVE')
- @export_write_operation()
- def linkCVEAndReturnNothing(cve, user):
- """Ensure that this CVE is linked to this bug."""
-
- @call_with(user=REQUEST_USER)
- @operation_parameters(cve=Reference(ICve, title=_('CVE'), required=True))
- @export_write_operation()
- def unlinkCVE(cve, user):
- """Ensure that any links between this bug and the given CVE are
- removed.
- """
-
- def findCvesInText(text, user):
- """Find any CVE references in the given text, make sure they exist
- in the database, and are linked to this bug.
-
- The user is the one linking to the CVE.
- """
-
def canBeAQuestion():
"""Return True of False if a question can be created from this bug.
@@ -719,39 +548,9 @@
3. The bug was not made into a question previously.
"""
- def convertToQuestion(person, comment=None):
- """Create and return a Question from this Bug.
-
- Bugs that are also in external bug trackers cannot be converted
- to questions. This is also true for bugs that are being developed.
-
- The `IQuestionTarget` is provided by the `IBugTask` that is not
- Invalid and is not a conjoined slave. Only one question can be
- made from a bug.
-
- An AssertionError is raised if the bug has zero or many BugTasks
- that can provide a QuestionTarget. It will also be raised if a
- question was previously created from the bug.
-
- :person: The `IPerson` creating a question from this bug
- :comment: A string. An explanation of why the bug is a question.
- """
-
def getQuestionCreatedFromBug():
"""Return the question created from this Bug, or None."""
- def linkMessage(message, bugwatch=None, user=None,
- remote_comment_id=None):
- """Add a comment to this bug.
-
- :param message: The `IMessage` to be used as a comment.
- :param bugwatch: The `IBugWatch` of the bug this comment was
- imported from, if it's an imported comment.
- :param user: The `IPerson` adding the comment.
- :param remote_comment_id: The id this comment has in the
- remote bug tracker, if it's an imported comment.
- """
-
def getMessagesForView(slice_info):
"""Return BugMessage,Message,MessageChunks for renderinger.
@@ -764,20 +563,6 @@
@operation_parameters(
target=Reference(schema=Interface, title=_('Target')))
- @call_with(owner=REQUEST_USER)
- @export_factory_operation(Interface, [])
- def addNomination(owner, target):
- """Nominate a bug for an IDistroSeries or IProductSeries.
-
- :owner: An IPerson.
- :target: An IDistroSeries or IProductSeries.
-
- This method creates and returns a BugNomination. (See
- lp.bugs.model.bugnomination.BugNomination.)
- """
-
- @operation_parameters(
- target=Reference(schema=Interface, title=_('Target')))
@export_read_operation()
def canBeNominatedFor(target):
"""Can this bug nominated for this target?
@@ -827,66 +612,6 @@
Return None if this bug doesn't have such a bug watch.
"""
- def setStatus(target, status, user):
- """Set the status of the bugtask related to the specified target.
-
- :target: The target of the bugtask that should be modified.
- :status: The status the bugtask should be set to.
- :user: The `IPerson` doing the change.
-
- If a bug task was edited, emit a
- `lazr.lifecycle.interfaces.IObjectModifiedEvent` and
- return the edited bugtask.
-
- Return None if no bugtask was edited.
- """
-
- @mutator_for(private)
- @operation_parameters(private=copy_field(private))
- @call_with(who=REQUEST_USER)
- @export_write_operation()
- def setPrivate(private, who):
- """Set bug privacy.
-
- :private: True/False.
- :who: The IPerson who is making the change.
-
- Return True if a change is made, False otherwise.
- """
-
- @mutator_for(security_related)
- @operation_parameters(security_related=copy_field(security_related))
- @call_with(who=REQUEST_USER)
- @export_write_operation()
- def setSecurityRelated(security_related, who):
- """Set bug security.
-
- :security_related: True/False.
- :who: The IPerson who is making the change.
-
- This may also cause the security contact to be subscribed
- if one is registered and if the bug is not private.
-
- Return True if a change is made, False otherwise.
- """
-
- @operation_parameters(
- private=copy_field(private),
- security_related=copy_field(security_related),
- )
- @call_with(who=REQUEST_USER)
- @export_write_operation()
- @operation_for_version("devel")
- def setPrivacyAndSecurityRelated(private, security_related, who):
- """Set bug privacy and security .
-
- :private: True/False.
- :security_related: True/False.
- :who: The IPerson who is making the change.
-
- Return (private_changed, security_related_changed) tuple.
- """
-
def getBugTask(target):
"""Return the bugtask with the specified target.
@@ -912,37 +637,6 @@
def isUserAffected(user):
"""Is :user: marked as affected by this bug?"""
- @operation_parameters(
- affected=Bool(
- title=_("Does this bug affect you?"),
- required=False, default=True))
- @call_with(user=REQUEST_USER)
- @export_write_operation()
- def markUserAffected(user, affected=True):
- """Mark :user: as affected by this bug."""
-
- @mutator_for(duplicateof)
- @operation_parameters(duplicate_of=copy_field(duplicateof))
- @export_write_operation()
- def markAsDuplicate(duplicate_of):
- """Mark this bug as a duplicate of another."""
-
- @operation_parameters(
- comment_number=Int(
- title=_('The number of the comment in the list of messages.'),
- required=True),
- visible=Bool(title=_('Show this comment?'), required=True))
- @call_with(user=REQUEST_USER)
- @export_write_operation()
- def setCommentVisibility(user, comment_number, visible):
- """Set the visible attribute on a bug comment. This is restricted
- to Launchpad admins, and will return a HTTP Error 401: Unauthorized
- error for non-admin callers.
- """
-
- def userCanView(user):
- """Return True if `user` can see this IBug, false otherwise."""
-
def userCanSetCommentVisibility(user):
"""Return True if `user` can set bug comment visibility.
@@ -962,20 +656,6 @@
"""
- @operation_parameters(
- submission=Reference(
- Interface, title=_('A HWDB submission'), required=True))
- @export_write_operation()
- def linkHWSubmission(submission):
- """Link a `HWSubmission` to this bug."""
-
- @operation_parameters(
- submission=Reference(
- Interface, title=_('A HWDB submission'), required=True))
- @export_write_operation()
- def unlinkHWSubmission(submission):
- """Remove a link to a `HWSubmission`."""
-
@call_with(user=REQUEST_USER)
@operation_returns_collection_of(Interface)
@export_read_operation()
@@ -990,12 +670,6 @@
if the user is the owner or an admin.
"""
- def setHeat(heat, timestamp=None):
- """Set the heat for the bug."""
-
- def updateHeat():
- """Update the heat for the bug."""
-
@operation_parameters(
days_old=Int(
title=_('Number of days of inactivity for which to check.'),
@@ -1019,6 +693,344 @@
:param end_date: The latest date for which activity can be
returned.
"""
+
+ def maybeConfirmBugtasks():
+ """Maybe try to confirm our new bugtasks."""
+
+
+class IBugEdit(Interface):
+ @call_with(owner=REQUEST_USER)
+ @operation_parameters(
+ data=Bytes(constraint=attachment_size_constraint),
+ comment=Text(), filename=TextLine(), is_patch=Bool(),
+ content_type=TextLine(), description=Text())
+ @export_factory_operation(IBugAttachment, [])
+ def addAttachment(owner, data, comment, filename, is_patch=False,
+ content_type=None, description=None):
+ """Attach a file to this bug.
+
+ :owner: An IPerson.
+ :data: A file-like object, or a `str`.
+ :description: A brief description of the attachment.
+ :comment: An IMessage or string.
+ :filename: A string.
+ :is_patch: A boolean.
+ """
+
+ def addCommentNotification(message, recipients=None, activity=None):
+ """Add a bug comment notification.
+
+ If a BugActivity instance is provided as an `activity`, it is linked
+ to the notification."""
+
+ def addChange(change, recipients=None):
+ """Record a change to the bug.
+
+ :param change: An `IBugChange` instance from which to take the
+ change data.
+ :param recipients: A set of `IBugNotificationRecipient`s to whom
+ to send notifications about this change. If None is passed
+ the default list of recipients for the bug will be used.
+ """
+
+ @operation_parameters(
+ target=Reference(schema=Interface, title=_('Target')))
+ @call_with(owner=REQUEST_USER)
+ @export_factory_operation(Interface, [])
+ def addNomination(owner, target):
+ """Nominate a bug for an IDistroSeries or IProductSeries.
+
+ :owner: An IPerson.
+ :target: An IDistroSeries or IProductSeries.
+
+ This method creates and returns a BugNomination. (See
+ lp.bugs.model.bugnomination.BugNomination.)
+ """
+
+ @call_with(owner=REQUEST_USER)
+ @rename_parameters_as(
+ bugtracker='bug_tracker', remotebug='remote_bug')
+ @export_factory_operation(
+ IBugWatch, ['bugtracker', 'remotebug'])
+ def addWatch(bugtracker, remotebug, owner):
+ """Create a new watch for this bug on the given remote bug and bug
+ tracker, owned by the person given as the owner.
+ """
+
+ def removeWatch(bug_watch, owner):
+ """Remove a bug watch from the bug."""
+
+ @call_with(owner=REQUEST_USER)
+ @operation_parameters(target=copy_field(IBugTask['target']))
+ @export_factory_operation(IBugTask, [])
+ def addTask(owner, target):
+ """Create a new bug task on this bug.
+
+ :raises IllegalTarget: if the bug task cannot be added to the bug.
+ """
+
+ def convertToQuestion(person, comment=None):
+ """Create and return a Question from this Bug.
+
+ Bugs that are also in external bug trackers cannot be converted
+ to questions. This is also true for bugs that are being developed.
+
+ The `IQuestionTarget` is provided by the `IBugTask` that is not
+ Invalid and is not a conjoined slave. Only one question can be
+ made from a bug.
+
+ An AssertionError is raised if the bug has zero or many BugTasks
+ that can provide a QuestionTarget. It will also be raised if a
+ question was previously created from the bug.
+
+ :person: The `IPerson` creating a question from this bug
+ :comment: A string. An explanation of why the bug is a question.
+ """
+
+ def expireNotifications():
+ """Expire any pending notifications that have not been emailed.
+
+ This will mark any notifications related to this bug as having
+ been emailed. The intent is to prevent large quantities of
+ bug mail being generated during bulk imports or changes.
+ """
+
+ def findCvesInText(text, user):
+ """Find any CVE references in the given text, make sure they exist
+ in the database, and are linked to this bug.
+
+ The user is the one linking to the CVE.
+ """
+
+ def linkAttachment(owner, file_alias, comment, is_patch=False,
+ description=None):
+ """Link an `ILibraryFileAlias` to this bug.
+
+ :owner: An IPerson.
+ :file_alias: The `ILibraryFileAlias` to link to this bug.
+ :description: A brief description of the attachment.
+ :comment: An IMessage or string.
+ :is_patch: A boolean.
+
+ This method should only be called by addAttachment() and
+ FileBugViewBase.submit_bug_action, otherwise
+ we may get inconsistent settings of bug.private and
+ file_alias.restricted.
+ """
+
+ def linkCVE(cve, user):
+ """Ensure that this CVE is linked to this bug."""
+
+ # XXX intellectronica 2008-11-06 Bug #294858:
+ # We use this method to suppress the return value
+ # from linkCVE, which we don't want to export.
+ # In the future we'll have a decorator which does that for us.
+ @call_with(user=REQUEST_USER)
+ @operation_parameters(cve=Reference(ICve, title=_('CVE'), required=True))
+ @export_operation_as('linkCVE')
+ @export_write_operation()
+ def linkCVEAndReturnNothing(cve, user):
+ """Ensure that this CVE is linked to this bug."""
+
+ @call_with(user=REQUEST_USER)
+ @operation_parameters(cve=Reference(ICve, title=_('CVE'), required=True))
+ @export_write_operation()
+ def unlinkCVE(cve, user):
+ """Ensure that any links between this bug and the given CVE are
+ removed.
+ """
+
+ @mutator_for(IBugPublic['private'])
+ @operation_parameters(private=copy_field(IBugPublic['private']))
+ @call_with(who=REQUEST_USER)
+ @export_write_operation()
+ def setPrivate(private, who):
+ """Set bug privacy.
+
+ :private: True/False.
+ :who: The IPerson who is making the change.
+
+ Return True if a change is made, False otherwise.
+ """
+
+ @mutator_for(IBugView['security_related'])
+ @operation_parameters(
+ security_related=copy_field(IBugView['security_related']))
+ @call_with(who=REQUEST_USER)
+ @export_write_operation()
+ def setSecurityRelated(security_related, who):
+ """Set bug security.
+
+ :security_related: True/False.
+ :who: The IPerson who is making the change.
+
+ This may also cause the security contact to be subscribed
+ if one is registered and if the bug is not private.
+
+ Return True if a change is made, False otherwise.
+ """
+
+ @operation_parameters(
+ private=copy_field(IBugPublic['private']),
+ security_related=copy_field(IBugView['security_related']),
+ )
+ @call_with(who=REQUEST_USER)
+ @export_write_operation()
+ @operation_for_version("devel")
+ def setPrivacyAndSecurityRelated(private, security_related, who):
+ """Set bug privacy and security .
+
+ :private: True/False.
+ :security_related: True/False.
+ :who: The IPerson who is making the change.
+
+ Return (private_changed, security_related_changed) tuple.
+ """
+
+ @operation_parameters(
+ submission=Reference(
+ Interface, title=_('A HWDB submission'), required=True))
+ @export_write_operation()
+ def linkHWSubmission(submission):
+ """Link a `HWSubmission` to this bug."""
+
+ @operation_parameters(
+ submission=Reference(
+ Interface, title=_('A HWDB submission'), required=True))
+ @export_write_operation()
+ def unlinkHWSubmission(submission):
+ """Remove a link to a `HWSubmission`."""
+
+ def linkMessage(message, bugwatch=None, user=None,
+ remote_comment_id=None):
+ """Add a comment to this bug.
+
+ :param message: The `IMessage` to be used as a comment.
+ :param bugwatch: The `IBugWatch` of the bug this comment was
+ imported from, if it's an imported comment.
+ :param user: The `IPerson` adding the comment.
+ :param remote_comment_id: The id this comment has in the
+ remote bug tracker, if it's an imported comment.
+ """
+
+ @operation_parameters(
+ affected=Bool(
+ title=_("Does this bug affect you?"),
+ required=False, default=True))
+ @call_with(user=REQUEST_USER)
+ @export_write_operation()
+ def markUserAffected(user, affected=True):
+ """Mark :user: as affected by this bug."""
+
+ @mutator_for(IBugView['duplicateof'])
+ @operation_parameters(duplicate_of=copy_field(IBugView['duplicateof']))
+ @export_write_operation()
+ def markAsDuplicate(duplicate_of):
+ """Mark this bug as a duplicate of another."""
+
+ @operation_parameters(
+ comment_number=Int(
+ title=_('The number of the comment in the list of messages.'),
+ required=True),
+ visible=Bool(title=_('Show this comment?'), required=True))
+ @call_with(user=REQUEST_USER)
+ @export_write_operation()
+ def setCommentVisibility(user, comment_number, visible):
+ """Set the visible attribute on a bug comment. This is restricted
+ to Launchpad admins, and will return a HTTP Error 401: Unauthorized
+ error for non-admin callers.
+ """
+
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=False))
+ @call_with(muted_by=REQUEST_USER)
+ @export_write_operation()
+ @operation_for_version('devel')
+ def mute(person, muted_by):
+ """Add a muted subscription for `person`."""
+
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=False))
+ @call_with(unmuted_by=REQUEST_USER)
+ @export_write_operation()
+ @operation_for_version('devel')
+ def unmute(person, unmuted_by):
+ """Remove a muted subscription for `person`.
+
+ Returns previously muted direct subscription, if any."""
+
+ @operation_parameters(
+ subject=optional_message_subject_field(),
+ content=copy_field(IMessage['content']))
+ @call_with(owner=REQUEST_USER)
+ @export_factory_operation(IMessage, [])
+ def newMessage(owner, subject, content):
+ """Create a new message, and link it to this object."""
+
+ # The level actually uses BugNotificationLevel as its vocabulary,
+ # but due to circular import problems we fix that in
+ # _schema_circular_imports.py rather than here.
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=True),
+ level=Choice(
+ vocabulary=DBEnumeratedType, required=False,
+ title=_('Level')))
+ @call_with(subscribed_by=REQUEST_USER, suppress_notify=False)
+ @export_write_operation()
+ def subscribe(person, subscribed_by, suppress_notify=True, level=None):
+ """Subscribe `person` to the bug.
+
+ :param person: the subscriber.
+ :param subscribed_by: the person who created the subscription.
+ :param suppress_notify: a flag to suppress notify call.
+ :param level: The BugNotificationLevel for the new subscription.
+ :return: an `IBugSubscription`.
+ """
+
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=False))
+ @call_with(unsubscribed_by=REQUEST_USER)
+ @export_write_operation()
+ def unsubscribe(person, unsubscribed_by):
+ """Remove this person's subscription to this bug."""
+
+ @operation_parameters(
+ person=Reference(IPerson, title=_('Person'), required=False))
+ @call_with(unsubscribed_by=REQUEST_USER)
+ @export_write_operation()
+ def unsubscribeFromDupes(person, unsubscribed_by):
+ """Remove this person's subscription from all dupes of this bug."""
+
+ def setStatus(target, status, user):
+ """Set the status of the bugtask related to the specified target.
+
+ :target: The target of the bugtask that should be modified.
+ :status: The status the bugtask should be set to.
+ :user: The `IPerson` doing the change.
+
+ If a bug task was edited, emit a
+ `lazr.lifecycle.interfaces.IObjectModifiedEvent` and
+ return the edited bugtask.
+
+ Return None if no bugtask was edited.
+ """
+
+ def updateHeat():
+ """Update the heat for the bug."""
+
+
+class IBugAdmin(Interface):
+ heat_last_updated = Datetime(
+ title=_('Heat Last Updated'), required=False, readonly=True)
+
+ def setHeat(heat, timestamp=None):
+ """Set the heat for the bug."""
+
+
+
+class IBug(IBugPublic, IBugView, IBugEdit, IBugAdmin):
+ """The core bug entry."""
+ export_as_webservice_entry()
# We are forced to define these now to avoid circular import problems.
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2012-03-07 12:58:06 +0000
+++ lib/lp/bugs/model/bug.py 2012-03-08 05:47:22 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
# pylint: disable-msg=E0611,W0212
@@ -2006,7 +2006,7 @@
return self.users_affected_count_with_dupes > 1
def maybeConfirmBugtasks(self):
- """Maybe try to confirm our new bugtasks."""
+ """See `IBug`."""
if self.shouldConfirmBugtasks():
for bugtask in self.bugtasks:
bugtask.maybeConfirm()