Beispiel #1
0
class Attachment(RemoteObject):

    # Attachment data.
    id = fields.Field()
    attacher = fields.Object('User')
    creation_time = Datetime(DATETIME_FORMAT_WITH_SECONDS)
    last_change_time = Datetime(DATETIME_FORMAT_WITH_SECONDS)
    description = fields.Field()
    bug_id = fields.Field()
    bug_ref = fields.Field()

    # File data.
    file_name = fields.Field()
    size = fields.Field()
    content_type = fields.Field()

    # Attachment metadata.
    flags = fields.List(fields.Object('Flag'))
    is_obsolete = StringBoolean()
    is_private = StringBoolean()
    is_patch = StringBoolean()

    # Used for submitting changes.
    token = fields.Field()
    ref = fields.Field()

    # Only with attachmentdata=1
    data = fields.Field()
    encoding = fields.Field()

    def __repr__(self):
        return '<Attachment %s: "%s">' % (self.id, self.description)

    def __hash__(self):
        return self.id
class Committee(OpenStateObject):
    id = fields.Field()
    state = fields.Field()
    chamber = fields.Field()
    committee = fields.Field()
    subcommittee = fields.Field()
    members = fields.List(fields.Object(CommitteeMember))
    sources = fields.List(fields.Object(Source))

    @classmethod
    def get(cls, id):
        """
        Get a committee.

        :param id: the committee's Open State ID (e.g. CAC000005)
        """
        func = 'committees/%s' % id
        return super(Committee, cls).get(func)

    @classmethod
    def search(cls, **kwargs):
        """
        Search comittees.

        Use keyword argmunents to filter by committee fields.
        For example, ``openstates.Committee.search(state='ca')``.
        """
        return ListOf(cls).get('committees', kwargs).entries
class Legislator(OpenStateObject):
    id = fields.Field()
    leg_id = fields.Field()
    full_name = fields.Field()
    first_name = fields.Field()
    last_name = fields.Field()
    middle_name = fields.Field()
    suffixes = fields.Field()
    votesmart_id = fields.Field()
    nimsp_id = fields.Field()
    transparencydata_id = fields.Field()
    active = fields.Field()
    chamber = fields.Field()
    district = fields.Field()
    state = fields.Field()
    party = fields.Field()
    created_at = OpenStateDatetime()
    updated_at = OpenStateDatetime()
    roles = fields.List(fields.Object(Role))
    sources = fields.List(fields.Object(Source))

    # Deprecated
    nimsp_candidate_id = fields.Field()

    @classmethod
    def get(cls, id):
        """
        Get a specific legislator.

        :param id: the legislator's Open State ID (e.g. 'TXL000139')
        """
        func = 'legislators/%s' % id
        return super(Legislator, cls).get(func)

    @classmethod
    def search(cls, **kwargs):
        """
        Search legislators.

        Use keyword arguments to filter by legislators fields.
        For example, `openstates.Legislator.search(last_name='Alesi')`.
        """
        return ListOf(cls).get('legislators', kwargs).entries

    @classmethod
    def geo(cls, lat, long):
        """
        Get all state legislators for a given lat/long pair

        :param lat: the latitude
        :param long: the longitude
        """
        func = 'legislators/geo'
        params = dict(lat=lat, long=long)
        return ListOf(cls).get(func, params).entries

    def __str__(self):
        return self.full_name
Beispiel #4
0
class Changeset(RemoteObject):

    changer = fields.Object('User')
    changes = fields.List(fields.Object('Change'))
    change_time = Datetime(DATETIME_FORMAT_WITH_SECONDS)

    def __repr__(self):
        return '<Changeset by %s on %s>' % (
            self.changer, self.change_time.strftime(DATETIME_FORMAT))
class Event(OpenStateObject):
    id = fields.Field()
    state = fields.Field()
    description = fields.Field()
    when = OpenStateDatetime()
    end = OpenStateDatetime()
    location = fields.Field()
    type = fields.Field()
    session = fields.Field()
    participants = fields.List(fields.Object(EventParticipant))
    sources = fields.List(fields.Object(Source))

    @classmethod
    def search(cls, **kwargs):
        return ListOf(cls).get('events', kwargs).entries
Beispiel #6
0
class Comment(GitHubRemoteObject):
    """A GitHub issue comment.

    `GET /repos/:user/:repo/issues/comments/:id`

    {
      "url": "https://api.github.com/repos/octocat/Hello-World/issues/comments/1",
      "body": "Me too",
      "user": {
        "login": "******",
        "id": 1,
        "avatar_url": "https://github.com/images/error/octocat_happy.gif",
        "gravatar_id": "somehexcode",
        "url": "https://api.github.com/users/octocat"
      },
      "created_at": "2011-04-14T16:00:49Z",
      "updated_at": "2011-04-14T16:00:49Z"
    }

    """

    id = fields.Field()
    url = fields.Field()
    body = fields.Field()
    user = fields.Object(User)
    created_at = fields.Field()
    updated_at = fields.Field()

    def __unicode__(self):
        return u"<Comment %s>" % self.id
Beispiel #7
0
class UserList(ListObject):

    entries = fields.List(fields.Object(User))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def get_friends(cls, http=None, **kwargs):
        return cls.get_related("friends", http=http, **kwargs)

    @classmethod
    def get_followers(cls, http=None, **kwargs):
        return cls.get_related("followers", http=http, **kwargs)

    @classmethod
    def get_related(cls, relation, http=None, **kwargs):
        url = '/statuses/%s' % relation
        if 'id' in kwargs:
            url += '/%s.json' % quote_plus(kwargs['id'])
        else:
            url += '.json'
        query = urlencode(
            filter(lambda x: x in ('screen_name', 'user_id', 'page'), kwargs))
        url = urlunsplit((None, None, url, query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)
Beispiel #8
0
class Labels(GitHubRemoteListObject):
    entries = fields.List(fields.Object(Label))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def by_repository(cls, user_name, repo_name, **kwargs):
        """Get labels by repository.

        `GET /repos/:user/:repo/labels`

        """

        url = '/repos/%s/%s/labels' % (user_name, repo_name)
        return cls.get(urljoin(GitHub.endpoint, url), **kwargs)

    @classmethod
    def get_or_create_in_repository(cls, user_name, repo_name, label_name):
        labels = cls.by_repository(user_name,
                                   repo_name,
                                   per_page=100,
                                   all_pages=True)
        for label in labels:
            if label.name == label_name:
                return label

        label = Label()
        label.name = label_name
        labels.post(label)

        return label
Beispiel #9
0
class Game(Bombject):

    id = fields.Field()
    name = fields.Field()
    api_detail_url = fields.Field()
    site_detail_url = fields.Field()

    summary = fields.Field(api_name='deck')
    description = fields.Field()
    image = fields.Object(Image)
    published = fields.Datetime(dateformat='%Y-%m-%d %H:%M:%S',
                                api_name='date_added')
    updated = fields.Datetime(dateformat='%Y-%m-%d %H:%M:%S',
                              api_name='date_last_updated')

    characters = fields.Field()
    concepts = fields.Field()
    developers = fields.Field()
    platforms = fields.Field()
    publishers = fields.Field()

    @classmethod
    def get(cls, url, **kwargs):
        res = GameResult.get(url)
        res = res.filter()
        return res.results[0]
Beispiel #10
0
class Status(RemoteObject):
    """A Twitter update.

    Statuses can be fetched from
    ``http://twitter.com/statuses/show/<id>.json``.

    """

    created_at = fields.Field()
    id = fields.Field()
    text = fields.Field()
    source = fields.Field()
    truncated = fields.Field()
    in_reply_to_status_id = fields.Field()
    in_reply_to_user_id = fields.Field()
    in_reply_to_screen_name = fields.Field()
    favorited = fields.Field()
    user = fields.Object(User)

    @classmethod
    def get_status(cls, id, http=None):
        return cls.get(urljoin(Twitter.endpoint,
                               "/statuses/show/%d.json" % int(id)),
                       http=http)

    def __unicode__(self):
        return u"%s: %s" % (self.user.screen_name, self.text)
class Vote(OpenStateObject):
    date = OpenStateDatetime()
    chamber = fields.Field()
    committee = fields.Field()
    motion = fields.Field()
    yes_count = fields.Field()
    no_count = fields.Field()
    other_count = fields.Field()
    passed = fields.Field()
    type = fields.Field()
    yes_votes = fields.List(fields.Object(SpecificVote))
    no_votes = fields.List(fields.Object(SpecificVote))
    other_votes = fields.List(fields.Object(SpecificVote))

    def __str__(self):
        return "Vote on '%s'" % self.motion
Beispiel #12
0
class User(RemoteObject):
    """A Twitter account.

    A User can be retrieved from ``http://twitter.com/users/show.json`` with
    the appropriate ``id``, ``user_id``, or ``screen_name`` parameter.

    """

    id = fields.Field()
    name = fields.Field()
    screen_name = fields.Field()
    location = fields.Field()
    description = fields.Field()
    profile_image_url = fields.Field()
    protected = fields.Field()
    followers_count = fields.Field()
    status = fields.Object('Status')

    @classmethod
    def get_user(cls, http=None, **kwargs):
        url = '/users/show'
        if 'id' in kwargs:
            url += '/%s.json' % quote_plus(kwargs['id'])
        else:
            url += '.json'
        query = urlencode(
            filter(lambda x: x in ('screen_name', 'user_id'), kwargs))
        url = urlunsplit((None, None, url, query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)
Beispiel #13
0
class Issues(GitHubRemoteListObject):
    entries = fields.List(fields.Object(Issue))

    @classmethod
    def by_repository(cls, user_name, repo_name, state='open', **kwargs):
        """Get issues by repository.

        `GET /repos/:user/:repo/issues`

        """

        url = '/repos/%s/%s/issues?state=%s' % (user_name, repo_name, state)
        return cls.get(urljoin(GitHub.endpoint, url), **kwargs)

    @classmethod
    def by_repository_all(cls, user_name, repo_name, **kwargs):
        """Get all issues by repository (open and closed)."""

        open_issues = cls.by_repository(user_name,
                                        repo_name,
                                        state='open',
                                        **kwargs)
        closed_issues = cls.by_repository(user_name,
                                          repo_name,
                                          state='closed',
                                          **kwargs)

        open_issues.entries.extend(closed_issues.entries)
        return open_issues
Beispiel #14
0
class Flag(RemoteObject):

    id = fields.Field()
    name = fields.Field()
    setter = fields.Object('User')
    status = fields.Field()
    requestee = fields.Object('User')
    type_id = fields.Field()

    def __repr__(self):
        return '<Flag "%s">' % self.name

    def __str__(self):
        return self.name

    def __hash__(self):
        return self.id
Beispiel #15
0
class Event(GitHubRemoteObject):
    """A GitHub event.

    `GET /events`

    {
      "type": "Event",
      "public": true,
      "payload": {

      },
      "repo": {
        "id": 3,
        "name": "octocat/Hello-World",
        "url": "https://api.github.com/repos/octocat/Hello-World"
      },
      "actor": {
        "login": "******",
        "id": 1,
        "avatar_url": "https://github.com/images/error/octocat_happy.gif",
        "gravatar_id": "somehexcode",
        "url": "https://api.github.com/users/octocat"
      },
      "org": {
        "login": "******",
        "id": 1,
        "avatar_url": "https://github.com/images/error/octocat_happy.gif",
        "gravatar_id": "somehexcode",
        "url": "https://api.github.com/users/octocat"
      },
      "created_at": "2011-09-06T17:26:27Z"
    }

    """

    id = fields.Field()
    type = fields.Field()
    public = fields.Field()
    payload = fields.Field()
    repo = fields.Field()
    actor = fields.Object(User)
    org = fields.Object(User)
    created_at = fields.Field()

    def __unicode__(self):
        return u"<Event %s>" % self.id
Beispiel #16
0
class DirectMessage(RemoteObject):
    """A Twitter direct message.

    The authenticated user's most recent direct messages are at
    ``http://twitter.com/direct_messages.json``.

    """

    id = fields.Field()
    sender_id = fields.Field()
    text = fields.Field()
    recipient_id = fields.Field()
    created_at = fields.Field()
    sender_screen_name = fields.Field()
    recipient_screen_name = fields.Field()
    sender = fields.Object(User)
    recipient = fields.Object(User)

    def __unicode__(self):
        return u"%s: %s" % (self.sender.screen_name, self.text)
Beispiel #17
0
class Milestones(GitHubRemoteListObject):
    entries = fields.List(fields.Object(Milestone))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def by_repository(cls, user_name, repo_name, state='open', **kwargs):
        """Get milestones by repository.

        `GET /repos/:user/:repo/milestones`

        """

        url = '/repos/%s/%s/milestones?state=%s' % (user_name, repo_name,
                                                    state)
        return cls.get(urljoin(GitHub.endpoint, url), **kwargs)

    @classmethod
    def by_repository_all(cls, user_name, repo_name, state='open', **kwargs):
        """Get all milestones by repository (open and closed)."""

        open_milestones = cls.by_repository(user_name,
                                            repo_name,
                                            state='open',
                                            **kwargs)
        closed_milestones = cls.by_repository(user_name,
                                              repo_name,
                                              state='closed',
                                              **kwargs)

        open_milestones.entries.extend(closed_milestones.entries)
        return open_milestones

    @classmethod
    def get_or_create_in_repository(cls, user_name, repo_name,
                                    milestone_title):
        if milestone_title is None:
            return None

        milestones = cls.by_repository_all(user_name,
                                           repo_name,
                                           per_page=100,
                                           all_pages=True)
        for milestone in milestones:
            if milestone.title == milestone_title:
                return milestone

        milestone = Milestone()
        milestone.title = milestone_title
        milestones.post(milestone)

        return milestone
Beispiel #18
0
class ViewResult(CouchObject, ListObject):

    total_rows = fields.Field()
    offset = fields.Field()
    entries = fields.List(fields.Object(ListItem), api_name='rows')

    def filter(self, **kwargs):
        for k, v in kwargs.iteritems():
            if isinstance(v, list) or isinstance(v, dict) or isinstance(
                    v, bool):
                kwargs[k] = json.dumps(v)
        return super(ViewResult, self).filter(**kwargs)
Beispiel #19
0
class Collaborators(GitHubRemoteListObject):
    entries = fields.List(fields.Object(Collaborator))

    @classmethod
    def by_repository(cls, user_name, repo_name, **kwargs):
        """Get collaborators by repository.

        `GET /repos/:user/:repo/collaborators`

        """

        url = '/repos/%s/%s/collaborators' % (user_name, repo_name)
        return cls.get(urljoin(GitHub.endpoint, url), **kwargs)
Beispiel #20
0
class GameResult(Bombject):

    status_code = fields.Field()
    error = fields.Field()
    total = fields.Field(api_name='number_of_total_results')
    count = fields.Field(api_name='number_of_page_results')
    limit = fields.Field()
    offset = fields.Field()
    results = fields.List(fields.Object(Game))

    def update_from_dict(self, data):
        if not isinstance(data['results'], list):
            data = dict(data)
            data['results'] = [data['results']]
        super(GameResult, self).update_from_dict(data)
Beispiel #21
0
class Events(GitHubRemoteListObject):
    entries = fields.List(fields.Object(Event))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def by_repository(cls, repo_user, repo_name, **kwargs):
        """Get events by repository.

        `GET /repos/:user/:repo/events`

        """

        url = '/repos/%s/%s/events' % (repo_user, repo_name)
        return cls.get(urljoin(GitHub.endpoint, url), **kwargs)
Beispiel #22
0
class Comment(RemoteObject):

    id = fields.Field()
    creator = fields.Object('User')
    creation_time = Datetime(DATETIME_FORMAT_WITH_SECONDS)
    text = fields.Field()
    is_private = StringBoolean()

    def __repr__(self):
        return '<Comment by %s on %s>' % (
            self.creator, self.creation_time.strftime(DATETIME_FORMAT))

    def __str__(self):
        return self.text

    def __hash__(self):
        return self.id
Beispiel #23
0
class Timeline(ListObject):

    entries = fields.List(fields.Object(Status))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def public(cls, http=None):
        return cls.get(urljoin(Twitter.endpoint,
                               '/statuses/public_timeline.json'),
                       http=http)

    @classmethod
    def friends(cls, http=None, **kwargs):
        query = urlencode(
            filter(lambda x: x in ('since_id', 'max_id', 'count', 'page'),
                   kwargs))
        url = urlunsplit(
            (None, None, '/statuses/friends_timeline.json', query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)

    @classmethod
    def user(cls, http=None, **kwargs):
        url = '/statuses/user_timeline'
        if 'id' in kwargs:
            url += '/%s.json' % quote_plus(kwargs['id'])
        else:
            url += '.json'
        query = urlencode(
            filter(
                lambda x: x in
                ('screen_name', 'user_id', 'since_id', 'max_id', 'page'),
                kwargs))
        url = urlunsplit((None, None, url, query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)

    @classmethod
    def mentions(cls, http=None, **kwargs):
        query = urlencode(
            filter(lambda x: x in ('since_id', 'max_id', 'page'), kwargs))
        url = urlunsplit((None, None, '/statuses/mentions.json', query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)
Beispiel #24
0
    def __new__(cls, name, bases=None, attr=None):
        """Creates a new `PageObject` subclass.

        If `bases` and `attr` are specified, as in a regular subclass
        declaration, a new class is created as per the specified settings.

        If only `name` is specified, that value is used as a reference to a
        `RemoteObject` class to which the new `PageObject` class is bound.
        The `name` parameter can be either a name or a `RemoteObject` class,
        as when declaring a `remoteobjects.fields.Object` field.

        """
        direct = attr is None
        if direct:
            # Don't bother making a new subclass if we already made one for
            # this target.
            if name in cls._subclasses:
                return cls._subclasses[name]

            entryclass = name
            if callable(entryclass):
                name = cls.__name__ + entryclass.__name__
            else:
                name = cls.__name__ + entryclass

            bases = (cls._basemodule, )

            attr = {
                'entries': fields.List(fields.Object(entryclass)),
            }

        newcls = super(PageOf, cls).__new__(cls, name, bases, attr)

        # Save the result for later direct invocations.
        if direct:
            cls._subclasses[entryclass] = newcls
            newcls.__module__ = cls._modulename
            setattr(sys.modules[cls._modulename], name, newcls)
        elif cls._basemodule is None:
            cls._basemodule = newcls

        return newcls
Beispiel #25
0
class DirectMessageList(ListObject):

    entries = fields.List(fields.Object(DirectMessage))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def get_messages(cls, http=None, **kwargs):
        url = '/direct_messages.json'
        query = urlencode(filter(lambda x: x in ('since_id', 'page'), kwargs))
        url = urlunsplit((None, None, url, query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)

    @classmethod
    def get_sent_messages(cls, http=None, **kwargs):
        url = '/direct_messages/sent.json'
        query = urlencode(filter(lambda x: x in ('since_id', 'page'), kwargs))
        url = urlunsplit((None, None, url, query, None))
        return cls.get(urljoin(Twitter.endpoint, url), http=http)
class Bill(OpenStateObject):
    title = fields.Field()
    state = fields.Field()
    session = fields.Field()
    chamber = fields.Field()
    bill_id = fields.Field()
    created_at = OpenStateDatetime()
    updated_at = OpenStateDatetime()
    alternate_titles = fields.List(fields.Field())
    actions = fields.List(fields.Object(Action))
    sponsors = fields.List(fields.Object(Sponsor))
    votes = fields.List(fields.Object(Vote))
    versions = fields.List(fields.Object(Version))
    documents = fields.List(fields.Object(Document))
    sources = fields.List(fields.Object(Source))

    @classmethod
    def get(cls, state, session, chamber, bill_id):
        """
        Get a specific bill.

        :param state: the two-letter abbreviation of the originating state
        :param session: the session identifier for the bill (see the state's
          metadata for legal values)
        :param chamber: which legislative chamber the bill originated in
          ('upper' or 'lower')
        :param bill_id: the bill's ID as assigned by the state
        """
        func = "bills/%s/%s/%s/%s" % (state, session, chamber, bill_id)
        return super(Bill, cls).get(func)

    @classmethod
    def search(cls, query=None, **kwargs):
        """
        Search bills.

        :param query: a query string which will be used to search bill titles

        Any additional keyword arguments will be used to further filter the
        results.
        """
        if query:
            kwargs['q'] = query
        func = 'bills'
        return ListOf(cls).get(func, kwargs).entries

    def __str__(self):
        return '%s: %s' % (self.bill_id, self.title)
Beispiel #27
0
class Milestone(GitHubRemoteObject):
    """A GitHub milestone.

    `GET /repos/:user/:repo/milestones/:number`

    {
      "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
      "number": 1,
      "state": "open",
      "title": "v1.0",
      "description": "",
      "creator": {
        "login": "******",
        "id": 1,
        "avatar_url": "https://github.com/images/error/octocat_happy.gif",
        "gravatar_id": "somehexcode",
        "url": "https://api.github.com/users/octocat"
      },
      "open_issues": 4,
      "closed_issues": 8,
      "created_at": "2011-04-10T20:09:31Z",
      "due_on": null
    }

    """

    url = fields.Field()
    number = fields.Field()
    state = fields.Field()
    title = fields.Field()
    description = fields.Field()
    creator = fields.Object(User)
    open_issues = fields.Field()
    closed_issues = fields.Field()
    created_at = fields.Field()
    due_on = fields.Field()

    def __unicode__(self):
        return u"<Milestone %s>" % self.title
Beispiel #28
0
class Comments(GitHubRemoteListObject):
    entries = fields.List(fields.Object(Comment))

    def __getitem__(self, key):
        return self.entries.__getitem__(key)

    @classmethod
    def by_issue(cls, issue, **kwargs):
        """Get comments by issue.

        `GET /repos/:user/:repo/issues/:number/comments`

        """

        # Don't request the list of comments if we know there should be 0.
        if issue.comments == 0:
            comments = cls(entries=[])
            comments._location = '%s/comments' % issue.url
            return comments

        url = '%s/comments' % issue.url
        return cls.get(url, **kwargs)
class State(OpenStateObject):
    name = fields.Field()
    abbreviation = fields.Field()
    legislature_name = fields.Field()
    upper_chamber_name = fields.Field()
    lower_chamber_name = fields.Field()
    upper_chamber_term = fields.Field()
    lower_chamber_term = fields.Field()
    upper_chamber_title = fields.Field()
    lower_chamber_title = fields.Field()
    terms = fields.List(fields.Object(Term))

    @classmethod
    def get(cls, abbrev):
        """
        Get metadata about a state.

        :param abbrev: the state's two-letter abbreviation.
        """
        return super(State, cls).get('metadata/%s' % abbrev)

    def __str__(self):
        return self.name
Beispiel #30
0
class Bug(RemoteObject):

    id = fields.Field()
    summary = fields.Field()
    assigned_to = fields.Object('User')
    creator = fields.Object('User')
    target_milestone = fields.Field()
    attachments = fields.List(fields.Object('Attachment'))
    comments = fields.List(fields.Object('Comment'))
    history = fields.List(fields.Object('Changeset'))
    keywords = fields.List(fields.Object('Keyword'))
    status = fields.Field()
    resolution = fields.Field()

    # TODO: These are Mozilla specific and should be generalized
    cf_blocking_20 = fields.Field()
    cf_blocking_fennec = fields.Field()
    cf_crash_signature = fields.Field()

    creation_time = Datetime(DATETIME_FORMAT_WITH_SECONDS)
    flags = fields.List(fields.Object('Flag'))
    blocks = fields.List(fields.Field())
    depends_on = fields.List(fields.Field())
    url = fields.Field()
    cc = fields.List(fields.Object('User'))
    keywords = fields.List(fields.Field())
    whiteboard = fields.Field()

    op_sys = fields.Field()
    platform = fields.Field()
    priority = fields.Field()
    product = fields.Field()
    qa_contact = fields.Object('User')
    severity = fields.Field()
    see_also = fields.List(fields.Field())
    version = fields.Field()

    alias = fields.Field()
    classification = fields.Field()
    component = fields.Field()
    is_cc_accessible = StringBoolean()
    is_everconfirmed = StringBoolean()
    is_creator_accessible = StringBoolean()
    last_change_time = Datetime(DATETIME_FORMAT_WITH_SECONDS)
    ref = fields.Field()

    # Needed for submitting changes.
    token = fields.Field(api_name='update_token')

    # Time tracking.
    actual_time = fields.Field()
    deadline = Datetime(DATETIME_FORMAT_WITH_SECONDS)
    estimated_time = fields.Field()
    # groups = fields.Field() # unimplemented
    percentage_complete = fields.Field()
    remaining_time = fields.Field()
    work_time = fields.Field()

    def __repr__(self):
        return '<Bug %s: "%s">' % (self.id, self.summary)

    def __str__(self):
        return "[Bug %s] - %s" % (self.id, self.summary)

    def __hash__(self):
        return self.id