Jeff Mahoney 3dff52
# Copyright (C) 2007, 2008, 2009, 2010 Red Hat Inc.
Jeff Mahoney 3dff52
# Author: Will Woods <wwoods@redhat.com>
Jeff Mahoney 3dff52
#
Michal Koutný ccf7f1
# This work is licensed under the GNU GPLv2 or later.
Michal Koutný ccf7f1
# See the COPYING file in the top-level directory.
Michal Koutný ccf7f1
Michal Koutný ccf7f1
import copy
Jeff Mahoney 3dff52
from logging import getLogger
Michal Koutný ccf7f1
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
log = getLogger(__name__)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
class Bug(object):
Michal Koutný ccf7f1
    """
Michal Koutný ccf7f1
    A container object for a bug report. Requires a Bugzilla instance -
Jeff Mahoney 3dff52
    every Bug is on a Bugzilla, obviously.
Jeff Mahoney 3dff52
    Optional keyword args:
Jeff Mahoney 3dff52
        dict=DICT   - populate attributes with the result of a getBug() call
Jeff Mahoney 3dff52
        bug_id=ID   - if dict does not contain bug_id, this is required before
Jeff Mahoney 3dff52
                      you can read any attributes or make modifications to this
Jeff Mahoney 3dff52
                      bug.
Michal Koutný ccf7f1
    """
Jeff Mahoney 3dff52
    def __init__(self, bugzilla, bug_id=None, dict=None, autorefresh=False):
Jeff Mahoney 3dff52
        # pylint: disable=redefined-builtin
Jeff Mahoney 3dff52
        # API had pre-existing issue that we can't change ('dict' usage)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        self.bugzilla = bugzilla
Michal Koutný ccf7f1
        self._rawdata = {}
Jeff Mahoney 3dff52
        self.autorefresh = autorefresh
Jeff Mahoney 3dff52
Michal Koutný ccf7f1
        # pylint: disable=protected-access
Michal Koutný ccf7f1
        self._aliases = self.bugzilla._get_bug_aliases()
Michal Koutný ccf7f1
        # pylint: enable=protected-access
Michal Koutný ccf7f1
Jeff Mahoney 3dff52
        if not dict:
Jeff Mahoney 3dff52
            dict = {}
Jeff Mahoney 3dff52
        if bug_id:
Jeff Mahoney 3dff52
            dict["id"] = bug_id
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        self._update_dict(dict)
Jeff Mahoney 3dff52
        self.weburl = bugzilla.url.replace('xmlrpc.cgi',
Jeff Mahoney 3dff52
                                           'show_bug.cgi?id=%i' % self.bug_id)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def __str__(self):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Return a simple string representation of this bug
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        return self.__unicode__()
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def __unicode__(self):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Return a simple unicode string representation of this bug
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        return "#%-6s %-10s - %s - %s" % (self.bug_id, self.bug_status,
Jeff Mahoney 3dff52
                                          self.assigned_to, self.summary)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def __repr__(self):
Michal Koutný ccf7f1
        url = ""
Michal Koutný ccf7f1
        if self.bugzilla:
Michal Koutný ccf7f1
            url = self.bugzilla.url
Michal Koutný ccf7f1
        return '<Bug #%i on %s at %#x>' % (self.bug_id, url, id(self))
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def __getattr__(self, name):
Jeff Mahoney 3dff52
        refreshed = False
Jeff Mahoney 3dff52
        while True:
Jeff Mahoney 3dff52
            if refreshed and name in self.__dict__:
Jeff Mahoney 3dff52
                # If name was in __dict__ to begin with, __getattr__ would
Jeff Mahoney 3dff52
                # have never been called.
Jeff Mahoney 3dff52
                return self.__dict__[name]
Jeff Mahoney 3dff52
Michal Koutný ccf7f1
            for newname, oldname in self._aliases:
Jeff Mahoney 3dff52
                if name == oldname and newname in self.__dict__:
Jeff Mahoney 3dff52
                    return self.__dict__[newname]
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
            # Doing dir(bugobj) does getattr __members__/__methods__,
Jeff Mahoney 3dff52
            # don't refresh for those
Jeff Mahoney 3dff52
            if name.startswith("__") and name.endswith("__"):
Jeff Mahoney 3dff52
                break
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
            if refreshed or not self.autorefresh:
Jeff Mahoney 3dff52
                break
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
            log.info("Bug %i missing attribute '%s' - doing implicit "
Jeff Mahoney 3dff52
                "refresh(). This will be slow, if you want to avoid "
Jeff Mahoney 3dff52
                "this, properly use query/getbug include_fields, and "
Jeff Mahoney 3dff52
                "set bugzilla.bug_autorefresh = False to force failure.",
Jeff Mahoney 3dff52
                self.bug_id, name)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
            # We pass the attribute name to getbug, since for something like
Jeff Mahoney 3dff52
            # 'attachments' which downloads lots of data we really want the
Jeff Mahoney 3dff52
            # user to opt in.
Jeff Mahoney 3dff52
            self.refresh(extra_fields=[name])
Jeff Mahoney 3dff52
            refreshed = True
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        msg = ("Bug object has no attribute '%s'." % name)
Jeff Mahoney 3dff52
        if not self.autorefresh:
Jeff Mahoney 3dff52
            msg += ("\nIf '%s' is a bugzilla attribute, it may not have "
Jeff Mahoney 3dff52
                    "been cached when the bug was fetched. You may want "
Jeff Mahoney 3dff52
                    "to adjust your include_fields for getbug/query." % name)
Jeff Mahoney 3dff52
        raise AttributeError(msg)
Jeff Mahoney 3dff52
Michal Koutný ccf7f1
    def get_raw_data(self):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Return the raw API dictionary data that has been used to
Michal Koutný ccf7f1
        populate this bug
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        return copy.deepcopy(self._rawdata)
Michal Koutný ccf7f1
Jeff Mahoney 3dff52
    def refresh(self, include_fields=None, exclude_fields=None,
Jeff Mahoney 3dff52
        extra_fields=None):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Refresh the bug with the latest data from bugzilla
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        # pylint: disable=protected-access
Michal Koutný ccf7f1
        extra_fields = list(self._rawdata.keys()) + (extra_fields or [])
Jeff Mahoney 3dff52
        r = self.bugzilla._getbug(self.bug_id,
Jeff Mahoney 3dff52
            include_fields=include_fields, exclude_fields=exclude_fields,
Michal Koutný ccf7f1
            extra_fields=extra_fields)
Jeff Mahoney 3dff52
        # pylint: enable=protected-access
Jeff Mahoney 3dff52
        self._update_dict(r)
Jeff Mahoney 3dff52
    reload = refresh
Jeff Mahoney 3dff52
Michal Koutný ccf7f1
    def _translate_dict(self, newdict):
Jeff Mahoney 3dff52
        if self.bugzilla:
Jeff Mahoney 3dff52
            self.bugzilla.post_translation({}, newdict)
Jeff Mahoney 3dff52
Michal Koutný ccf7f1
        for newname, oldname in self._aliases:
Michal Koutný ccf7f1
            if oldname not in newdict:
Michal Koutný ccf7f1
                continue
Michal Koutný ccf7f1
Michal Koutný ccf7f1
            if newname not in newdict:
Michal Koutný ccf7f1
                newdict[newname] = newdict[oldname]
Michal Koutný ccf7f1
            elif newdict[newname] != newdict[oldname]:
Michal Koutný ccf7f1
                log.debug("Update dict contained differing alias values "
Michal Koutný ccf7f1
                          "d[%s]=%s and d[%s]=%s , dropping the value "
Michal Koutný ccf7f1
                          "d[%s]", newname, newdict[newname], oldname,
Michal Koutný ccf7f1
                        newdict[oldname], oldname)
Michal Koutný ccf7f1
            del(newdict[oldname])
Michal Koutný ccf7f1
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def _update_dict(self, newdict):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Update internal dictionary, in a way that ensures no duplicate
Michal Koutný ccf7f1
        entries are stored WRT field aliases
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        self._translate_dict(newdict)
Michal Koutný ccf7f1
        self._rawdata.update(newdict)
Jeff Mahoney 3dff52
        self.__dict__.update(newdict)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        if 'id' not in self.__dict__ and 'bug_id' not in self.__dict__:
Jeff Mahoney 3dff52
            raise TypeError("Bug object needs a bug_id")
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    ##################
Jeff Mahoney 3dff52
    # pickle helpers #
Jeff Mahoney 3dff52
    ##################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def __getstate__(self):
Michal Koutný ccf7f1
        ret = self._rawdata.copy()
Michal Koutný ccf7f1
        ret["_aliases"] = self._aliases
Jeff Mahoney 3dff52
        return ret
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def __setstate__(self, vals):
Michal Koutný ccf7f1
        self._rawdata = {}
Jeff Mahoney 3dff52
        self.bugzilla = None
Michal Koutný ccf7f1
        self._aliases = vals.get("_aliases", [])
Michal Koutný ccf7f1
        self.autorefresh = False
Jeff Mahoney 3dff52
        self._update_dict(vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    #####################
Jeff Mahoney 3dff52
    # Modify bug status #
Jeff Mahoney 3dff52
    #####################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def setstatus(self, status, comment=None, private=False):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Update the status for this bug report.
Jeff Mahoney 3dff52
        Commonly-used values are ASSIGNED, MODIFIED, and NEEDINFO.
Jeff Mahoney 3dff52
Michal Koutný 029c1e
        To change bugs to RESOLVED, use .close() instead.
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        # Note: fedora bodhi uses this function
Jeff Mahoney 3dff52
        vals = self.bugzilla.build_update(status=status,
Jeff Mahoney 3dff52
                                          comment=comment,
Jeff Mahoney 3dff52
                                          comment_private=private)
Jeff Mahoney 3dff52
        log.debug("setstatus: update=%s", vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs(self.bug_id, vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def close(self, resolution, dupeid=None, fixedin=None,
Jeff Mahoney 3dff52
              comment=None, isprivate=False):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Close this bug.
Jeff Mahoney 3dff52
        Valid values for resolution are in bz.querydefaults['resolution_list']
Jeff Mahoney 3dff52
        For bugzilla.redhat.com that's:
Jeff Mahoney 3dff52
        ['NOTABUG', 'WONTFIX', 'DEFERRED', 'WORKSFORME', 'CURRENTRELEASE',
Jeff Mahoney 3dff52
         'RAWHIDE', 'ERRATA', 'DUPLICATE', 'UPSTREAM', 'NEXTRELEASE',
Jeff Mahoney 3dff52
         'CANTFIX', 'INSUFFICIENT_DATA']
Jeff Mahoney 3dff52
        If using DUPLICATE, you need to set dupeid to the ID of the other bug.
Jeff Mahoney 3dff52
        If using WORKSFORME/CURRENTRELEASE/RAWHIDE/ERRATA/UPSTREAM/NEXTRELEASE
Jeff Mahoney 3dff52
          you can (and should) set 'new_fixed_in' to a string representing the
Jeff Mahoney 3dff52
          version that fixes the bug.
Jeff Mahoney 3dff52
        You can optionally add a comment while closing the bug. Set 'isprivate'
Jeff Mahoney 3dff52
          to True if you want that comment to be private.
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        # Note: fedora bodhi uses this function
Jeff Mahoney 3dff52
        vals = self.bugzilla.build_update(comment=comment,
Jeff Mahoney 3dff52
                                          comment_private=isprivate,
Jeff Mahoney 3dff52
                                          resolution=resolution,
Jeff Mahoney 3dff52
                                          dupe_of=dupeid,
Jeff Mahoney 3dff52
                                          fixed_in=fixedin,
Michal Koutný 029c1e
                                          status=str("RESOLVED"))
Jeff Mahoney 3dff52
        log.debug("close: update=%s", vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs(self.bug_id, vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    #####################
Jeff Mahoney 3dff52
    # Modify bug emails #
Jeff Mahoney 3dff52
    #####################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def setassignee(self, assigned_to=None,
Jeff Mahoney 3dff52
                    qa_contact=None, comment=None):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Set any of the assigned_to or qa_contact fields to a new
Jeff Mahoney 3dff52
        bugzilla account, with an optional comment, e.g.
Jeff Mahoney 3dff52
        setassignee(assigned_to='wwoods@redhat.com')
Jeff Mahoney 3dff52
        setassignee(qa_contact='wwoods@redhat.com', comment='wwoods QA ftw')
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        You must set at least one of the two assignee fields, or this method
Jeff Mahoney 3dff52
        will throw a ValueError.
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        Returns [bug_id, mailresults].
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        if not (assigned_to or qa_contact):
Jeff Mahoney 3dff52
            raise ValueError("You must set one of assigned_to "
Jeff Mahoney 3dff52
                             " or qa_contact")
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        vals = self.bugzilla.build_update(assigned_to=assigned_to,
Jeff Mahoney 3dff52
                                          qa_contact=qa_contact,
Jeff Mahoney 3dff52
                                          comment=comment)
Jeff Mahoney 3dff52
        log.debug("setassignee: update=%s", vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs(self.bug_id, vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def addcc(self, cclist, comment=None):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Adds the given email addresses to the CC list for this bug.
Jeff Mahoney 3dff52
        cclist: list of email addresses (strings)
Jeff Mahoney 3dff52
        comment: optional comment to add to the bug
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        vals = self.bugzilla.build_update(comment=comment,
Jeff Mahoney 3dff52
                                          cc_add=cclist)
Jeff Mahoney 3dff52
        log.debug("addcc: update=%s", vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs(self.bug_id, vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def deletecc(self, cclist, comment=None):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Removes the given email addresses from the CC list for this bug.
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        vals = self.bugzilla.build_update(comment=comment,
Jeff Mahoney 3dff52
                                          cc_remove=cclist)
Jeff Mahoney 3dff52
        log.debug("deletecc: update=%s", vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs(self.bug_id, vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    ####################
Jeff Mahoney 3dff52
    # comment handling #
Jeff Mahoney 3dff52
    ####################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def addcomment(self, comment, private=False):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Add the given comment to this bug. Set private to True to mark this
Jeff Mahoney 3dff52
        comment as private.
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        # Note: fedora bodhi uses this function
Jeff Mahoney 3dff52
        vals = self.bugzilla.build_update(comment=comment,
Jeff Mahoney 3dff52
                                          comment_private=private)
Jeff Mahoney 3dff52
        log.debug("addcomment: update=%s", vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs(self.bug_id, vals)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def getcomments(self):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Returns an array of comment dictionaries for this bug
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        comment_list = self.bugzilla.get_comments([self.bug_id])
Jeff Mahoney 3dff52
        return comment_list['bugs'][str(self.bug_id)]['comments']
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    #####################
Jeff Mahoney 3dff52
    # Get/Set bug flags #
Jeff Mahoney 3dff52
    #####################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def get_flag_type(self, name):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Return flag_type information for a specific flag
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        Older RHBugzilla returned a lot more info here, but it was
Jeff Mahoney 3dff52
        non-upstream and is now gone.
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        for t in self.flags:
Jeff Mahoney 3dff52
            if t['name'] == name:
Jeff Mahoney 3dff52
                return t
Jeff Mahoney 3dff52
        return None
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def get_flags(self, name):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Return flag value information for a specific flag
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        ft = self.get_flag_type(name)
Jeff Mahoney 3dff52
        if not ft:
Jeff Mahoney 3dff52
            return None
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return [ft]
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def get_flag_status(self, name):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Return a flag 'status' field
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        This method works only for simple flags that have only a 'status' field
Jeff Mahoney 3dff52
        with no "requestee" info, and no multiple values. For more complex
Jeff Mahoney 3dff52
        flags, use get_flags() to get extended flag value information.
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        f = self.get_flags(name)
Jeff Mahoney 3dff52
        if not f:
Jeff Mahoney 3dff52
            return None
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        # This method works only for simple flags that have only one
Jeff Mahoney 3dff52
        # value set.
Jeff Mahoney 3dff52
        assert len(f) <= 1
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        return f[0]['status']
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def updateflags(self, flags):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Thin wrapper around build_update(flags=X). This only handles simple
Jeff Mahoney 3dff52
        status changes, anything like needinfo requestee needs to call
Jeff Mahoney 3dff52
        build_update + update_bugs directly
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        :param flags: Dictionary of the form {"flagname": "status"}, example
Jeff Mahoney 3dff52
            {"needinfo": "?", "devel_ack": "+"}
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        flaglist = []
Jeff Mahoney 3dff52
        for key, value in flags.items():
Jeff Mahoney 3dff52
            flaglist.append({"name": key, "status": value})
Jeff Mahoney 3dff52
        return self.bugzilla.update_bugs([self.bug_id],
Jeff Mahoney 3dff52
            self.bugzilla.build_update(flags=flaglist))
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    ########################
Jeff Mahoney 3dff52
    # Experimental methods #
Jeff Mahoney 3dff52
    ########################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def get_attachments(self, include_fields=None, exclude_fields=None):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Helper call to Bugzilla.get_attachments. If you want to fetch
Jeff Mahoney 3dff52
        specific attachment IDs, use that function instead
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        if "attachments" in self.__dict__:
Jeff Mahoney 3dff52
            return self.attachments
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        data = self.bugzilla.get_attachments([self.bug_id], None,
Jeff Mahoney 3dff52
                include_fields, exclude_fields)
Jeff Mahoney 3dff52
        return data["bugs"][str(self.bug_id)]
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def get_attachment_ids(self):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Helper function to return only the attachment IDs for this bug
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        return [a["id"] for a in self.get_attachments(exclude_fields=["data"])]
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def get_history_raw(self):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        Experimental. Get the history of changes for this bug.
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        return self.bugzilla.bugs_history_raw([self.bug_id])
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
class User(object):
Michal Koutný ccf7f1
    """
Michal Koutný ccf7f1
    Container object for a bugzilla User.
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    :arg bugzilla: Bugzilla instance that this User belongs to.
Jeff Mahoney 3dff52
    Rest of the params come straight from User.get()
Michal Koutný ccf7f1
    """
Jeff Mahoney 3dff52
    def __init__(self, bugzilla, **kwargs):
Jeff Mahoney 3dff52
        self.bugzilla = bugzilla
Jeff Mahoney 3dff52
        self.__userid = kwargs.get('id')
Jeff Mahoney 3dff52
        self.__name = kwargs.get('name')
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        self.__email = kwargs.get('email', self.__name)
Jeff Mahoney 3dff52
        self.__can_login = kwargs.get('can_login', False)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        self.real_name = kwargs.get('real_name', None)
Jeff Mahoney 3dff52
        self.password = None
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        self.groups = kwargs.get('groups', {})
Jeff Mahoney 3dff52
        self.groupnames = []
Jeff Mahoney 3dff52
        for g in self.groups:
Jeff Mahoney 3dff52
            if "name" in g:
Jeff Mahoney 3dff52
                self.groupnames.append(g["name"])
Jeff Mahoney 3dff52
        self.groupnames.sort()
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    ########################
Jeff Mahoney 3dff52
    # Read-only attributes #
Jeff Mahoney 3dff52
    ########################
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    # We make these properties so that the user cannot set them.  They are
Jeff Mahoney 3dff52
    # unaffected by the update() method so it would be misleading to let them
Jeff Mahoney 3dff52
    # be changed.
Jeff Mahoney 3dff52
    @property
Jeff Mahoney 3dff52
    def userid(self):
Jeff Mahoney 3dff52
        return self.__userid
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    @property
Jeff Mahoney 3dff52
    def email(self):
Jeff Mahoney 3dff52
        return self.__email
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    @property
Jeff Mahoney 3dff52
    def can_login(self):
Jeff Mahoney 3dff52
        return self.__can_login
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    # name is a key in some methods.  Mark it dirty when we change it #
Jeff Mahoney 3dff52
    @property
Jeff Mahoney 3dff52
    def name(self):
Jeff Mahoney 3dff52
        return self.__name
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def refresh(self):
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        Update User object with latest info from bugzilla
Jeff Mahoney 3dff52
        """
Jeff Mahoney 3dff52
        newuser = self.bugzilla.getuser(self.email)
Jeff Mahoney 3dff52
        self.__dict__.update(newuser.__dict__)
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
    def updateperms(self, action, groups):
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        A method to update the permissions (group membership) of a bugzilla
Jeff Mahoney 3dff52
        user.
Jeff Mahoney 3dff52
Jeff Mahoney 3dff52
        :arg action: add, remove, or set
Jeff Mahoney 3dff52
        :arg groups: list of groups to be added to (i.e. ['fedora_contrib'])
Michal Koutný ccf7f1
        """
Jeff Mahoney 3dff52
        self.bugzilla.updateperms(self.name, action, groups)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
Michal Koutný ccf7f1
class Group(object):
Michal Koutný ccf7f1
    """
Michal Koutný ccf7f1
    Container object for a bugzilla Group.
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    :arg bugzilla: Bugzilla instance that this Group belongs to.
Michal Koutný ccf7f1
    Rest of the params come straight from Group.get()
Michal Koutný ccf7f1
    """
Michal Koutný ccf7f1
    def __init__(self, bugzilla, **kwargs):
Michal Koutný ccf7f1
        self.bugzilla = bugzilla
Michal Koutný ccf7f1
        self.__groupid = kwargs.get('id')
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        self.name = kwargs.get('name')
Michal Koutný ccf7f1
        self.description = kwargs.get('description', self.name)
Michal Koutný ccf7f1
        self.is_active = kwargs.get('is_active', False)
Michal Koutný ccf7f1
        self.icon_url = kwargs.get('icon_url', None)
Michal Koutný ccf7f1
        self.is_active_bug_group = kwargs.get('is_active_bug_group', None)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        self.membership = kwargs.get('membership', [])
Michal Koutný ccf7f1
        self.__member_emails = set()
Michal Koutný ccf7f1
        self._refresh_member_emails_list()
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    ########################
Michal Koutný ccf7f1
    # Read-only attributes #
Michal Koutný ccf7f1
    ########################
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    # We make these properties so that the user cannot set them.  They are
Michal Koutný ccf7f1
    # unaffected by the update() method so it would be misleading to let them
Michal Koutný ccf7f1
    # be changed.
Michal Koutný ccf7f1
    @property
Michal Koutný ccf7f1
    def groupid(self):
Michal Koutný ccf7f1
        return self.__groupid
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    @property
Michal Koutný ccf7f1
    def member_emails(self):
Michal Koutný ccf7f1
        return sorted(self.__member_emails)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def _refresh_member_emails_list(self):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Refresh the list of emails of the members of the group.
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        if self.membership:
Michal Koutný ccf7f1
            for m in self.membership:
Michal Koutný ccf7f1
                if "email" in m:
Michal Koutný ccf7f1
                    self.__member_emails.add(m["email"])
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def refresh(self, membership=False):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Update Group object with latest info from bugzilla
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        newgroup = self.bugzilla.getgroup(
Michal Koutný ccf7f1
            self.name, membership=membership)
Michal Koutný ccf7f1
        self.__dict__.update(newgroup.__dict__)
Michal Koutný ccf7f1
        self._refresh_member_emails_list()
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def members(self):
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        Retrieve the members of this Group from bugzilla
Michal Koutný ccf7f1
        """
Michal Koutný ccf7f1
        if not self.membership:
Michal Koutný ccf7f1
            self.refresh(membership=True)
Michal Koutný ccf7f1
        return self.membership