コード例 #1
0
class IBugNotification(IHasOwner):
    """A textual representation of bug changes."""

    id = Attribute('id')
    message = Attribute(
        "The message containing the text representation of the changes"
        " to the bug.")
    activity = Attribute(
        "The bug activity object corresponding to this notification.  Will "
        "be None for older notification objects, and will be None if the "
        "bugchange object that provides the data for the change returns None "
        "for getBugActivity.")
    bug = BugField(title=u"The bug this notification is for.", required=True)
    is_comment = Bool(title=u"Comment",
                      description=u"Is the message a comment?",
                      required=True)
    date_emailed = Datetime(
        title=u"Date emailed",
        description=u"When was the notification sent? None, if it hasn't"
        " been sent yet.",
        required=False)
    recipients = Attribute(
        "The people to which this notification should be sent.")
    status = Choice(
        title=_("Status"),
        required=True,
        vocabulary=BugNotificationStatus,
        default=BugNotificationStatus.PENDING,
        description=_("The status of this bug notification."),
    )
    bug_filters = Attribute(
        "List of bug filters that caused this notification.")
コード例 #2
0
class IBugBranch(IHasDateCreated, IHasBug, IHasBranchTarget):
    """A branch linked to a bug."""

    export_as_webservice_entry()

    id = Int(title=_("Bug Branch #"))
    bug = exported(
        BugField(
            title=_("Bug #"),
            required=True, readonly=True))
    branch_id = Int(title=_("Branch ID"), required=True, readonly=True)
    branch = exported(
        ReferenceChoice(
            title=_("Branch"), schema=IBranch,
            vocabulary="Branch", required=True))
    revision_hint = TextLine(title=_("Revision Hint"))

    bug_task = Object(
        schema=IBugTask, title=_("The bug task that the branch fixes"),
        description=_(
            "the bug task reported against this branch's product or the "
            "first bug task (in case where there is no task reported "
            "against the branch's product)."),
        readonly=True)

    registrant = Object(
        schema=IPerson, readonly=True, required=True,
        title=_("The person who linked the bug to the branch"))
コード例 #3
0
class IBugActivity(Interface):
    """A log of all things that have happened to a bug."""
    export_as_webservice_entry()

    bug = exported(BugField(title=_('Bug'), readonly=True))

    datechanged = exported(
        Datetime(title=_('Date Changed'),
                 description=_("The date on which this activity occurred."),
                 readonly=True))

    personID = Attribute('DB ID for Person')
    person = exported(
        PersonChoice(title=_('Person'),
                     required=True,
                     vocabulary='ValidPersonOrTeam',
                     readonly=True,
                     description=_("The person's Launchpad ID or "
                                   "e-mail address.")))

    whatchanged = exported(
        TextLine(title=_('What Changed'),
                 description=_("The property of the bug that changed."),
                 readonly=True))

    target = TextLine(
        title=_('Change Target'),
        required=False,
        readonly=True,
        description=_(
            'The target of what changed, if the change occurred on a '
            'bugtask.'))

    attribute = TextLine(
        title=_('Changed Attribute'),
        required=True,
        readonly=True,
        description=_(
            "The attribute that changed.  If the change occurred on a "
            "bugtask, this will be the bugtask's attribute; otherwise "
            "it will be the bug attribute, and the same as 'what "
            "changed'."))

    oldvalue = exported(
        TextLine(title=_('Old Value'),
                 description=_("The value before the change."),
                 readonly=True))

    newvalue = exported(
        TextLine(title=_('New Value'),
                 description=_("The value after the change."),
                 readonly=True))

    message = exported(
        Text(title=_('Message'),
             description=_("Additional information about what changed."),
             readonly=True))
コード例 #4
0
ファイル: buglink.py プロジェクト: pombreda/UnnaturalCodeFork
class IBugLink(IHasBug):
    """An entity representing a link between a bug and its target."""

    bug = BugField(title=_("The bug that is linked to."),
                   required=True, readonly=True)
    bugID = Attribute("Database id of the bug.")

    target = Object(title=_("The object to which the bug is linked."),
                    required=True, readonly=True, schema=Interface)
コード例 #5
0
class IAbstractSubscriptionInfo(Interface):

    bug = BugField(
        title=_("Bug"), readonly=True, required=True,
        description=_("A bug that this subscription is on. "
                      "If subscription is on a duplicate "
                      "bug, references that bug."))

    principal = PersonChoice(
        title=_("Subscriber"), required=True, readonly=True,
        vocabulary='ValidPersonOrTeam',
        description=_(
            "The person or team for which this information is gathered."))
コード例 #6
0
class IPersonSubscriptions(Interface):
    """Subscription information for a given person and bug."""

    count = Attribute(
        'The total number of subscriptions, real and virtual')

    muted = Bool(
       title=_("Bug is muted?"),
       description=_("Is the bug muted?"),
       default=False, readonly=True)

    bug = BugField(
        title=_("Bug"), readonly=True, required=True,
        description=_("A bug that this subscription is on. "
                      "If subscription is on a duplicate "
                      "bug, references that bug."))

    person = PersonChoice(
        title=_("Subscriber"), required=True, readonly=True,
        vocabulary='ValidPersonOrTeam',
        description=_("The person for which this information is gathered."))

    direct = Attribute(
        "An IRealSubscriptionInfoCollection.  Contains information about all "
        "direct subscriptions. Includes those through membership in teams "
        "directly subscribed to a bug.")

    from_duplicate = Attribute(
        "An IRealSubscriptionInfoCollection.  Contains information about all "
        "subscriptions through duplicate bugs. Includes those through team "
        "membership.")

    as_owner = Attribute(
        "An IVirtualSubscriptionInfoCollection containing information about "
        "all virtual subscriptions as target owner when no bug supervisor "
        "is defined for the target, including those through team "
        "memberships.")

    as_assignee = Attribute(
        "An IVirtualSubscriptionInfoCollection containing information about "
        "all virtual subscriptions as an assignee, including those through "
        "team memberships.")

    def reload():
        """Reload subscriptions for a person/bug."""

    def getDataForClient():
        """Get data for use in client-side code.
コード例 #7
0
class IBugBranch(IHasBug):
    """A branch linked to a bug."""

    export_as_webservice_entry()

    bug = exported(BugField(title=_("Bug #"), required=True, readonly=True))
    branch = exported(
        ReferenceChoice(title=_("Branch"),
                        schema=IBranch,
                        vocabulary="Branch",
                        required=True))

    datecreated = Attribute("The date on which I was created.")
    registrant = Object(schema=IPerson,
                        readonly=True,
                        required=True,
                        title=_("The person who linked the bug to the branch"))
コード例 #8
0
ファイル: bug.py プロジェクト: pombreda/UnnaturalCodeFork
class IBugView(Interface):
    """IBug attributes that require launchpad.View permission."""

    name = exported(
        BugNameField(title=_('Nickname'),
                     required=False,
                     description=_("""A short and unique name.
                Add one only if you often need to retype the URL
                but have trouble remembering the bug number."""),
                     constraint=bug_name_validator))
    title = exported(
        Title(title=_('Summary'),
              required=True,
              description=_("""A one-line summary of the problem.""")))
    description = exported(
        Description(title=_('Description'),
                    required=True,
                    description=_("""A detailed description of the problem,
                 including the steps required to reproduce it."""),
                    strip_text=True,
                    trailing_only=True,
                    min_length=1,
                    max_length=50000))
    ownerID = Int(title=_('Owner'), required=True, readonly=True)
    owner = exported(
        Reference(IPerson, title=_("The owner's IPerson"), readonly=True))
    bugtasks = exported(CollectionField(title=_(
        'BugTasks on this bug, sorted upstream, then '
        'by ubuntu, then by other distroseries.'),
                                        value_type=Reference(schema=IBugTask),
                                        readonly=True),
                        exported_as='bug_tasks')
    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."),
        value_type=Object(schema=IBugWatch),
        readonly=True),
                       exported_as='bug_watches')
    cves = exported(
        CollectionField(title=_('CVE entries related to this bug.'),
                        value_type=Reference(schema=ICve),
                        readonly=True))
    has_cves = Bool(title=u"True if the bug has cve entries.")
    cve_links = Attribute('Links between this bug and CVE entries.')
    duplicates = exported(
        CollectionField(
            title=_("MultiJoin of bugs which are dupes of this one."),
            value_type=BugField(),
            readonly=True))
    # See lp.bugs.model.bug.Bug.attachments for why there are two similar
    # properties here.
    # attachments_unpopulated would more naturally be attachments, and
    # attachments be attachments_prepopulated, but lazr.resful cannot
    # export over a non-exported attribute in an interface.
    # https://bugs.launchpad.net/lazr.restful/+bug/625102
    attachments_unpopulated = CollectionField(
        title=_("List of bug attachments."),
        value_type=Reference(schema=IBugAttachment),
        readonly=True)
    attachments = doNotSnapshot(
        exported(
            CollectionField(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,
             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.")
    tags = exported(
        List(title=_("Tags"),
             description=_("Space-separated keywords for classifying "
                           "this bug report."),
             value_type=Tag(),
             required=False))
    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))
    number_of_duplicates = exported(
        Int(title=_('The number of bugs marked as duplicates of this bug'),
            required=True,
            readonly=True))
    message_count = exported(
        Int(title=_('The number of comments on this bug'),
            required=True,
            readonly=True))
    users_affected_count = exported(
        Int(title=_('The number of users affected by this bug '
                    '(not including duplicates)'),
            required=True,
            readonly=True))
    users_unaffected_count = exported(
        # We don't say "(not including duplicates)" here because
        # affected and unaffected are asymmetrical that way.  If a dup
        # affects you, then the master bug affects you; but if a dup
        # *doesn't* affect you, the master bug may or may not affect
        # you, since a dup is often a specific symptom of a more
        # general master bug.
        Int(title=_('The number of users unaffected by this bug'),
            required=True,
            readonly=True))
    users_affected = exported(
        doNotSnapshot(
            CollectionField(title=_('The number of users affected by this bug '
                                    '(not including duplicates)'),
                            value_type=Reference(schema=IPerson),
                            readonly=True)))
    users_unaffected = exported(
        doNotSnapshot(
            CollectionField(title=_('Users explicitly marked as unaffected '
                                    '(not including duplicates)'),
                            value_type=Reference(schema=IPerson),
                            readonly=True)))
    users_affected_count_with_dupes = exported(
        Int(title=_('The number of users affected by this bug '
                    '(including duplicates)'),
            required=True,
            readonly=True))
    other_users_affected_count_with_dupes = exported(
        Int(title=_('The number of users affected by this bug '
                    '(including duplicates), excluding the current user'),
            required=True,
            readonly=True))
    users_affected_with_dupes = exported(
        doNotSnapshot(
            CollectionField(title=_('Users affected (including duplicates)'),
                            value_type=Reference(schema=IPerson),
                            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.")
    indexed_messages = doNotSnapshot(
        exported(CollectionField(title=_(
            "The messages related to this object, in reverse "
            "order of creation (so newest first)."),
                                 readonly=True,
                                 value_type=Reference(schema=IMessage)),
                 exported_as='messages'))

    def getSpecifications(user):
        """List of related specifications that the user can view."""

    def _indexed_messages(include_content=False, include_parents=False):
        """Low level query for getting bug messages.

        :param include_content: If True retrieve the content for the messages
            too.
        :param include_parents: If True retrieve the object for parent
            messages too. If False the parent attribute will be *forced* to
            None to prevent lazy evaluation triggering database lookups.
        """

    def hasBranch(branch):
        """Is this branch linked to this bug?"""

    def isSubscribed(person):
        """Is person subscribed to this bug?

        Returns True if the user is explicitly subscribed to this bug
        (no matter what the type of subscription), otherwise False.

        If person is None, the return value is always False.
        """

    def isSubscribedToDupes(person):
        """Is person directly subscribed to dupes of this bug?

        Returns True if the user is directly subscribed to at least one
        duplicate of this bug, otherwise False.
        """

    def isMuted(person):
        """Does person have a muted subscription on this bug?

        :returns: True if the user has muted all email from this bug.
        """

    def getDirectSubscriptions():
        """A sequence of IBugSubscriptions directly linked to this bug."""

    def getDirectSubscribers(recipients=None, level=None):
        """A list of IPersons that are directly subscribed to this bug.

        Direct subscribers have an entry in the BugSubscription table.
        """

    def getDirectSubscribersWithDetails():
        """Get direct subscribers and their subscriptions for the bug.

        Those with muted bug subscriptions are excluded from results.

        :returns: A ResultSet of tuples (Person, BugSubscription)
            representing a subscriber and their bug subscription.
        """

    def getIndirectSubscribers(recipients=None, level=None):
        """Return IPersons that are indirectly subscribed to this bug.

        Indirect subscribers get bugmail, but don't have an entry in the
        BugSubscription table. This subscribers from dupes, etc.
        """

    def getAlsoNotifiedSubscribers(recipients=None, level=None):
        """Return IPersons in the "Also notified" subscriber list.

        This includes assignees, but not subscribers from duplicates.
        """

    def getSubscriptionsFromDuplicates():
        """Return IBugSubscriptions subscribed from dupes of this bug."""

    def getSubscribersFromDuplicates():
        """Return IPersons subscribed from dupes of this bug."""

    def getSubscribersForPerson(person):
        """Find the persons or teams by which person is subscribed.

        This call should be quite cheap to make and performs a single query.

        :return: An IResultSet.
        """

    def getSubscriptionForPerson(person):
        """Return the `BugSubscription` for a `Person` to this `Bug`.

        If no such `BugSubscription` exists, return None.
        """

    def getSubscriptionInfo(level=None):
        """Return a `BugSubscriptionInfo` at the given `level`.

        :param level: A member of `BugNotificationLevel`. Defaults to
            `BugSubscriptionLevel.LIFECYCLE` if unspecified.
        """

    def getBugNotificationRecipients(level=BugNotificationLevel.LIFECYCLE):
        """Return a complete INotificationRecipientSet instance.

        The INotificationRecipientSet instance will contain details of
        all recipients for bug notifications sent by this bug; this
        includes email addresses and textual and header-ready
        rationales. See `BugNotificationRecipients` for
        details of this implementation.
        """

    def clearBugNotificationRecipientsCache():
        """Clear the bug notification recipient BugNotificationLevel cache.

        Call this when a change to a bug or bugtask would change the
        notification recipients. Changing a a bugtask's milestone or
        target is such a case.
        """

    def canBeAQuestion():
        """Return True of False if a question can be created from this bug.

        A Question can be created from a bug if:
        1. There is only one bugtask with a status of New, Incomplete,
           Confirmed, or Wont Fix. Any other bugtasks must be Invalid.
        2. The bugtask's target uses Launchpad to track bugs.
        3. The bug was not made into a question previously.
        """

    def getQuestionCreatedFromBug():
        """Return the question created from this Bug, or None."""

    def getMessagesForView(slice_info):
        """Return BugMessage,Message,MessageChunks for renderinger.

        This eager loads message.owner validity associated with the
        bugmessages.

        :param slice_info: Either None or a list of slices to constraint the
            returned rows. The step parameter in each slice is ignored.
        """

    @operation_parameters(target=Reference(schema=Interface,
                                           title=_('Target')))
    @export_read_operation()
    def canBeNominatedFor(target):
        """Can this bug nominated for this target?

        :nomination_target: An IDistroSeries or IProductSeries.

        Returns True or False.
        """

    @operation_parameters(target=Reference(schema=Interface,
                                           title=_('Target')))
    @operation_returns_entry(Interface)
    @export_read_operation()
    def getNominationFor(target):
        """Return the IBugNomination for the target.

        If no nomination is found, a NotFoundError is raised.

        :param nomination_target: An IDistroSeries or IProductSeries.
        """

    @operation_parameters(
        target=Reference(schema=Interface, title=_('Target'), required=False),
        nominations=List(
            title=_("Nominations to search through."),
            value_type=Reference(schema=Interface),  # IBugNomination
            required=False))
    @operation_returns_collection_of(Interface)  # IBugNomination
    @export_read_operation()
    def getNominations(target=None, nominations=None):
        """Return a list of all IBugNominations for this bug.

        The list is ordered by IBugNominations.target.bugtargetdisplayname.

        :param target: An IProduct or IDistribution. Only nominations
            for this target are returned.
        :param nominations: The list of nominations to search through.
            If none is given, the bug's nominations are looked through.
            This can be useful when having to call this method multiple
            times, to avoid getting the list of nominations each time.
        """

    def getBugWatch(bugtracker, remote_bug):
        """Return the BugWatch that has the given bugtracker and remote bug.

        Return None if this bug doesn't have such a bug watch.
        """

    def getBugTask(target):
        """Return the bugtask with the specified target.

        Return None if no such bugtask is found.
        """

    def getBugTasksByPackageName(bugtasks):
        """Return a mapping from `ISourcePackageName` to its bug tasks.

        This mapping is suitable to pass as the bugtasks_by_package
        cache to getConjoinedMaster().

        The mapping is from a `ISourcePackageName` to all the bug tasks
        that are targeted to such a package name, no matter which
        distribution or distro series it is.

        All the tasks that don't have a package will be available under
        None.
        """

    @call_with(user=REQUEST_USER)
    @export_write_operation()
    def isUserAffected(user):
        """Is :user: marked as affected by this bug?"""

    def userCanSetCommentVisibility(user):
        """Return True if `user` can set bug comment visibility.

        This method is called by security adapters for authenticated users.

        Users who can set bug comment visibility are:
        - Admins and registry admins
        - users in project roles on any bugtask:
          - maintainer
          - driver
          - bug supervisor

        Additionally, the comment owners can hide their own comments but that
        is not checked here - this method is to see if arbitrary users can
        hide comments they did not make themselves.

        """

    @call_with(user=REQUEST_USER)
    @operation_returns_collection_of(Interface)
    @export_read_operation()
    def getHWSubmissions(user=None):
        """Return HWDB submissions linked to this bug.

        :return: A sequence of HWDB submissions linked to this bug.
        :param user: The user making the request.

        Only those submissions are returned which the user can access.
        Public submissions are always included; private submisisons only
        if the user is the owner or an admin.
        """

    @operation_parameters(days_old=Int(
        title=_('Number of days of inactivity for which to check.'),
        required=False))
    @export_read_operation()
    def isExpirable(days_old=None):
        """Is this bug eligible for expiration and was it last updated
        more than X days ago?

        If days_old is None the default number of days without activity
        is used.

        Returns True or False.
        """

    def getActivityForDateRange(start_date, end_date):
        """Return all the `IBugActivity` for this bug in a date range.

        :param start_date: The earliest date for which activity can be
            returned.
        :param end_date: The latest date for which activity can be
            returned.
        """

    def shouldConfirmBugtasks():
        """Should we try to confirm this bug's bugtasks?

        Return True if more than one user is affected."""

    def maybeConfirmBugtasks():
        """Maybe try to confirm our new bugtasks."""

    def personIsDirectSubscriber(person):
        """Return True if the person is a direct subscriber to this `IBug`.

        Otherwise, return False.
        """

    def personIsAlsoNotifiedSubscriber(person):
        """Return True if the person is an indirect subscriber to this `IBug`.

        Otherwise, return False.
        """

    def personIsSubscribedToDuplicate(person):
        """Return True if the person subscribed to a duplicate of this `IBug`.

        Otherwise, return False.
        """

    def getAllowedInformationTypes(user):
        """Get a list of acceptable `InformationType`s for this bug.
コード例 #9
0
ファイル: buglink.py プロジェクト: pombredanne/launchpad-3
class IBugLinkForm(Interface):
    """Schema for the unlink bugs form."""

    bug = BugField(
        title=_('Bug ID'), required=True)
コード例 #10
0
ファイル: bugtask.py プロジェクト: pombreda/UnnaturalCodeFork
class IBugTask(IHasDateCreated, IHasBug, IBugTaskDelete):
    """A bug needing fixing in a particular product or package."""
    export_as_webservice_entry()

    id = Int(title=_("Bug Task #"))
    bug = exported(
        BugField(title=_("Bug"), readonly=True))
    product = Choice(
        title=_('Project'), required=False, vocabulary='Product')
    productID = Attribute('The product ID')
    productseries = Choice(
        title=_('Series'), required=False, vocabulary='ProductSeries')
    productseriesID = Attribute('The product series ID')
    sourcepackagename = Choice(
        title=_("Package"), required=False,
        vocabulary='SourcePackageName')
    sourcepackagenameID = Attribute('The sourcepackagename ID')
    distribution = Choice(
        title=_("Distribution"), required=False, vocabulary='Distribution')
    distributionID = Attribute('The distribution ID')
    distroseries = Choice(
        title=_("Series"), required=False,
        vocabulary='DistroSeries')
    distroseriesID = Attribute('The distroseries ID')
    milestone = exported(ReferenceChoice(
        title=_('Milestone'),
        required=False,
        readonly=True,
        vocabulary='BugTaskMilestone',
        schema=Interface))  # IMilestone
    milestoneID = Attribute('The id of the milestone.')

    # The status and importance's vocabularies do not
    # contain an UNKNOWN item in bugtasks that aren't linked to a remote
    # bugwatch; this would be better described in a separate interface,
    # but adding a marker interface during initialization is expensive,
    # and adding it post-initialization is not trivial.
    # Note that status is a property because the model only exposes INCOMPLETE
    # but the DB stores INCOMPLETE_WITH_RESPONSE and
    # INCOMPLETE_WITHOUT_RESPONSE for query efficiency.
    status = exported(
        Choice(title=_('Status'), vocabulary=BugTaskStatus,
               default=BugTaskStatus.NEW, readonly=True))
    _status = Attribute('The actual status DB column used in queries.')
    importance = exported(
        Choice(title=_('Importance'), vocabulary=BugTaskImportance,
               default=BugTaskImportance.UNDECIDED, readonly=True))
    assignee = exported(
        PersonChoice(
            title=_('Assigned to'), required=False,
            vocabulary='ValidAssignee',
            readonly=True))
    assigneeID = Int(title=_('The assignee ID (for eager loading)'))
    bugtargetdisplayname = exported(
        Text(title=_("The short, descriptive name of the target"),
             readonly=True),
        exported_as='bug_target_display_name')
    bugtargetname = exported(
        Text(title=_("The target as presented in mail notifications"),
             readonly=True),
        exported_as='bug_target_name')
    bugwatch = exported(
        ReferenceChoice(
            title=_("Remote Bug Details"), required=False,
            schema=IBugWatch,
            vocabulary='BugWatch', description=_(
                "Select the bug watch that "
                "represents this task in the relevant bug tracker. If none "
                "of the bug watches represents this particular bug task, "
                "leave it as (None). Linking the remote bug watch with the "
                "task in this way means that a change in the remote bug "
                "status will change the status of this bug task in "
                "Launchpad.")),
        exported_as='bug_watch')
    date_assigned = exported(
        Datetime(title=_("Date Assigned"),
                 description=_("The date on which this task was assigned "
                               "to someone."),
                 readonly=True,
                 required=False))
    datecreated = exported(
        Datetime(title=_("Date Created"),
                 description=_("The date on which this task was created."),
                 readonly=True),
        exported_as='date_created')
    date_confirmed = exported(
        Datetime(title=_("Date Confirmed"),
                 description=_("The date on which this task was marked "
                               "Confirmed."),
                 readonly=True,
                 required=False))
    date_incomplete = exported(
        Datetime(title=_("Date Incomplete"),
                 description=_("The date on which this task was marked "
                               "Incomplete."),
                 readonly=True,
                 required=False))
    date_inprogress = exported(
        Datetime(title=_("Date In Progress"),
                 description=_("The date on which this task was marked "
                               "In Progress."),
                 readonly=True,
                 required=False),
        exported_as='date_in_progress')
    date_closed = exported(
        Datetime(title=_("Date Closed"),
                 description=_("The date on which this task was marked "
                               "either Won't Fix, Invalid or Fix Released."),
                 readonly=True,
                 required=False))
    date_left_new = exported(
        Datetime(title=_("Date left new"),
                 description=_("The date on which this task was marked "
                               "with a status higher than New."),
                 readonly=True,
                 required=False))
    date_triaged = exported(
        Datetime(title=_("Date Triaged"),
                 description=_("The date on which this task was marked "
                               "Triaged."),
                 readonly=True,
                 required=False))
    date_fix_committed = exported(
        Datetime(title=_("Date Fix Committed"),
                 description=_("The date on which this task was marked "
                               "Fix Committed."),
                 readonly=True,
                 required=False))
    date_fix_released = exported(
        Datetime(title=_("Date Fix Released"),
                 description=_("The date on which this task was marked "
                               "Fix Released."),
                 readonly=True,
                 required=False))
    date_left_closed = exported(
        Datetime(title=_("Date left closed"),
                 description=_("The date on which this task was "
                               "last reopened."),
                 readonly=True,
                 required=False))
    age = Datetime(title=_("Age"),
                   description=_("The age of this task, expressed as the "
                                 "length of time between the creation date "
                                 "and now."))
    task_age = Int(title=_("Age of the bug task"),
            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=Interface, readonly=True))
    target = exported(Reference(
        title=_('Target'), required=True, schema=Interface,  # IBugTarget
        readonly=True,
        description=_("The software in which this bug should be fixed.")))
    title = exported(
        Text(title=_("The title of the bug related to this bugtask"),
             readonly=True))
    related_tasks = exported(
        CollectionField(
            description=_(
                "IBugTasks related to this one, namely other "
                "IBugTasks on the same IBug."),
            value_type=Reference(schema=Interface),  # Will be specified later
            readonly=True))
    pillar = Choice(
        title=_('Pillar'),
        description=_("The LP pillar (product or distribution) "
                      "associated with this task."),
        vocabulary='DistributionOrProduct', readonly=True)
    other_affected_pillars = Attribute(
        "The other pillars (products or distributions) affected by this bug. "
        "This returns a list of pillars OTHER THAN the pillar associated "
        "with this particular bug.")
    # This property does various database queries. It is a property so a
    # "snapshot" of its value will be taken when a bugtask is modified, which
    # allows us to compare it to the current value and see if there are any
    # new subscribers that should get an email containing full bug details
    # (rather than just the standard change mail.) It is a property on
    # IBugTask because we currently only ever need this value for events
    # handled on IBugTask.
    bug_subscribers = Field(
        title=_("A list of IPersons subscribed to the bug, whether directly "
                "or indirectly."), readonly=True)

    conjoined_master = Attribute(
        "The series-specific bugtask in a conjoined relationship")
    conjoined_slave = Attribute(
        "The generic bugtask in a conjoined relationship")

    is_complete = exported(
        Bool(description=_(
                "True or False depending on whether or not there is more "
                "work required on this bug task."),
             readonly=True))

    @operation_returns_collection_of(Interface)  # Actually IBug.
    @call_with(user=REQUEST_USER, limit=10)
    @export_read_operation()
    def findSimilarBugs(user, limit=10):
        """Return the list of possible duplicates for this BugTask."""

    @call_with(user=REQUEST_USER)
    @operation_parameters(person=copy_field(assignee))
    @export_read_operation()
    @operation_for_version("devel")
    def getContributorInfo(user, person):
        """Is the person a contributor to bugs in this task's pillar?

        :param user: The user doing the search. Private bugs that this
            user doesn't have access to won't be included in the search.
        :param person: The person to check to see if they are a contributor.

        Return a dict with the following values:
        is_contributor: True if the user has any bugs assigned to him in the
        context of this bug task's pillar, either directly or by team
        participation.
        person_name: the displayname of the person
        pillar_name: the displayname of the bug task's pillar

        This API call is provided for use by the client Javascript where the
        calling context does not have access to the person or pillar names.
        """

    def getConjoinedMaster(bugtasks, bugtasks_by_package=None):
        """Return the conjoined master in the given bugtasks, if any.

        :param bugtasks: The bugtasks to be considered when looking for
            the conjoined master.
        :param bugtasks_by_package: A cache, mapping a
            `ISourcePackageName` to a list of bug tasks targeted to such
            a package name. Both distribution and distro series tasks
            should be included in this list.

        This method exists mainly to allow calculating the conjoined
        master from a cached list of bug tasks, reducing the number of
        db queries needed.
        """

    def subscribe(person, subscribed_by):
        """Subscribe this person to the underlying bug.

        This method was documented as being required here so that
        MentorshipOffers could happen on IBugTask. If that was the sole reason
        this method should be deletable. When we move to context-less bug
        presentation (where the bug is at /bugs/n?task=ubuntu) then we can
        eliminate this if it is no longer useful.
        """

    def isSubscribed(person):
        """Return True if the person is an explicit subscriber to the
        underlying bug for this bugtask.

        This method was documented as being required here so that
        MentorshipOffers could happen on IBugTask. If that was the sole
        reason then this method should be deletable.  When we move to
        context-less bug presentation (where the bug is at
        /bugs/n?task=ubuntu) then we can eliminate this if it is no
        longer useful.
        """

    @mutator_for(milestone)
    @rename_parameters_as(new_milestone='milestone')
    @operation_parameters(new_milestone=copy_field(milestone))
    @call_with(user=REQUEST_USER)
    @export_write_operation()
    def transitionToMilestone(new_milestone, user):
        """Set the BugTask milestone.

        Set the bugtask milestone, making sure that the user is
        authorised to do so.
        """

    @mutator_for(importance)
    @rename_parameters_as(new_importance='importance')
    @operation_parameters(new_importance=copy_field(importance))
    @call_with(user=REQUEST_USER)
    @export_write_operation()
    def transitionToImportance(new_importance, user):
        """Set the BugTask importance.

        Set the bugtask importance, making sure that the user is
        authorised to do so.
        """

    def canTransitionToStatus(new_status, user):
        """Return True if the user is allowed to change the status to
        `new_status`.

        :new_status: new status from `BugTaskStatus`
        :user: the user requesting the change

        Some status transitions, e.g. Triaged, require that the user
        be a bug supervisor or the owner of the project.
        """

    @mutator_for(status)
    @rename_parameters_as(new_status='status')
    @operation_parameters(
        new_status=copy_field(status))
    @call_with(user=REQUEST_USER)
    @export_write_operation()
    def transitionToStatus(new_status, user):
        """Perform a workflow transition to the new_status.

        :new_status: new status from `BugTaskStatus`
        :user: the user requesting the change

        For certain statuses, e.g. Confirmed, other actions will
        happen, like recording the date when the task enters this
        status.

        Some status transitions require extra conditions to be met.
        See `canTransitionToStatus` for more details.
        """

    def userCanSetAnyAssignee(user):
        """Check if the current user can set anybody sa a bugtask assignee.

        Owners, drivers, bug supervisors and Launchpad admins can always
        assign to someone else.  Other users can assign to someone else if a
        bug supervisor is not defined.
        """

    def userCanUnassign(user):
        """Check if the current user can set assignee to None."""

    @mutator_for(assignee)
    @operation_parameters(assignee=copy_field(assignee))
    @export_write_operation()
    def transitionToAssignee(assignee, validate=True):
        """Perform a workflow transition to the given assignee.

        When the bugtask assignee is changed from None to an IPerson
        object, the date_assigned is set on the task. If the assignee
        value is set to None, date_assigned is also set to None.
        """

    def validateTransitionToTarget(target):
        """Check whether a transition to this target is legal.

        :raises IllegalTarget: if the new target is not allowed.
        """

    @mutator_for(target)
    @call_with(user=REQUEST_USER)
    @operation_parameters(
        target=copy_field(target))
    @export_write_operation()
    def transitionToTarget(target, user):
        """Convert the bug task to a different bug target."""

    def updateTargetNameCache():
        """Update the targetnamecache field in the database.

        This method is meant to be called when an IBugTask is created or
        modified and will also be called from the update_stats.py cron script
        to ensure that the targetnamecache is properly updated when, for
        example, an IDistribution is renamed.
        """

    def asEmailHeaderValue():
        """Return a value suitable for an email header value for this bugtask.

        The return value is a single line of arbitrary length, so header
        folding should be done by the callsite, as needed.

        For an upstream task, this value might look like:

          product=firefox; status=New; importance=Critical; assignee=None;

        See doc/bugmail-headers.txt for a complete explanation and more
        examples.
        """

    def getDelta(old_task):
        """Compute the delta from old_task to this task.

        Returns an IBugTaskDelta or None if there were no changes between
        old_task and this task.
        """

    def getPackageComponent():
        """Return the task's package's component or None.

        Returns the component associated to the current published
        package in that distribution's current series. If the task is
        not a package task, returns None.
        """

    def userHasDriverPrivileges(user):
        """Does the user have driver privileges on the current bugtask?

        :return: A boolean.
        """

    def userHasBugSupervisorPrivileges(user):
        """Is the user privileged and allowed to change details on a bug?