Blame bugzilla/bug.py

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