From ad1a3d2ba98016ec745fbb764ee1cea32dff5441 Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 05 2017 12:44:22 +0000
Subject: [PATCH 1/9] Use url_for in templates


---

diff --git a/hubs/templates/hubs.html b/hubs/templates/hubs.html
index 231b364..30b7078 100644
--- a/hubs/templates/hubs.html
+++ b/hubs/templates/hubs.html
@@ -17,14 +17,14 @@
       <div id="vertical-navbar">
         <ul class="nav navbar-nav p-r-1">
           <li class="p-x-1 nav-item {% if request.path.endswith('/' + g.auth.user.username + '/') %}active{% endif %}">
-            <a class="nav-link" href="/{{g.auth.user.username}}">me</a></li>
-          <li class="p-x-1 nav-item"><a class="nav-link" href="/{{g.auth.user.username}}/stream">My Stream</a></li>
+            <a class="nav-link" href="{{ url_for("hub", name=g.auth.user.username) }}">me</a></li>
+          <li class="p-x-1 nav-item"><a class="nav-link" href="{{ url_for("stream", name=g.auth.user.username) }}">My Stream</a></li>
           {% for hub in g.auth.user.bookmarks %}
             <li class='p-x-1 nav-item idle-{{hub.activity_class}}{% if request.path.endswith('/' + hub.name + '/') %} active{% endif %}'>
-              <a class="nav-link" href="/{{hub.name}}">{{hub.name}}</a></li>
+              <a class="nav-link" href="{{ url_for("hub", name=hub.name) }}">{{hub.name}}</a></li>
           {% endfor %}
           <!-- At the end of the list, tack on a link to all groups -->
-          <li class="p-x-1 nav-item"><a class="nav-link" href="/groups">all</a></li>
+          <li class="p-x-1 nav-item"><a class="nav-link" href="{{ url_for("groups") }}">all</a></li>
         </ul>
       </div><!-- /.navbar-collapse -->
       {% endif %}

From b439153a22f78395c117fd222111af7d4d14097a Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 06 2017 23:29:35 +0000
Subject: [PATCH 2/9] Add an authorization framework


---

diff --git a/hubs/app.py b/hubs/app.py
index c8b5aca..7b28c4c 100644
--- a/hubs/app.py
+++ b/hubs/app.py
@@ -95,11 +95,14 @@ def check_auth():
             )
             flask.session.auth.avatar = username2avatar(
                 flask.session.auth.nickname)
-            flask.session.auth.user = hubs.models.User.by_username(
-                flask.session.auth.nickname)
+
+            user = hubs.models.User.get_or_create(
+                flask.g.db, username=flask.session.auth.nickname,
+                fullname=flask.session.auth.fullname)
+            flask.session.auth.user = user
         flask.g.auth = flask.session.auth
     else:
-        flask.g.auth = munch.Munch(logged_in=False)
+        flask.g.auth = munch.Munch(logged_in=False, user=None)
 
 # Register widgets
 import hubs.widgets  # noqa
diff --git a/hubs/authz.py b/hubs/authz.py
new file mode 100644
index 0000000..ebd85ca
--- /dev/null
+++ b/hubs/authz.py
@@ -0,0 +1,88 @@
+from __future__ import absolute_import, unicode_literals
+
+from enum import IntEnum
+from flask import current_app
+
+
+#class Authorization(object):
+#
+#    def __init__(self, app=None, db=None):
+#        self.db = db
+#        if app is not None:
+#            self.init_app(app)
+#
+#    def init_app(self):
+#        app.config.setdefault("AUTHZ_ADAPTER", None)
+
+
+class AccessLevel(IntEnum):
+    anonymous = 0
+    logged_in = 1
+    member = 2
+    sponsor = 3
+    owner = 4
+
+
+PERMISSIONS = {
+    "hub.public.view": AccessLevel.anonymous,
+    "hub.preview.view": AccessLevel.anonymous,
+    "hub.private.view": AccessLevel.member,
+    "hub.users.manage": AccessLevel.sponsor,
+    "hub.config": AccessLevel.owner,
+    "widget.public.view": AccessLevel.anonymous,
+    "widget.restricted.view": AccessLevel.logged_in,
+}
+
+
+class ObjectAuthzMixin(object):
+
+    def allows(self, user, action):
+        site_admins = current_app.config.get("SITE_ADMINS", [])
+        if user is not None and user.username in site_admins:
+            return True
+        user_level = self._get_auth_access_level(user)
+        permission = self._get_auth_permission_name(action)
+        min_level = PERMISSIONS[permission]
+        if user_level >= min_level:
+            return True
+        return False
+
+    def _get_auth_access_level(self, user):
+        if user is None:
+            return AccessLevel.anonymous
+        levels = [
+            self._get_auth_user_access_level(user),
+            self._get_auth_group_access_level(user),
+        ]
+        return max(l for l in levels if l is not None)
+
+    def _get_auth_user_access_level(self, user):
+        return AccessLevel.logged_in
+
+    def _get_auth_group_access_level(self, user):
+        group = self._get_auth_group()
+        user_roles = self._get_auth_user_roles(user)
+        try:
+            user_level = user_roles[group]
+        except KeyError:
+            # No role for this group
+            return None
+        try:
+            return AccessLevel[user_level]
+        except KeyError:
+            # Unsupported role
+            return None
+
+    def _get_auth_group(self):
+        raise NotImplementedError
+
+    def _get_auth_user_roles(self, user):
+        return get_user_roles(user)
+
+    def _get_auth_permission_name(self, action):
+        return action
+
+
+def get_user_roles(user):
+    # TODO: use the new API
+    return {g: "member" for g in user.groups}
diff --git a/hubs/migrations/versions/bed8bbc0f78e_authz.py b/hubs/migrations/versions/bed8bbc0f78e_authz.py
new file mode 100644
index 0000000..525c2ad
--- /dev/null
+++ b/hubs/migrations/versions/bed8bbc0f78e_authz.py
@@ -0,0 +1,63 @@
+# This Alembic database migration is part of the Fedora Hubs project.
+# Copyright (C) <year>  <name of author>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Authorization
+
+Revision ID: bed8bbc0f78e
+Revises: 6d4862ec4f93
+Create Date: 2017-03-30 21:38:03.629462
+"""
+
+from __future__ import absolute_import, unicode_literals
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'bed8bbc0f78e'
+down_revision = '6d4862ec4f93'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    op.add_column(
+        'hubs_config',
+        sa.Column('auth_group', sa.String(length=256), nullable=True))
+    op.add_column(
+        'hubs_config',
+        sa.Column('visibility', sa.Enum('public', 'preview', 'private'),
+                  nullable=True))
+    op.add_column(
+        'widgets',
+        sa.Column('visibility', sa.Enum('public', 'restricted'),
+                  nullable=True))
+    op.execute("UPDATE hubs_config SET visibility = 'public'")
+    op.execute("UPDATE widgets SET visibility = 'public'")
+    with op.batch_alter_table("hubs_config") as batch_op:
+        batch_op.alter_column("visibility", nullable=False)
+    with op.batch_alter_table("widgets") as batch_op:
+        batch_op.alter_column("visibility", nullable=False)
+
+
+def downgrade():
+    # http://alembic.zzzcomputing.com/en/latest/batch.html
+    with op.batch_alter_table("hubs_config") as batch_op:
+        batch_op.drop_column('visibility')
+        batch_op.drop_column('auth_group')
+    with op.batch_alter_table("widgets") as batch_op:
+        batch_op.drop_column('visibility')
diff --git a/hubs/models.py b/hubs/models.py
index 48ed695..758a64f 100644
--- a/hubs/models.py
+++ b/hubs/models.py
@@ -47,6 +47,7 @@ import fedmsg.utils
 
 import hubs.defaults
 import hubs.widgets
+from hubs.authz import ObjectAuthzMixin, AccessLevel
 from hubs.utils import username2avatar
 
 
@@ -135,7 +136,7 @@ class Association(BASE):
             .first()
 
 
-class Hub(BASE):
+class Hub(ObjectAuthzMixin, BASE):
     __tablename__ = 'hubs'
     name = sa.Column(sa.String(50), primary_key=True)
     created_on = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
@@ -221,9 +222,6 @@ class Hub(BASE):
         session.delete(association)
         session.commit()
 
-    def is_admin(self, user):
-        return user in self.owners
-
     @classmethod
     def by_name(cls, name):
         return cls.query.filter_by(name=name).first()
@@ -264,19 +262,39 @@ class Hub(BASE):
         hubs.defaults.add_group_widgets(session, hub, name, summary, **extra)
         return hub
 
-    @property
-    def left_widgets(self):
-        return sorted(
-            [w for w in self.widgets
-             if w.plugin in hubs.widgets.registry and w.left],
-            key=lambda w: w.index)
+    def _get_auth_user_access_level(self, user):
+        # overridden to handle user hubs.
+        if self.user_hub and user.username == self.name:
+            return AccessLevel.owner
+        return super(Hub, self)._get_auth_user_access_level(user)
+
+    def _get_auth_group(self):
+        # While we're local-only, just use the hub name:
+        return self.name
+        # When the CAIAPI is in place we will be able to use the auth_group
+        # setting:
+        # group = self.config.auth_group
+        # if group is None:
+        #     group = self.name
+        # return group
+
+    def _get_auth_user_roles(self, user):
+        """Override until we can get groups from FAS.
+
+        This will not return all roles, only the one for the current hub, but
+        that fine since it's the only one we're interested in when this method
+        is called.
+        """
+        return {
+            assoc.hub.name: assoc.role
+            for assoc in self.associations
+            if assoc.user.username == user.username
+            }
 
-    @property
-    def right_widgets(self):
-        return sorted(
-            [w for w in self.widgets
-             if w.plugin in hubs.widgets.registry and not w.left],
-            key=lambda w: w.index)
+    def _get_auth_permission_name(self, action):
+        if action == "view":
+            action = "{}.view".format(self.config.visibility)
+        return "hub.{}".format(action)
 
     def __json__(self):
         return {
@@ -292,7 +310,11 @@ class Hub(BASE):
 
 
 class HubConfig(BASE):
+
     __tablename__ = 'hubs_config'
+
+    VISIBILITY = ["public", "preview", "private"]
+
     id = sa.Column(sa.Integer, primary_key=True)
     hub_id = sa.Column(sa.String(50), sa.ForeignKey('hubs.name'),
                        nullable=False)
@@ -303,6 +325,9 @@ class HubConfig(BASE):
     header_img = sa.Column(sa.String(256), default=randomheader)
     chat_channel = sa.Column(sa.String(256), nullable=True)
     chat_domain = sa.Column(sa.String(256), nullable=True)
+    auth_group = sa.Column(sa.String(256), nullable=True)
+    visibility = sa.Column(
+        sa.Enum(*VISIBILITY), default="public", nullable=False)
 
     def __json__(self):
         return {
@@ -311,6 +336,7 @@ class HubConfig(BASE):
             'avatar': self.avatar,
             'chat_channel': self.chat_channel,
             'chat_domain': self.chat_domain,
+            'visibility': self.visibility,
         }
 
     @property
@@ -326,8 +352,12 @@ def _widget_config_default(context):
         ]))
 
 
-class Widget(BASE):
+class Widget(ObjectAuthzMixin, BASE):
+
     __tablename__ = 'widgets'
+
+    VISIBILITY = ["public", "restricted"]
+
     idx = sa.Column(sa.Integer, primary_key=True)
     plugin = sa.Column(sa.String(50), nullable=False)
     created_on = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
@@ -336,6 +366,8 @@ class Widget(BASE):
 
     index = sa.Column(sa.Integer, nullable=False)
     left = sa.Column(sa.Boolean, nullable=False, default=False)
+    visibility = sa.Column(
+        sa.Enum(*VISIBILITY), default="public", nullable=False)
 
     @classmethod
     def by_idx(cls, idx):
@@ -359,6 +391,21 @@ class Widget(BASE):
     def config(self, config):
         self._config = json.dumps(config)
 
+    def _get_auth_access_level(self, user):
+        return self.hub._get_auth_access_level(user)
+
+    def _get_auth_user_roles(self, user):
+        """Override until we can get groups from FAS"""
+        return self.hub._get_auth_user_roles(user)
+
+    def _get_auth_permission_name(self, action):
+        if action != "view":
+            return self.hub._get_auth_permission_name(action)
+        hub_visibility = self.hub.config.visibility
+        if hub_visibility != "preview":
+            return "hub.{}.view".format(hub_visibility)
+        return "widget.{}.view".format(self.visibility)
+
     def __json__(self):
         module = hubs.widgets.registry[self.plugin]
         root_view = module.get_views()["root"](module)
diff --git a/hubs/static/client/app/core/HubConfigPanel.jsx b/hubs/static/client/app/core/HubConfigPanel.jsx
index 517042f..ddc22d3 100644
--- a/hubs/static/client/app/core/HubConfigPanel.jsx
+++ b/hubs/static/client/app/core/HubConfigPanel.jsx
@@ -3,6 +3,7 @@ import {
   IntlProvider,
   defineMessages,
   FormattedMessage,
+  FormattedHTMLMessage,
   } from 'react-intl';
 import TabPanel from '../components/TabPanel.jsx';
 
@@ -20,21 +21,51 @@ const messages = defineMessages({
     id: "hubs.core.config.general.summary",
     defaultMessage: "Summary",
   },
+  general_summary_help: {
+    id: "hubs.core.config.general.summary_help",
+    defaultMessage: "This text will be displayed at the top of the hub.",
+  },
   general_left_width: {
     id: "hubs.core.config.general.left_width",
     defaultMessage: "Left width",
   },
+  general_left_width_help: {
+    id: "hubs.core.config.general.left_width_help",
+    defaultMessage: (
+      "Change the proportions of you hub's columns by setting the width of "
+     +"the left column (the right column's width will be {right_width})."
+      ),
+  },
+  general_visibility: {
+    id: "hubs.core.config.general.visibility",
+    defaultMessage: "Visibility",
+  },
+  general_visibility_help: {
+    id: "hubs.core.config.general.visibility_help",
+    defaultMessage: (
+      "Public: the hub is visible to anyone.<br />"
+     +"Private: the hub is only visible to members.<br />"
+     +"Preview: some widgets can be made visible to logged-in users only."
+      ),
+  },
   general_avatar: {
     id: "hubs.core.config.general.avatar",
     defaultMessage: "Avatar",
   },
+  general_avatar_help: {
+    id: "hubs.core.config.general.avatar_help",
+    defaultMessage: "An URL to you hub's main image or logo."
+  },
   chat_title: {
     id: "hubs.core.config.chat.title",
     defaultMessage: "Chat Settings",
   },
   chat_intro_1: {
     id: "hubs.core.config.chat.intro1",
-    defaultMessage: "If your team or project has an IRC channel associated with it, you can connect this hub to it via the Hubs chat widget.",
+    defaultMessage: (
+      "If your team or project has an IRC channel associated with it, you "
+     +"can connect this hub to it via the Hubs chat widget."
+     ),
   },
   chat_intro_2: {
     id: "hubs.core.config.chat.intro2",
@@ -72,6 +103,7 @@ export class GeneralPanel extends React.Component {
 
   render() {
     var stillLoading = (typeof this.props.hubConfig.summary === "undefined");
+    var visibilities = this.props.generalConfig.hub_visibility || [];
     return (
       <div>
         <FormattedMessage {...messages.general_title} tagName="h3" />
@@ -87,6 +119,9 @@ export class GeneralPanel extends React.Component {
             onChange={this.props.handleChange}
             value={this.props.hubConfig.summary || ""}
             />
+          <p className="form-text text-muted">
+            <FormattedMessage {...messages.general_summary_help} />
+          </p>
         </div>
         <div className="form-group">
           <label htmlFor="hub-settings-general-leftwidth">
@@ -100,6 +135,32 @@ export class GeneralPanel extends React.Component {
             onChange={this.props.handleChange}
             value={this.props.hubConfig.left_width || ""}
             />
+          <p className="form-text text-muted">
+            <FormattedMessage
+              {...messages.general_left_width_help}
+              values={{right_width: 12 - (this.props.hubConfig.left_width || 8)}}
+              />
+          </p>
+        </div>
+        <div className="form-group">
+          <label htmlFor="hub-settings-general-visibility">
+            <FormattedMessage {...messages.general_visibility} />
+          </label>
+          <select
+            className="form-control" name="visibility"
+            value={this.props.hubConfig.visibility || ""}
+            onChange={this.props.handleChange}
+            disabled={stillLoading}
+            >
+            {visibilities.map(function(value) {
+              return (
+                <option value={value} key={value}>{value}</option>
+                );
+            })}
+          </select>
+          <p className="form-text text-muted">
+            <FormattedHTMLMessage {...messages.general_visibility_help} />
+          </p>
         </div>
         <div className="form-group">
           <label htmlFor="hub-settings-general-avatar">
@@ -112,6 +173,9 @@ export class GeneralPanel extends React.Component {
             onChange={this.props.handleChange}
             value={this.props.hubConfig.avatar || ""}
             />
+          <p className="form-text text-muted">
+            <FormattedMessage {...messages.general_avatar_help} />
+          </p>
         </div>
       </div>
     );
diff --git a/hubs/templates/errors/hub.html b/hubs/templates/errors/hub.html
new file mode 100644
index 0000000..36506e1
--- /dev/null
+++ b/hubs/templates/errors/hub.html
@@ -0,0 +1,8 @@
+{% extends "hubs.html" %}
+
+{% block hub_content %}
+
+<p class="text-center">{{ msg }}</p>
+
+{% endblock %}
+
diff --git a/hubs/templates/hubs.html b/hubs/templates/hubs.html
index 30b7078..35ab48a 100644
--- a/hubs/templates/hubs.html
+++ b/hubs/templates/hubs.html
@@ -48,7 +48,7 @@
         
         <!-- right side of header -->
         <div class="col-md-{{ hub.config.right_width }} header-right">
-          {% if g.auth.logged_in and hub.is_admin(g.auth.user) %}
+          {% if hub.allows(g.auth.user, "config") %}
           <div style="text-align:center">
             <button class="btn btn-secondary" data-toggle="modal" data-target="#settings-modal">
               <i class="fa fa-cog" aria-hidden="true"></i>
@@ -100,6 +100,7 @@
 
   <div class="row">
     <div class="col-md-offset-2">
+      {% block hub_content %}
       <div class="row">
         <div class="col-md-{{ hub.config.left_width }}">
           <!--
@@ -144,7 +145,13 @@
           </div>
           {% endif %}
 
-          {% include "includes/left_widgets.html" %}
+          <div id="left_widgets">
+            {% for widget in widgets["left"] %}
+            <div id="widget-{{ widget.idx }}" class="widget row"
+                 data-url="{{ url_for('%s_root' % widget.plugin, hub=hub.name, idx=widget.idx) }}"
+                 ></div>
+            {% endfor %}
+          </div>
         </div>
 
         <div class="col-md-{{ hub.config.right_width }}">
@@ -162,16 +169,23 @@
           </div>
           {% endif %}
 
-          {% include "includes/right_widgets.html" %}
+          <div id="right_widgets">
+            {% for widget in widgets["right"] %}
+            <div id="widget-{{ widget.idx }}" class="widget row"
+                 data-url="{{ url_for('%s_root' % widget.plugin, hub=hub.name, idx=widget.idx) }}"
+                 ></div>
+            {% endfor %}
+          </div>
         </div>
 
       </div>  <!-- /row -->
+      {% endblock hub_content %}
     </div>  <!-- /col-md-offset-2 -->
   </div>  <!-- /row -->
-      
-    
+
+
 </div>
-      
+
 
 <div id="settings-modal" class="modal fade" tabindex="-1" role="dialog">
 </div>
diff --git a/hubs/templates/includes/left_widgets.html b/hubs/templates/includes/left_widgets.html
deleted file mode 100644
index 032a026..0000000
--- a/hubs/templates/includes/left_widgets.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<div id="left_widgets">
-{% for widget in hub.left_widgets %}
-  <div id="widget-{{ widget.idx }}" class="widget row"
-       data-url="{{ url_for('%s_root' % widget.plugin, hub=hub.name, idx=widget.idx) }}"
-       ></div>
-{% endfor %}
-</div>
diff --git a/hubs/templates/includes/right_widgets.html b/hubs/templates/includes/right_widgets.html
deleted file mode 100644
index a0b2a3b..0000000
--- a/hubs/templates/includes/right_widgets.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<div id="right_widgets">
-  {% for widget in hub.right_widgets %}
-  <div id="widget-{{ widget.idx }}" class="widget row"
-       data-url="{{ url_for('%s_root' % widget.plugin, hub=hub.name, idx=widget.idx) }}"
-       ></div>
-  {% endfor %}
-</div>
diff --git a/hubs/templates/stream.html b/hubs/templates/stream.html
index e33c301..b7ca796 100644
--- a/hubs/templates/stream.html
+++ b/hubs/templates/stream.html
@@ -50,7 +50,13 @@
   </div>
 
   <div class="col-sm-4">
-    {% include "includes/right_widgets.html" %}
+    <div id="right_widgets">
+      {% for widget in widgets["right"] %}
+      <div id="widget-{{ widget.idx }}" class="widget row"
+           data-url="{{ url_for('%s_root' % widget.plugin, hub=hub.name, idx=widget.idx) }}"
+           ></div>
+      {% endfor %}
+    </div>
   </div>
 </div>
 
diff --git a/hubs/tests/__init__.py b/hubs/tests/__init__.py
index 67d82fa..edd2ba5 100644
--- a/hubs/tests/__init__.py
+++ b/hubs/tests/__init__.py
@@ -120,7 +120,7 @@ def auth_set(APP, auth):
         g.auth = auth
         g.oidc_id_token = None
         if not auth:
-            g.auth = munch.Munch(logged_in=False)
+            g.auth = munch.Munch(logged_in=False, user=None)
 
     with appcontext_pushed.connected_to(handler, APP):
         yield
@@ -136,9 +136,7 @@ class FakeUser(object):
         """
         self.username = username
         self.booksmarks = []
-
-    def __getitem__(self, key):
-        return self.dic[key]
+        self.groups = []
 
 
 class FakeAuthorization(object):
diff --git a/hubs/tests/test_fedora_hubs_flask_api.py b/hubs/tests/test_fedora_hubs_flask_api.py
index 2c8f1ff..778ea54 100644
--- a/hubs/tests/test_fedora_hubs_flask_api.py
+++ b/hubs/tests/test_fedora_hubs_flask_api.py
@@ -66,7 +66,9 @@ class HubsAPITest(hubs.tests.APPTest):
                 result.get_data(as_text=True))
 
     def test_hub_json(self):
-        result = self.app.get('/ralph/json', follow_redirects=True)
+        user = tests.FakeAuthorization('ralph')
+        with tests.auth_set(app, user):
+            result = self.app.get('/ralph/json', follow_redirects=True)
         # assert the status code of the response
         self.assertEqual(result.status_code, 200)
         data = {
diff --git a/hubs/tests/test_models.py b/hubs/tests/test_models.py
index 8669160..c3279cd 100644
--- a/hubs/tests/test_models.py
+++ b/hubs/tests/test_models.py
@@ -153,15 +153,3 @@ class ModelTest(hubs.tests.APPTest):
                           session=self.session,
                           username=username,
                           visited_hub=hub)
-
-    def test_removed_widget(self):
-        hub = hubs.models.Hub.get("ralph")
-        widget = hubs.models.Widget(
-            hub=hub, plugin="does-not-exist",
-            left=True, index=-1, _config="{}")
-        self.session.add(widget)
-        self.assertNotIn(
-            "does-not-exist", [w.plugin for w in hub.left_widgets])
-        widget.left = False
-        self.assertNotIn(
-            "does-not-exist", [w.plugin for w in hub.right_widgets])
diff --git a/hubs/tests/test_view_utils.py b/hubs/tests/test_view_utils.py
new file mode 100644
index 0000000..d5e8218
--- /dev/null
+++ b/hubs/tests/test_view_utils.py
@@ -0,0 +1,24 @@
+from __future__ import unicode_literals
+
+import hubs.models
+import hubs.tests
+from hubs.views.utils import get_visible_widgets
+
+
+class ModelTest(hubs.tests.APPTest):
+
+    def test_removed_widget(self):
+        hub = hubs.models.Hub.get("ralph")
+        widget = hubs.models.Widget(
+            hub=hub, plugin="does-not-exist",
+            left=True, index=-1, _config="{}")
+        self.session.add(widget)
+        user = hubs.tests.FakeAuthorization('ralph')
+        with hubs.tests.auth_set(self.app.application, user):
+            with self.app.application.app_context():
+                widgets = get_visible_widgets(hub)
+        self.assertNotIn(
+            "does-not-exist", [w.plugin for w in widgets["left"]])
+        widget.left = False
+        self.assertNotIn(
+            "does-not-exist", [w.plugin for w in widgets["right"]])
diff --git a/hubs/tests/test_widgets/test_about.py b/hubs/tests/test_widgets/test_about.py
index d86435f..edab332 100644
--- a/hubs/tests/test_widgets/test_about.py
+++ b/hubs/tests/test_widgets/test_about.py
@@ -2,16 +2,19 @@ from __future__ import unicode_literals
 
 import json
 
-import hubs.tests.test_widgets
+from hubs.tests import FakeAuthorization, auth_set
+from hubs.tests.test_widgets import WidgetTest
 
 
-class TestBadges(hubs.tests.test_widgets.WidgetTest):
+class TestAbout(WidgetTest):
     plugin = 'about'  # The name in hubs.widgets.registry
 
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
-        response = self.app.get('/ralph/%i/json' % widget.idx)
-        assert response.status_code == 200, response.status_code
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/ralph/%i/json' % widget.idx)
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.get_data(as_text=True))
         self.assertDictEqual(data['data'], {
             'text': 'Testing.',
diff --git a/hubs/tests/test_widgets/test_badges.py b/hubs/tests/test_widgets/test_badges.py
index 3521ec2..30284fd 100644
--- a/hubs/tests/test_widgets/test_badges.py
+++ b/hubs/tests/test_widgets/test_badges.py
@@ -2,16 +2,20 @@ from __future__ import unicode_literals
 
 import json
 
-import hubs.tests.test_widgets
+import hubs.widgets
+from hubs.tests import FakeAuthorization, auth_set
+from hubs.tests.test_widgets import WidgetTest
 
 
-class TestBadges(hubs.tests.test_widgets.WidgetTest):
+class TestBadges(WidgetTest):
     plugin = 'badges'  # The name in hubs.widgets.registry
 
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
-        response = self.app.get('/ralph/%i/json' % widget.idx)
-        assert response.status_code == 200, response.status_code
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/ralph/%i/json' % widget.idx)
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.get_data(as_text=True))
         self.assertEquals(data['plugin'], 'badges')
         self.assertIn('assertions', data['data'].keys())
diff --git a/hubs/tests/test_widgets/test_contact.py b/hubs/tests/test_widgets/test_contact.py
index a934c38..3e63953 100644
--- a/hubs/tests/test_widgets/test_contact.py
+++ b/hubs/tests/test_widgets/test_contact.py
@@ -73,7 +73,9 @@ class ContactsTest(APPTest):
 
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
-        response = self.app.get('/ralph/%i/json' % widget.idx)
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/ralph/%i/json' % widget.idx)
         self.assertEqual(response.status_code, 200)
         data = json.loads(response.get_data(as_text=True))
         self.assertDictEqual(data['data'], {
diff --git a/hubs/tests/test_widgets/test_fedmsgstats.py b/hubs/tests/test_widgets/test_fedmsgstats.py
index cb3d5d3..4861b49 100644
--- a/hubs/tests/test_widgets/test_fedmsgstats.py
+++ b/hubs/tests/test_widgets/test_fedmsgstats.py
@@ -2,16 +2,19 @@ from __future__ import unicode_literals
 
 import json
 
-import hubs.tests.test_widgets
+from hubs.tests import FakeAuthorization, auth_set
+from hubs.tests.test_widgets import WidgetTest
 
 
-class TestFedmsgStats(hubs.tests.test_widgets.WidgetTest):
+class TestFedmsgStats(WidgetTest):
     plugin = 'fedmsgstats'  # The name in hubs.widgets.registry
 
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
-        response = self.app.get('/ralph/%i/json' % widget.idx)
-        assert response.status_code == 200, response.status_code
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/ralph/%i/json' % widget.idx)
+        self.assertEqual(response.status_code, 200)
         data = json.loads(response.get_data(as_text=True))
         self.assertDictEqual(data['data'], {
             u'fedmsgs': 83854,
diff --git a/hubs/tests/test_widgets/test_library.py b/hubs/tests/test_widgets/test_library.py
index 0e09a7d..eaac331 100644
--- a/hubs/tests/test_widgets/test_library.py
+++ b/hubs/tests/test_widgets/test_library.py
@@ -2,16 +2,18 @@ from __future__ import unicode_literals
 
 import json
 
-import hubs.tests.test_widgets
-import hubs.models
+from hubs.tests import FakeAuthorization, auth_set
+from hubs.tests.test_widgets import WidgetTest
 
 
-class TestLibrary(hubs.tests.test_widgets.WidgetTest):
+class TestLibrary(WidgetTest):
     plugin = 'library'  # The name in hubs.widgets.registry
 
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
-        response = self.app.get('/ralph/%i/json' % widget.idx)
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/ralph/%i/json' % widget.idx)
         self.assertEqual(response.status_code, 200)
         data = json.loads(response.get_data(as_text=True))
         expected_dict = {
diff --git a/hubs/tests/test_widgets/test_meetings.py b/hubs/tests/test_widgets/test_meetings.py
index 78c41b9..8c2784b 100644
--- a/hubs/tests/test_widgets/test_meetings.py
+++ b/hubs/tests/test_widgets/test_meetings.py
@@ -2,16 +2,19 @@ from __future__ import unicode_literals
 
 import json
 
-import hubs.tests.test_widgets
+from hubs.tests import FakeAuthorization, auth_set
+from hubs.tests.test_widgets import WidgetTest
 
 
-class TestMeetings(hubs.tests.test_widgets.WidgetTest):
+class TestMeetings(WidgetTest):
     plugin = 'meetings'
 
     def test_data_simple(self):
         team = 'i18n'
         widget = self.widget_instance(team, self.plugin)
-        response = self.app.get('/%s/%i/json' % (team, widget.idx))
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/%s/%i/json' % (team, widget.idx))
         self.assertEqual(200, response.status_code)
         data = json.loads(response.get_data(as_text=True))
         calendar_name = data['data']['calendar']
@@ -20,8 +23,10 @@ class TestMeetings(hubs.tests.test_widgets.WidgetTest):
     def test_render_simple(self):
         team = 'i18n'
         widget = self.widget_instance(team, self.plugin)
-        response = self.app.get('/%s/w/%s/%i/'
-                                % (team, self.plugin, widget.idx))
+        user = FakeAuthorization('ralph')
+        with auth_set(self.app.application, user):
+            response = self.app.get('/%s/w/%s/%i/'
+                                    % (team, self.plugin, widget.idx))
         self.assertEqual(200, response.status_code)
         self.assertIn('i18n', response.get_data(as_text=True))
         self.assertIn('<strong>Request A New Meeting</strong>',
diff --git a/hubs/views/hub.py b/hubs/views/hub.py
index 4227357..75b5764 100644
--- a/hubs/views/hub.py
+++ b/hubs/views/hub.py
@@ -5,19 +5,25 @@ import flask
 import hubs.models
 
 from hubs.app import app
-from .utils import get_hub, login_required, RequestValidator
+from .utils import (
+    get_hub, get_visible_widgets, login_required, RequestValidator,
+    require_hub_access,
+    )
 
 
 @app.route('/<name>')
 @app.route('/<name>/')
+@require_hub_access("view")
 def hub(name):
     hub = get_hub(name, load_config=True)
+    widgets = get_visible_widgets(hub)
     return flask.render_template(
-        'hubs.html', hub=hub, edit=False)
+        'hubs.html', hub=hub, widgets=widgets, edit=False)
 
 
 @app.route('/<name>/json/')
 @app.route('/<name>/json')
+@require_hub_access("view", json=True)
 def hub_json(name):
     hub = get_hub(name)
     response = flask.jsonify(hub.__json__())
@@ -27,21 +33,22 @@ def hub_json(name):
 
 @app.route('/<name>/edit', methods=['GET', 'POST'])
 @login_required
+@require_hub_access("config")
 def hub_edit(name):
+    hub = get_hub(name, load_config=True)
     if flask.request.method == 'POST':
-        return hub_edit_post(name)
+        return hub_edit_post(hub)
     else:
-        return hub_edit_get(name)
+        return hub_edit_get(hub)
 
 
-def hub_edit_get(name):
-    hub = get_hub(name, load_config=True)
+def hub_edit_get(hub):
+    widgets = get_visible_widgets(hub)
     return flask.render_template(
-        'hubs.html', hub=hub, edit=True)
+        'hubs.html', hub=hub, widgets=widgets, edit=True)
 
 
-def hub_edit_post(name):
-    hub = get_hub(name)
+def hub_edit_post(hub):
     is_js = flask.request.form.get('js', False)
 
     error = False
@@ -138,11 +145,12 @@ def hub_edit_post(name):
         else:
             return ('ok', 200)
     else:
-        return flask.redirect(flask.url_for('hub', name=name))
+        return flask.redirect(flask.url_for('hub', name=hub.name))
 
 
 @app.route('/<name>/config', methods=['GET', 'POST'])
 @login_required
+@require_hub_access("config", json=True)
 def hub_config(name):
     hub = get_hub(name)
     config = {
@@ -151,6 +159,7 @@ def hub_config(name):
             "hub": {},
             "general": {
                 "chat_networks": app.config["CHAT_NETWORKS"],
+                "hub_visibility": hubs.models.HubConfig.VISIBILITY,
             },
         },
     }
diff --git a/hubs/views/root.py b/hubs/views/root.py
index 1250490..74215da 100644
--- a/hubs/views/root.py
+++ b/hubs/views/root.py
@@ -48,9 +48,6 @@ def login():
     else:
         return_point = default
 
-    hubs.models.User.get_or_create(
-        flask.g.db, username=flask.g.auth.nickname,
-        fullname=flask.g.auth.fullname)
     flask.flash('Login successful', 'success')
 
     return flask.redirect(return_point)
diff --git a/hubs/views/user.py b/hubs/views/user.py
index 62866ae..5012aea 100644
--- a/hubs/views/user.py
+++ b/hubs/views/user.py
@@ -6,14 +6,18 @@ import hubs.models
 import hubs.stream
 
 from hubs.app import app
-from .utils import login_required, get_hub
+from .utils import (
+    login_required, get_hub, get_visible_widgets, require_hub_access,
+    )
 
 
 @app.route('/<name>/stream')
 @app.route('/<name>/stream/')
 @login_required
+@require_hub_access("view")
 def stream(name):
     hub = get_hub(name)
+    widgets = get_visible_widgets(hub)
     saved = hubs.models.SavedNotification.by_username(name)
     saved = [n.__json__() for n in saved]
 
@@ -23,6 +27,7 @@ def stream(name):
     return flask.render_template(
         'stream.html',
         hub=hub,
+        widgets=widgets,
         saved=json.dumps(saved),
         actions=actions
     )
diff --git a/hubs/views/utils.py b/hubs/views/utils.py
index a90e468..0a89595 100644
--- a/hubs/views/utils.py
+++ b/hubs/views/utils.py
@@ -1,15 +1,16 @@
 from __future__ import unicode_literals
 
 import datetime
-import flask
 import functools
 import logging
 
-from hubs.models import Hub, Widget
+import flask
 from six.moves.urllib import parse as urlparse
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm.exc import NoResultFound
 
+from hubs.models import Hub, Widget
+
 
 log = logging.getLogger(__name__)
 
@@ -44,6 +45,27 @@ def get_widget_instance(hub, idx, session=None):
         flask.abort(404)
 
 
+def get_visible_widgets(hub):
+    from hubs.widgets import registry
+    widgets = {"left": [], "right": []}
+    for widget in hub.widgets:
+        if widget.plugin not in registry:
+            continue  # disabled widget
+        try:
+            user = flask.g.auth.user
+        except AttributeError:
+            user = None
+        if not widget.allows(user, "view"):
+            continue
+        if widget.left:
+            widgets["left"].append(widget)
+        else:
+            widgets["right"].append(widget)
+    widgets["left"].sort(key=lambda w: w.index)
+    widgets["right"].sort(key=lambda w: w.index)
+    return widgets
+
+
 class WidgetConfigError(Exception):
     pass
 
@@ -127,6 +149,38 @@ def login_required(function):
     return decorated_function
 
 
+def require_hub_access(action, url_param="name", json=False):
+    """Check access to the hub for the specified action."""
+    def decorator(function):
+        @functools.wraps(function)
+        def wrapper(*args, **kwargs):
+            hub_name = kwargs[url_param]
+            hub = get_hub(hub_name, load_config=True)
+            try:
+                user = flask.g.auth.user
+            except AttributeError:
+                user = None
+            if not hub.allows(user, action):
+                if action == "view":
+                    msg = "This hub is for members only."
+                elif action == "config":
+                    msg = "You are not allowed to configure this hub."
+                else:
+                    msg = "Access forbidden."
+                if json:
+                    result = flask.jsonify({
+                        "status": "ERROR", "message": msg, "hub": hub.name,
+                        })
+                else:
+                    result = flask.render_template(
+                        'errors/hub.html', hub=hub, msg=msg,
+                        )
+                return result, 403
+            return function(*args, **kwargs)
+        return wrapper
+    return decorator
+
+
 def is_safe_url(target):
     """ Checks that the target url is safe and sending to the current
     website not some other malicious one.
diff --git a/hubs/views/widget.py b/hubs/views/widget.py
index 39bb8f7..35fb12a 100644
--- a/hubs/views/widget.py
+++ b/hubs/views/widget.py
@@ -8,11 +8,13 @@ from pkg_resources import resource_isdir
 from .utils import (
     create_widget_instance, configure_widget_instance, get_hub,
     get_widget_instance, login_required, WidgetConfigError,
-    get_position,
+    get_position, require_hub_access,
     )
 
 
 @app.route('/<name>/add', methods=['GET', 'POST'])
+@login_required
+@require_hub_access("config")
 def hub_add_widget(name):
     hub = get_hub(name)
     position = get_position()
@@ -45,6 +47,8 @@ def hub_add_widget(name):
 
 
 @app.route('/<hub>/add/<widget>', methods=['GET', 'POST'])
+@login_required
+@require_hub_access("config", url_param="hub")
 def widget_add(hub, widget):
     hub = get_hub(hub)
     position = get_position()
@@ -65,6 +69,7 @@ def widget_add(hub, widget):
 
 @app.route('/<hub>/<int:idx>/edit', methods=['GET', 'POST'])
 @login_required
+@require_hub_access("config", url_param="hub")
 def widget_edit(hub, idx):
     widget_instance = get_widget_instance(hub, idx)
     if flask.request.method == 'POST':
@@ -85,6 +90,7 @@ def widget_edit(hub, idx):
 
 @app.route('/<hub>/<int:idx>/delete', methods=['POST'])
 @login_required
+@require_hub_access("config", url_param="hub")
 def widget_edit_delete(hub, idx):
     ''' Remove a widget from a hub. '''
     widget_instance = get_widget_instance(hub, idx)
@@ -100,6 +106,7 @@ def widget_edit_delete(hub, idx):
 
 
 @app.route('/<hub>/<int:idx>/json')
+@require_hub_access("view", url_param="hub")
 def widget_json(hub, idx):
     widget = get_widget_instance(hub, idx)
     response = flask.jsonify(widget.__json__())
diff --git a/hubs/widgets/halp/views.py b/hubs/widgets/halp/views.py
index 264da3d..f1aae71 100644
--- a/hubs/widgets/halp/views.py
+++ b/hubs/widgets/halp/views.py
@@ -7,7 +7,7 @@ import flask
 from flask.signals import template_rendered, before_render_template
 
 from hubs.models import Hub
-from hubs.views.utils import get_hub
+from hubs.views.utils import get_hub, require_hub_access
 from hubs.widgets import registry
 from hubs.widgets.base import WidgetView
 from .functions import GetRequests
@@ -33,9 +33,9 @@ class DataView(WidgetView):
 
     name = "data"
     url_rules = ["data"]
+    json = True
 
-    def dispatch_request(self, *args, **kwargs):
-        instance = self._get_instance(*args, **kwargs)
+    def get_context(self, instance, *args, **kwargs):
         get_requests = GetRequests(instance)
         data = {"requests": []}
         hubs_filter = flask.request.args.getlist("hubs")
@@ -55,7 +55,7 @@ class DataView(WidgetView):
             if len(data["requests"]) == 3:
                 # Only 3 requests on the main widget.
                 break
-        return flask.jsonify(data)
+        return data
 
 
 class SearchView(WidgetView):
@@ -64,9 +64,9 @@ class SearchView(WidgetView):
     name = "search"
     url_rules = ["search"]
     methods = ['GET', 'POST']
+    json = True
 
-    def dispatch_request(self, *args, **kwargs):
-        instance = self._get_instance(*args, **kwargs)
+    def get_context(self, instance, *args, **kwargs):
         get_requests = GetRequests(instance)
         # prepare filters
         if "hubs" in flask.request.values:
@@ -112,7 +112,7 @@ class SearchView(WidgetView):
             data["requests"], instance.config["per_page"])
         data["requests"] = requests
         data["page"] = page_data
-        return flask.jsonify(data)
+        return data
 
 
 class RequestersView(WidgetView):
@@ -121,9 +121,9 @@ class RequestersView(WidgetView):
     name = "requesters"
     url_rules = ["requesters"]
     MAX_SUGGESTS = 5
+    json = True
 
-    def dispatch_request(self, *args, **kwargs):
-        instance = self._get_instance(*args, **kwargs)
+    def get_context(self, instance, *args, **kwargs):
         get_requests = GetRequests(instance)
         results = set([
             req["author"]["name"] for req in get_requests()
@@ -132,7 +132,7 @@ class RequestersView(WidgetView):
         if query:
             results = [name for name in results if name.startswith(query)]
         results = sorted(list(results))[:self.MAX_SUGGESTS]
-        return flask.jsonify({"results": results})
+        return {"results": results}
 
 
 class ConfigView(WidgetView):
@@ -143,6 +143,7 @@ class ConfigView(WidgetView):
     name = "config"
     url_rules = ["config"]
     template_name = "halp_config.html"
+    permission = "config"
 
     def get_context(self, instance, *args, **kwargs):
         post_url = flask.url_for(
@@ -168,6 +169,7 @@ def hubs_suggest_view():
     return flask.jsonify({"results": [h.name for h in results]})
 
 
+@require_hub_access("config", url_param="hub")
 def add_view(hub):
     """The custom configuration panel when adding the widget.
 
@@ -188,7 +190,9 @@ def add_view(hub):
         )
     initial["hubs"] = [hub.name]
     context = dict(mode="add", url=post_url, initial=initial)
-    before_render_template.send(widget, template=template, context=context)
+    before_render_template.send(
+        flask.current_app, template=template, context=context)
     output = template.render(**context)
-    template_rendered.send(widget, template=template, context=context)
+    template_rendered.send(
+        flask.current_app, template=template, context=context)
     return output
diff --git a/hubs/widgets/view.py b/hubs/widgets/view.py
index 943302d..f02e777 100644
--- a/hubs/widgets/view.py
+++ b/hubs/widgets/view.py
@@ -1,5 +1,6 @@
 from __future__ import unicode_literals, absolute_import
 
+import flask
 from flask.signals import template_rendered, before_render_template
 from flask.views import View
 
@@ -44,6 +45,8 @@ class WidgetView(View):
     name = None
     url_rules = []
     template_name = None
+    json = False
+    permission = "view"
 
     def __init__(self, widget):
         """
@@ -96,12 +99,30 @@ class WidgetView(View):
         implement. It binds the other methods together.
         """
         instance = self._get_instance(*args, **kwargs)
+        try:
+            user = flask.g.auth.user
+        except AttributeError:
+            user = None
+        if not instance.allows(user, self.permission):
+            msg = "You are not allowed to access this widget."
+            if self.json:
+                result = flask.jsonify({
+                    "status": "ERROR", "message": msg,
+                    })
+            else:
+                result = flask.render_template(
+                    'errors/hub.html', hub=instance.hub, msg=msg,
+                    )
+            return result, 403
         context = self.get_context(instance, *args, **kwargs)
-        context.update(self.get_extra_context(instance, *args, **kwargs))
-        template = self.get_template()
-        before_render_template.send(
-            self.widget, template=template, context=context)
-        output = template.render(**context)
-        template_rendered.send(
-            self.widget, template=template, context=context)
-        return output
+        if self.json:
+            return flask.jsonify(context)
+        else:
+            context.update(self.get_extra_context(instance, *args, **kwargs))
+            template = self.get_template()
+            before_render_template.send(
+                flask.current_app, template=template, context=context)
+            output = template.render(**context)
+            template_rendered.send(
+                flask.current_app, template=template, context=context)
+            return output
diff --git a/requirements.txt b/requirements.txt
index 56ea13b..74264a8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,7 @@ blinker
 datanommer.models
 decorator
 dogpile.cache
+enum34
 fedmsg
 fedmsg_meta_fedora_infrastructure
 flask

From f6797c682f4785bef0f28fce9eea360853d23524 Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 06 2017 23:29:35 +0000
Subject: [PATCH 3/9] Add a config panel to view, add and delete members


---

diff --git a/hubs/models.py b/hubs/models.py
index 758a64f..d54dccd 100644
--- a/hubs/models.py
+++ b/hubs/models.py
@@ -123,9 +123,9 @@ class Association(BASE):
     role = sa.Column(sa.Enum(*roles), primary_key=True)
 
     user = relation("User", backref=backref(
-        'associations', cascade="all, delete"))
+        'associations', cascade="all, delete, delete-orphan"))
     hub = relation("Hub", backref=backref(
-        'associations', cascade="all, delete"))
+        'associations', cascade="all, delete, delete-orphan"))
 
     @classmethod
     def get(cls, hub, user, role):
diff --git a/hubs/static/client/app/core/HubConfig.jsx b/hubs/static/client/app/core/HubConfig.jsx
index 73cbc7a..f4b75af 100644
--- a/hubs/static/client/app/core/HubConfig.jsx
+++ b/hubs/static/client/app/core/HubConfig.jsx
@@ -7,6 +7,7 @@ import {
 import {
   GeneralPanel,
   ChatPanel,
+  UserPanel,
   NotImplementedPanel,
   } from './HubConfigPanel.jsx';
 import TabSet from '../components/TabSet.jsx';
@@ -56,13 +57,15 @@ export default class HubConfig extends React.Component {
     this.state = {
       hubConfig: {},
       generalConfig: {},
+      users: {owner: [], member: []},
       dirty: false,
       mustReload: false,  // the settings were changed at least once
       error: null,
       loading: false
-    }
+    };
     this.serverRequest = null;
     this.handleChange = this.handleChange.bind(this);
+    this.handleUserChange = this.handleUserChange.bind(this);
     this.loadState = this.loadState.bind(this);
     this.pushState = this.pushState.bind(this);
   }
@@ -73,21 +76,31 @@ export default class HubConfig extends React.Component {
       dataType: 'json',
       cache: false,
       success: function(data) {
-        this.setState({
-          hubConfig: data.result.hub,
-          generalConfig: data.result.general,
-          dirty: false,
-          error: null,
-          loading: false
-        });
+        if (data.status === "OK") {
+          this.setState({
+            hubConfig: data.result.hubconfig,
+            generalConfig: data.result.general,
+            users: data.result.users,
+            dirty: false,
+            error: null,
+          });
+        } else if (data.status === "ERROR") {
+          this.setState({error: data.message});
+        } else {
+          this.setState({
+            error: "Unknown status: " + data.status
+          });
+        }
       }.bind(this),
       error: function(xhr, status, err) {
-        console.error(this.props.url, status, err.toString());
-        this.setState({
-          error: err.toString(),
-          loading: false
-        });
-      }.bind(this)
+        var msg = err.toString();
+        if (!msg) { msg = "Error communicating with the server"; }
+        console.error(this.props.url, status, msg);
+        this.setState({error: msg});
+      }.bind(this),
+      complete: function(xhr, status) {
+        this.setState({loading: false});
+      }.bind(this),
     }
   }
 
@@ -98,10 +111,11 @@ export default class HubConfig extends React.Component {
     this.serverRequest = $.ajax(params);
   }
 
-  pushState() {
+  pushState(data, category) {
     var params = this.getRequestParams();
     params.method = "POST";
-    params.data = this.state.hubConfig;
+    params.data = data;
+    params.url += "?category=" + category;
     this.setState({loading: true});
     this.serverRequest = $.ajax(params);
   }
@@ -115,10 +129,17 @@ export default class HubConfig extends React.Component {
       mustReload: true,
     }, function() {
       window.clearTimeout(this.pushTimer);
-      this.pushTimer = window.setTimeout(this.pushState, 1000);
+      this.pushTimer = window.setTimeout(
+        this.pushState, 1000, this.state.hubConfig, "config");
     });
   }
 
+  handleUserChange(username, role) {
+    console.log("new role", username, role);
+    var data = {username: username, role: role};
+    this.pushState(data, "role_change");
+  }
+
   componentDidMount() {
     this.loadState();
   }
@@ -129,7 +150,7 @@ export default class HubConfig extends React.Component {
       // The configuration was changed but the current display may not
       // reflect it (for example, the displayed summary may be the old one).
       // The only way to make the changes visible is to reload the page.
-      window.location = window.location;
+      window.location.reload(true);
     }
   }
 
@@ -138,7 +159,6 @@ export default class HubConfig extends React.Component {
       <IntlProvider locale={navigator.language}>
         <div className="modal-dialog modal-lg" role="document">
           <div className="modal-content">
-            <form method="post" action={this.props.url}>
               <div className="modal-header">
                 <button
                   type="button"
@@ -164,21 +184,34 @@ export default class HubConfig extends React.Component {
                       generalConfig={this.state.generalConfig}
                       tabTitle={<FormattedMessage {...messages.general} />}
                       handleChange={this.handleChange}
+                      loading={this.state.loading}
                       />
-                    <NotImplementedPanel
+                    <UserPanel
+                      users={this.state.users.owner}
+                      generalConfig={this.state.generalConfig}
+                      role="owner"
+                      handleChange={this.handleUserChange}
                       tabTitle={<FormattedMessage {...messages.owners} />}
+                      loading={this.state.loading}
                       />
-                    <NotImplementedPanel
+                    <UserPanel
+                      users={this.state.users.member}
+                      generalConfig={this.state.generalConfig}
+                      role="member"
+                      handleChange={this.handleUserChange}
                       tabTitle={<FormattedMessage {...messages.members} />}
+                      loading={this.state.loading}
                       />
                     <ChatPanel
                       hubConfig={this.state.hubConfig}
                       generalConfig={this.state.generalConfig}
                       tabTitle={<FormattedMessage {...messages.chat} />}
                       handleChange={this.handleChange}
+                      loading={this.state.loading}
                       />
                     <NotImplementedPanel
                       tabTitle={<FormattedMessage {...messages.other} />}
+                      loading={this.state.loading}
                       />
                   </TabSet>
                 </div>
@@ -190,7 +223,7 @@ export default class HubConfig extends React.Component {
                   ></span>
                 <button
                   type="button"
-                  className="btn btn-default"
+                  className="btn btn-secondary"
                   data-dismiss="modal"
                   disabled={this.state.dirty}
                   >
@@ -210,7 +243,6 @@ export default class HubConfig extends React.Component {
                   </div>
                 }
               </div>
-            </form>
           </div>
         </div>
       </IntlProvider>
diff --git a/hubs/static/client/app/core/HubConfigPanel.jsx b/hubs/static/client/app/core/HubConfigPanel.jsx
index ddc22d3..865e340 100644
--- a/hubs/static/client/app/core/HubConfigPanel.jsx
+++ b/hubs/static/client/app/core/HubConfigPanel.jsx
@@ -83,6 +83,34 @@ const messages = defineMessages({
     id: "hubs.core.config.chat.network",
     defaultMessage: "IRC network",
   },
+  users_fullname: {
+    id: "hubs.core.config.users.fullname",
+    defaultMessage: "Full name",
+  },
+  users_username: {
+    id: "hubs.core.config.users.username",
+    defaultMessage: "Username",
+  },
+  users_change_role: {
+    id: "hubs.core.config.users.change_role",
+    defaultMessage: "Change role",
+  },
+  users_na: {
+    id: "hubs.core.config.users.na",
+    defaultMessage: "(N/A)",
+  },
+  users_remove_user: {
+    id: "hubs.core.config.users.remove_user",
+    defaultMessage: "(remove user)",
+  },
+  users_add_user: {
+    id: "hubs.core.config.users.add_user",
+    defaultMessage: "Add user...",
+  },
+  users_add: {
+    id: "hubs.core.config.users.add",
+    defaultMessage: "Add",
+  },
 });
 
 
@@ -105,7 +133,7 @@ export class GeneralPanel extends React.Component {
     var stillLoading = (typeof this.props.hubConfig.summary === "undefined");
     var visibilities = this.props.generalConfig.hub_visibility || [];
     return (
-      <div>
+      <form onSubmit={(e) => {e.preventDefault();}}>
         <FormattedMessage {...messages.general_title} tagName="h3" />
         <FormattedMessage {...messages.general_intro} tagName="p" />
         <div className="form-group">
@@ -177,6 +205,112 @@ export class GeneralPanel extends React.Component {
             <FormattedMessage {...messages.general_avatar_help} />
           </p>
         </div>
+      </form>
+    );
+  }
+
+}
+
+
+export class UserPanel extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      addUsername: "",
+    };
+    this.handleAdd = this.handleAdd.bind(this);
+    this.handleAddInputChange = this.handleAddInputChange.bind(this);
+    this.handleChange = this.handleChange.bind(this);
+  }
+
+  handleAdd(e) {
+    e.preventDefault();
+    this.props.handleChange(this.state.addUsername, this.props.role);
+    this.setState({addUsername: ""});
+  }
+
+  handleAddInputChange(e) {
+    this.setState({
+      addUsername: e.target.value,
+    });
+  }
+
+  handleChange(e) {
+    e.preventDefault();
+    this.props.handleChange(e.target.name, e.target.value);
+  }
+
+  render() {
+    var users = this.props.users.map(function(user) {
+      return (
+        <tr key={user.username}>
+          <td>{user.fullname}</td>
+          <td>{user.username}</td>
+          <td>
+            {user.locked ?
+              <FormattedMessage {...messages.users_na} />
+            :
+              <select
+                name={user.username}
+                value={user.role}
+                className="form-control"
+                onChange={this.handleChange}
+                disabled={this.props.loading}
+                >
+                {this.props.generalConfig.roles.map(function(role) {
+                  return (
+                    <option value={role} key={role}>{role}</option>
+                    );
+                })}
+                <FormattedMessage {...messages.users_remove_user}>
+                  {(value) => ( <option value="">{value}</option> )}
+                </FormattedMessage>
+              </select>
+            }
+          </td>
+        </tr>
+        );
+    }.bind(this));
+
+    return (
+      <div>
+        <h3>{this.props.tabTitle}</h3>
+        <p className="text-muted">[Membership requests will be shown here]</p>
+        <hr />
+        <form className="form-inline" onSubmit={this.handleAdd}
+              style={{marginBottom: "1em"}}>
+          <div className="form-group">
+            <FormattedMessage {...messages.users_add_user}>
+              {(value) => (
+                <input
+                  type="text" className="form-control"
+                  name="username" placeholder={value}
+                  onChange={this.handleAddInputChange}
+                  value={this.state.addUsername}
+                  style={{marginRight: "1em"}}
+                  />
+                )}
+            </FormattedMessage>
+          </div>
+          <button className="btn btn-primary" disabled={this.props.loading}>
+            <FormattedMessage {...messages.users_add} />
+          </button>
+        </form>
+        {users.length !== 0 &&
+        <table className="table table-hover table-sm">
+          <thead>
+            <tr>
+              <FormattedMessage {...messages.users_fullname} tagName="th" />
+              <FormattedMessage {...messages.users_username} tagName="th" />
+              <FormattedMessage {...messages.users_change_role} tagName="th" />
+            </tr>
+          </thead>
+          <tbody>
+            {users}
+          </tbody>
+        </table>
+        }
       </div>
     );
   }
@@ -210,7 +344,7 @@ export class ChatPanel extends React.Component {
     }
 
     return (
-      <div>
+      <form onSubmit={(e) => {e.preventDefault();}}>
         <FormattedMessage {...messages.chat_title} tagName="h3" />
         <FormattedMessage {...messages.chat_intro_1} tagName="p" />
         <FormattedMessage {...messages.chat_intro_2} tagName="p" />
@@ -244,7 +378,7 @@ export class ChatPanel extends React.Component {
             {networks}
           </select>
         </div>
-      </div>
+      </form>
     );
   }
 
diff --git a/hubs/templates/widget_add.html b/hubs/templates/widget_add.html
index f410ac8..3e0d4d6 100644
--- a/hubs/templates/widget_add.html
+++ b/hubs/templates/widget_add.html
@@ -23,6 +23,18 @@
       {% else %}
         <p>Nothing to configure</p>
       {% endfor %}
+      {% if hub.config.visibility == "preview" %}
+        <fieldset class="form-group ">
+          <strong>Visibility</strong>
+          <select name="visibility" class="form-control">
+            <option value="public">Public</option>
+            <option value="restricted">Restricted</option>
+          </select>
+          <small class="text-muted">
+            Restricted widgets will only be visible by logged-in users.
+          </small>
+        </fieldset>
+      {% endif %}
       </div> <!-- /modal-body -->
       <div class="modal-footer">
         <button type="button" class="btn btn-default" data-dismiss="modal">
diff --git a/hubs/templates/widget_edit.html b/hubs/templates/widget_edit.html
index 3139d5d..6118e57 100644
--- a/hubs/templates/widget_edit.html
+++ b/hubs/templates/widget_edit.html
@@ -22,6 +22,22 @@
       {% else %}
         <p>Nothing to configure</p>
       {% endfor %}
+      {% if hub.config.visibility == "preview" %}
+        <fieldset class="form-group ">
+          <strong>Visibility</strong>
+          <select name="visibility" class="form-control">
+            <option value="public"
+                {% if widget_instance.visibility == "public" %}selected{% endif %}
+                >Public</option>
+            <option value="restricted"
+                {% if widget_instance.visibility == "restricted" %}selected{% endif %}
+                >Restricted</option>
+          </select>
+          <small class="text-muted">
+            Restricted widgets will only be visible by logged-in users.
+          </small>
+        </fieldset>
+      {% endif %}
       </div> <!-- /modal-body -->
       <div class="modal-footer">
         <button type="button" class="btn btn-default" data-dismiss="modal">
diff --git a/hubs/tests/test_fedora_hubs_flask_api.py b/hubs/tests/test_fedora_hubs_flask_api.py
index 778ea54..29b91fc 100644
--- a/hubs/tests/test_fedora_hubs_flask_api.py
+++ b/hubs/tests/test_fedora_hubs_flask_api.py
@@ -401,7 +401,7 @@ class HubsAPITest(hubs.tests.APPTest):
 
     def test_hub_config_get(self):
         expected = {
-            "hub": {
+            "hubconfig": {
                 "summary": "Ralph",
                 "left_width": 8,
                 "avatar": (
@@ -414,6 +414,16 @@ class HubsAPITest(hubs.tests.APPTest):
             },
             "general": {
                 "chat_networks": app.config["CHAT_NETWORKS"],
+                "roles": ["owner", "member"],
+            },
+            "users": {
+                "member": [],
+                "owner": [{
+                    "fullname": "Ralph",
+                    "locked": True,
+                    "role": "owner",
+                    "username": "ralph",
+                }],
             },
         }
         user = tests.FakeAuthorization('ralph')
@@ -428,7 +438,7 @@ class HubsAPITest(hubs.tests.APPTest):
     def test_hub_config_post(self):
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
-            url = '/ralph/config'
+            url = '/ralph/config?category=config'
             result = self.app.post(url, data={
                 "summary": "changed value",
                 "chat_domain": "",
@@ -437,19 +447,19 @@ class HubsAPITest(hubs.tests.APPTest):
         result_data = json.loads(result.get_data(as_text=True))
         self.assertEqual(result_data["status"], "OK")
         self.assertEqual(
-            result_data["result"]["hub"]["summary"],
+            result_data["result"]["hubconfig"]["summary"],
             "changed value")
         self.assertEqual(
-            result_data["result"]["hub"]["chat_domain"],
+            result_data["result"]["hubconfig"]["chat_domain"],
             app.config["CHAT_NETWORKS"][0]["domain"])
 
     def test_hub_config_post_unknown_post_data(self):
         # Unknown POST data is silently ignored
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
-            url = '/ralph/config'
+            url = '/ralph/config?category=config'
             result = self.app.post(url, data={"non_existant": "dummy"})
-        self.assertEqual(result.status_code, 400)
+        self.assertEqual(result.status_code, 200)
         result_data = json.loads(result.get_data(as_text=True))
         self.assertEqual(result_data["status"], "ERROR")
         self.assertEqual(result_data["message"], "Invalid value(s)")
@@ -462,9 +472,9 @@ class HubsAPITest(hubs.tests.APPTest):
     def test_hub_config_post_invalid_chat_domain(self):
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
-            url = '/ralph/config'
+            url = '/ralph/config?category=config'
             result = self.app.post(url, data={"chat_domain": "dummy"})
-        self.assertEqual(result.status_code, 400)
+        self.assertEqual(result.status_code, 200)
         result_data = json.loads(result.get_data(as_text=True))
         self.assertEqual(result_data["status"], "ERROR")
         self.assertEqual(result_data["message"], "Invalid value(s)")
@@ -478,6 +488,6 @@ class HubsAPITest(hubs.tests.APPTest):
     def test_hub_config_unauthorized(self):
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
-            url = '/decause/config'
+            url = '/decause/config?category=config'
             result = self.app.post(url, data={"summary": "Defaced!"})
         self.assertEqual(result.status_code, 403)
diff --git a/hubs/views/hub.py b/hubs/views/hub.py
index 75b5764..c3ea5f4 100644
--- a/hubs/views/hub.py
+++ b/hubs/views/hub.py
@@ -153,45 +153,96 @@ def hub_edit_post(hub):
 @require_hub_access("config", json=True)
 def hub_config(name):
     hub = get_hub(name)
-    config = {
+    if flask.request.method == 'POST':
+        category = flask.request.args["category"]
+        if category == "config":
+            result = hub_config_post_config(hub)
+        if category == "role_change":
+            result = hub_config_post_role_change(hub)
+        if result is not None:
+            return result
+    result = hub_config_get(hub)
+    return flask.jsonify(result)
+
+
+def hub_config_get(hub):
+    roles = ["owner", "member"]
+    result = {
         "status": "OK",
         "result": {
-            "hub": {},
+            "hubconfig": hub.config.__json__(),
             "general": {
                 "chat_networks": app.config["CHAT_NETWORKS"],
                 "hub_visibility": hubs.models.HubConfig.VISIBILITY,
+                "roles": roles,
             },
+            "users": {role: [] for role in roles},
         },
     }
-    if flask.request.method == 'POST':
-        # Validate values
-        def _validate_chat_domain(value):
-            valid_chat_domains = [
-                network["domain"] for network in app.config["CHAT_NETWORKS"]
-                ]
-            if not value and len(valid_chat_domains) > 0:
-                value = valid_chat_domains[0]
-            if value not in valid_chat_domains:
-                raise ValueError("Unsupported chat domain.")
-            return value
-        validator = RequestValidator(dict(
-            # Only allow the parameters listed in __json__().
-            (key, None) for key in hub.config.__json__().keys()
-        ))
-        validator.converters["chat_domain"] = _validate_chat_domain
-        try:
-            values = validator(flask.request.form)
-        except ValueError as e:
-            config = {"status": "ERROR", "message": "Invalid value(s)"}
-            config["fields"] = e.args[0]
-            return flask.jsonify(config), 400
-        # Now set the configuration values
-        for key, value in values.items():
-            setattr(hub.config, key, value)
-        try:
-            flask.g.db.commit()
-        except Exception as err:
-            config = {"status": "ERROR", "message": str(err)}
-            return flask.jsonify(config), 400
-    config["result"]["hub"].update(hub.config.__json__())
-    return flask.jsonify(config)
+    for assoc in sorted(hub.associations, key=lambda a: a.user.username):
+        if assoc.role not in roles:
+            continue
+        user = {
+            "username": assoc.user.username,
+            "fullname": assoc.user.fullname,
+            "role": assoc.role,
+        }
+        if (assoc.role == "owner" and
+                assoc.user.username == flask.g.auth.user.username):
+            user["locked"] = True
+        result["result"]["users"][assoc.role].append(user)
+    return result
+
+
+def hub_config_post_config(hub):
+    # Validate values
+    def _validate_chat_domain(value):
+        valid_chat_domains = [
+            network["domain"] for network in app.config["CHAT_NETWORKS"]
+            ]
+        if not value and len(valid_chat_domains) > 0:
+            value = valid_chat_domains[0]
+        if value not in valid_chat_domains:
+            raise ValueError("Unsupported chat domain.")
+        return value
+
+    validator = RequestValidator(dict(
+        # Only allow the parameters listed in __json__().
+        (key, None) for key in hub.config.__json__().keys()
+    ))
+    validator.converters["chat_domain"] = _validate_chat_domain
+    try:
+        values = validator(flask.request.form)
+    except ValueError as e:
+        result = {"status": "ERROR", "message": "Invalid value(s)"}
+        result["fields"] = e.args[0]
+        return flask.jsonify(result)
+
+    # Now set the configuration values
+    for key, value in values.items():
+        setattr(hub.config, key, value)
+    try:
+        flask.g.db.commit()
+    except Exception as err:
+        result = {"status": "ERROR", "message": str(err)}
+        return flask.jsonify(result)
+
+
+def hub_config_post_role_change(hub):
+    username = flask.request.form["username"]
+    user = hubs.models.User.get(username=username)
+    if user is None:
+        result = {
+            "status": "ERROR",
+            "fields": {"username": "Unknown user"},
+            "message": "Unknown user",
+            }
+        return flask.jsonify(result)
+    role = flask.request.form.get("role")
+    for assoc in hub.associations:
+        if assoc.hub == hub and assoc.user == user:
+            hub.associations.remove(assoc)
+    if role:
+        hub.associations.append(hubs.models.Association(
+            hub=hub, user=user, role=role))
+    flask.g.db.commit()
diff --git a/hubs/views/utils.py b/hubs/views/utils.py
index 0a89595..15ee71a 100644
--- a/hubs/views/utils.py
+++ b/hubs/views/utils.py
@@ -113,7 +113,12 @@ def configure_widget_instance(widget_instance):
     cur_config = cur_config.copy()
     cur_config.update(config)
     widget_instance.config = cur_config
-    # TODO: use a SQLAlchemy signal here
+    # Set the visibility parameter.
+    try:
+        widget_instance.visibility = flask.request.form["visibility"]
+    except KeyError:
+        pass  # the value is optional.
+    # Update the hub's mtime.  TODO: use a SQLAlchemy signal here.
     widget_instance.hub.last_edited = datetime.datetime.utcnow()
     try:
         flask.g.db.commit()

From 9d703800365277bc63f53a8570880d15ee8512ea Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 06 2017 23:29:35 +0000
Subject: [PATCH 4/9] Documentation


---

diff --git a/docs/api.rst b/docs/api.rst
deleted file mode 100644
index a5c0786..0000000
--- a/docs/api.rst
+++ /dev/null
@@ -1,57 +0,0 @@
-Developer Interfaces
-====================
-
-This documents ways developers can interface with Fedora Hubs.
-
-
-View utils
-----------
-
-.. automodule:: hubs.views.utils
-   :members:
-
-
-.. _widgets-api:
-
-
-Widgets
--------
-
-.. automodule:: hubs.widgets
-   :members:
-   :show-inheritance:
-
-Widgets registry
-^^^^^^^^^^^^^^^^
-
-.. automodule:: hubs.widgets.registry
-   :members:
-   :show-inheritance:
-
-Widget class
-^^^^^^^^^^^^
-
-.. automodule:: hubs.widgets.base
-   :members:
-   :show-inheritance:
-
-Widget parameter validators
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. automodule:: hubs.widgets.validators
-   :members:
-   :show-inheritance:
-
-Widget view
-^^^^^^^^^^^
-
-.. automodule:: hubs.widgets.view
-   :members:
-   :show-inheritance:
-
-Caching
-^^^^^^^
-
-.. automodule:: hubs.widgets.caching
-   :members:
-   :show-inheritance:
diff --git a/docs/api/auth.rst b/docs/api/auth.rst
new file mode 100644
index 0000000..36b5cf3
--- /dev/null
+++ b/docs/api/auth.rst
@@ -0,0 +1,28 @@
+Authentication and authorization
+================================
+
+Authentication is the fact of checking that the user is who they say they are.
+
+Authorization is the fact of allowing or refusing access to resources for this
+user.
+
+In Hubs, authentication is delegated to Fedora's OpenID server, and the
+authorization system is in the :py:mod:`hubs.authz` module.
+
+Authorization
+-------------
+
+A hub's visibility is controlled by the ``HubConfig.visibility`` parameter. It
+can have 3 values:
+
+- ``public``: the hub is visible to everyone
+- ``private``: the hub is only visible to members
+- ``preview``: the hub is visible to everyone, but some widgets can be
+  restricted to logged-in users only
+
+To restrict a widget to logged-in users only on a ``preview`` hub, set its
+:py:attr:`~hubs.models.Widget.visibility` property to ``restricted``.
+
+.. automodule:: hubs.authz
+   :members:
+   :private-members:
diff --git a/docs/api/index.rst b/docs/api/index.rst
new file mode 100644
index 0000000..90ec30e
--- /dev/null
+++ b/docs/api/index.rst
@@ -0,0 +1,11 @@
+Developer Interfaces
+====================
+
+This documents ways developers can interface with Fedora Hubs.
+
+.. toctree::
+   :maxdepth: 2
+
+   views
+   widgets
+   auth
diff --git a/docs/api/views.rst b/docs/api/views.rst
new file mode 100644
index 0000000..08a2a90
--- /dev/null
+++ b/docs/api/views.rst
@@ -0,0 +1,9 @@
+Views
+=====
+
+View utils
+----------
+
+.. automodule:: hubs.views.utils
+   :members:
+
diff --git a/docs/api/widgets.rst b/docs/api/widgets.rst
new file mode 100644
index 0000000..021bf49
--- /dev/null
+++ b/docs/api/widgets.rst
@@ -0,0 +1,44 @@
+.. _widgets-api:
+
+
+Widgets
+=======
+
+.. automodule:: hubs.widgets
+   :members:
+   :show-inheritance:
+
+Widgets registry
+----------------
+
+.. automodule:: hubs.widgets.registry
+   :members:
+   :show-inheritance:
+
+Widget class
+------------
+
+.. automodule:: hubs.widgets.base
+   :members:
+   :show-inheritance:
+
+Widget parameter validators
+---------------------------
+
+.. automodule:: hubs.widgets.validators
+   :members:
+   :show-inheritance:
+
+Widget view
+-----------
+
+.. automodule:: hubs.widgets.view
+   :members:
+   :show-inheritance:
+
+Caching
+-------
+
+.. automodule:: hubs.widgets.caching
+   :members:
+   :show-inheritance:
diff --git a/docs/conf.py b/docs/conf.py
index 9264743..30c5281 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -34,6 +34,7 @@ extensions = [
     'sphinx.ext.doctest',
     'sphinx.ext.napoleon',
     'sphinx.ext.viewcode',
+    'sphinx.ext.todo',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -116,7 +117,7 @@ pygments_style = 'sphinx'
 # keep_warnings = False
 
 # If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
+todo_include_todos = True
 
 
 # -- Options for HTML output ----------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index 72ec92e..ed17c81 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -13,7 +13,7 @@ Contents:
 
    overview
    dev-guide
-   api
+   api/index
 
 
 Indices and tables
diff --git a/docs/overview.rst b/docs/overview.rst
index a72f167..5b661c4 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -11,8 +11,9 @@ mailing lists and IRC which can be intimidating to new contributors. The vision 
 to have a modern, web-based Fedora activity center which feels like the positive,
 connected energy of Flock or FUDCon, available to all of us at any time.
 
+
 Integration
-^^^^^^^^^^^
+-----------
 
 Fedora Hubs integrates with, or plans to integrate with, the following tools and projects:
 
@@ -35,3 +36,21 @@ If you would like to know more, `these blog posts / videos
 information about our vision for the project, or check out the
 `issue tracker <https://pagure.io/fedora-hubs/issues/>`_ to see what features are
 planned.
+
+
+Authentication
+--------------
+
+Users are authenticated through the `Fedora Account System
+<https://admin.fedoraproject.org/accounts>`_ (FAS). Every user in FAS will have
+a personal hub created for them when they first login on Fedora Hubs.
+
+Hubs can have 3 different visibility settings:
+
+- ``public``: the hub is visible to everyone
+- ``private``: the hub is only visible to members
+- ``preview``: the hub is visible to everyone, but some widgets can be
+  restricted to logged-in users only
+
+In case of ``preview`` hubs, each widget's visibility can be set in the
+widget's configuration panel.
diff --git a/hubs/authz.py b/hubs/authz.py
index ebd85ca..b38a150 100644
--- a/hubs/authz.py
+++ b/hubs/authz.py
@@ -1,21 +1,44 @@
+"""
+The authorization model is based on an access level hierarchy: each level
+grants more permissions than the previous level. The levels are represented by
+the :py:class:`AccessLevel` enum. For example they can be ``anonymous``,
+``logged_in``, ``member``, ``admin``... anything really, as long as there is a
+hierarchy.
+
+Permissions are named, for example ``view`` or ``edit``. Those permissions are
+linked to the corresponding access level using the :py:data:`PERMISSIONS`
+dictionary.  For example the ``view`` permission can be granted to
+``anonymous`` users, or to ``members`` only.
+
+Object instances where authorization applies can use the
+:py:class:`ObjectAuthzMixin` mixin which provides a method to check for
+permissions.  Those objects must implement a method in the mixin, see the class
+documentation for details.
+
+Todo:
+    Make a Flask extension that can be reused in other applications.
+    The :py:class:`AccessLevel` enum and the :py:data:`PERMISSIONS` mapping
+    will be configurable to suit the app's specifics.  There will be
+    separate backends for getting the user roles that can be chosen by
+    configuration.  The CAIAPI backend will be included.
+
+Attributes:
+    PERMISSIONS (dict): Mapping of permission names (string) to permission
+        level (:py:class:`AccessLevel` member).
+"""
+
 from __future__ import absolute_import, unicode_literals
 
 from enum import IntEnum
 from flask import current_app
 
 
-#class Authorization(object):
-#
-#    def __init__(self, app=None, db=None):
-#        self.db = db
-#        if app is not None:
-#            self.init_app(app)
-#
-#    def init_app(self):
-#        app.config.setdefault("AUTHZ_ADAPTER", None)
-
-
 class AccessLevel(IntEnum):
+    """Possible levels of access for a resource.
+
+    We use an ``IntEnum`` because there is a hierarchy in access level.
+    The ``IntEnum`` class make it easy because it is a subclass of integer.
+    """
     anonymous = 0
     logged_in = 1
     member = 2
@@ -35,8 +58,19 @@ PERMISSIONS = {
 
 
 class ObjectAuthzMixin(object):
+    """Make a model class authorization-aware.
+
+    This mixin introduces the :py:meth:`allows` method, which can be used to
+    check if a user has a permission.
+    """
 
     def allows(self, user, action):
+        """Check whether a user has the right to do the given action.
+
+        Args:
+            user (hubs.models.User): a User instance.
+            action (str): an action, which will be translated to a permission.
+        """
         site_admins = current_app.config.get("SITE_ADMINS", [])
         if user is not None and user.username in site_admins:
             return True
@@ -48,6 +82,19 @@ class ObjectAuthzMixin(object):
         return False
 
     def _get_auth_access_level(self, user):
+        """Get a user's access level.
+
+        A user's access level can come from its own properties (being logged-in
+        for example, or its username) and from its group roles. The global
+        access level is the highest access of the two.
+
+        Args:
+            user (hubs.models.User): a User instance.
+
+        Returns:
+            int: the member of :py:class:`AccessLevel` representing this user's
+                access level.
+        """
         if user is None:
             return AccessLevel.anonymous
         levels = [
@@ -57,9 +104,28 @@ class ObjectAuthzMixin(object):
         return max(l for l in levels if l is not None)
 
     def _get_auth_user_access_level(self, user):
+        """Get a user's access level based on its own properties.
+
+        Args:
+            user (hubs.models.User): a User instance.
+
+        Returns:
+            int: the member of :py:class:`AccessLevel` representing this user's
+                access level.
+        """
         return AccessLevel.logged_in
 
     def _get_auth_group_access_level(self, user):
+        """Get a user's access level based on its group roles.
+
+        Args:
+            user (hubs.models.User): a User instance.
+
+        Returns:
+            int: the member of :py:class:`AccessLevel` representing this user's
+                access level or ``None`` if no access level can be derived from
+                group roles.
+        """
         group = self._get_auth_group()
         user_roles = self._get_auth_user_roles(user)
         try:
@@ -74,15 +140,36 @@ class ObjectAuthzMixin(object):
             return None
 
     def _get_auth_group(self):
+        """Get the corresponding authentication backend's group.
+
+        For each object using this mixin, there can be a group in the
+        authentication backend that will control access to this resource.
+        Roles in this group can be mapped to AccessLevels in this app.
+        """
         raise NotImplementedError
 
     def _get_auth_user_roles(self, user):
-        return get_user_roles(user)
+        """Get all the roles for a user.
+
+        Args:
+            user (hubs.models.User): a User instance.
+
+        Returns:
+            dict: the mapping of group names (str) to roles (str) in the
+                authentication backend for this user.
+
+        Todo:
+            Use the new CAIAPI when it is ready.
+        """
+        return {g: "member" for g in user.groups}
 
     def _get_auth_permission_name(self, action):
-        return action
+        """Return the permission name for the given action.
 
+        Args:
+            action (str): an action name
 
-def get_user_roles(user):
-    # TODO: use the new API
-    return {g: "member" for g in user.groups}
+        Returns:
+            str: a key in the :py:data:`PERMISSIONS` dictionary.
+        """
+        return action
diff --git a/hubs/views/utils.py b/hubs/views/utils.py
index 15ee71a..1821026 100644
--- a/hubs/views/utils.py
+++ b/hubs/views/utils.py
@@ -1,3 +1,8 @@
+"""
+The :py:mod:`hubs.views.utils` module contains helper functions and classes
+for the views.
+"""
+
 from __future__ import unicode_literals
 
 import datetime

From 06f0aabc228a36ead8b1ab1cfdad66b3b609daf9 Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 11 2017 12:51:54 +0000
Subject: [PATCH 5/9] Add tests


...and un-skip the existing unit tests.

---

diff --git a/hubs/app.py b/hubs/app.py
index 7b28c4c..d50c365 100644
--- a/hubs/app.py
+++ b/hubs/app.py
@@ -65,12 +65,6 @@ def commas(numeric):
     return "{:,.2f}".format(numeric)
 
 
-@app.teardown_request
-def shutdown_session(exception=None):
-    """ Remove the DB session at the end of each request. """
-    session.remove()
-
-
 # Set up OpenIDConnect
 OIDC = OpenIDConnect(app, credentials_store=flask.session)
 
diff --git a/hubs/tests/__init__.py b/hubs/tests/__init__.py
index edd2ba5..ad600c0 100644
--- a/hubs/tests/__init__.py
+++ b/hubs/tests/__init__.py
@@ -1,17 +1,17 @@
 from __future__ import unicode_literals
 
-from contextlib import contextmanager
 import json
-from datetime import datetime, timedelta
 import os
-
-import munch
-from flask import template_rendered
-from os.path import dirname
 import unittest
+from contextlib import contextmanager
+from datetime import datetime, timedelta
+from os.path import dirname
 
+import flask
+import munch
 import requests
 import vcr
+from flask.signals import template_rendered
 
 import hubs.models
 
@@ -99,6 +99,21 @@ class APPTest(unittest.TestCase):
             raise IOError("Failed to talk to %r %r" % (response.url, response))
         return response.json()
 
+    def check_url(self, url, user=None, code=200):
+        with auth_set(hubs.app.app, user):
+            with hubs.app.app.app_context():
+                with captured_templates(flask.current_app) as templates:
+                    response = self.app.get(url)
+        self.assertEqual(response.status_code, code)
+        if templates:
+            self.assertEqual(len(templates), 1)
+            template, context = templates[0]
+        else:
+            template = context = None
+        response.context = context
+        response.template = template
+        return response
+
 
 @contextmanager
 def auth_set(APP, auth):
diff --git a/hubs/tests/hubs_test.cfg b/hubs/tests/hubs_test.cfg
index 66ccd47..a910e72 100644
--- a/hubs/tests/hubs_test.cfg
+++ b/hubs/tests/hubs_test.cfg
@@ -9,3 +9,5 @@ DB_URL = 'sqlite:///%s/test.db' % (os.path.dirname(os.path.abspath(__file__)))
 import os
 OIDC_CLIENT_SECRETS = os.path.join(os.path.dirname(
     os.path.abspath(__file__)), 'client_secrets.json')
+
+SITE_ADMINS = ["admin"]
diff --git a/hubs/tests/test_authz.py b/hubs/tests/test_authz.py
new file mode 100644
index 0000000..ec607de
--- /dev/null
+++ b/hubs/tests/test_authz.py
@@ -0,0 +1,113 @@
+from __future__ import absolute_import, unicode_literals
+
+from hubs.authz import ObjectAuthzMixin, AccessLevel, PERMISSIONS
+from hubs.tests import APPTest, FakeAuthorization
+
+
+class TestObj(ObjectAuthzMixin):
+
+    def __init__(self, roles=None):
+        self.roles = roles or {}
+
+    def _get_auth_group(self):
+        return "auth-group"
+
+    def _get_auth_user_roles(self, user):
+        return self.roles
+
+
+class AuthzTestCase(APPTest):
+
+    def run(self, *args, **kwargs):
+        import hubs.app
+        with hubs.app.app.app_context():
+            super(AuthzTestCase, self).run(*args, **kwargs)
+
+    def test_mixin_access_level_anonymous(self):
+        obj = TestObj()
+        self.assertEqual(
+            obj._get_auth_access_level(None), AccessLevel.anonymous)
+
+    def test_mixin_access_level_logged_in(self):
+        obj = TestObj()
+        user = FakeAuthorization('ralph')
+        self.assertEqual(
+            obj._get_auth_access_level(user), AccessLevel.logged_in)
+
+    def test_mixin_group_access_level(self):
+        obj = TestObj(roles={"auth-group": "member"})
+        user = FakeAuthorization('ralph')
+        self.assertEqual(
+            obj._get_auth_access_level(user), AccessLevel.member)
+
+    def test_mixin_group_access_level_no_role(self):
+        obj = TestObj(roles={"unrelated-group": "member"})
+        user = FakeAuthorization('ralph')
+        self.assertEqual(
+            obj._get_auth_access_level(user), AccessLevel.logged_in)
+
+    def test_mixin_group_access_level_unsupported(self):
+        obj = TestObj(roles={"auth-group": "dark_lord"})
+        user = FakeAuthorization('ralph')
+        self.assertEqual(
+            obj._get_auth_access_level(user), AccessLevel.logged_in)
+
+    def test_allows_anonymous(self):
+        obj = TestObj()
+        self.assertTrue(obj.allows(None, 'hub.public.view'))
+        self.assertTrue(obj.allows(None, 'hub.preview.view'))
+        self.assertFalse(obj.allows(None, 'hub.private.view'))
+        self.assertFalse(obj.allows(None, 'hub.users.manage'))
+        self.assertFalse(obj.allows(None, 'hub.config'))
+        self.assertTrue(obj.allows(None, 'widget.public.view'))
+        self.assertFalse(obj.allows(None, 'widget.restricted.view'))
+
+    def test_allows_logged_in(self):
+        user = FakeAuthorization('ralph')
+        obj = TestObj()
+        self.assertTrue(obj.allows(user, 'hub.public.view'))
+        self.assertTrue(obj.allows(user, 'hub.preview.view'))
+        self.assertFalse(obj.allows(user, 'hub.private.view'))
+        self.assertFalse(obj.allows(user, 'hub.users.manage'))
+        self.assertFalse(obj.allows(user, 'hub.config'))
+        self.assertTrue(obj.allows(user, 'widget.public.view'))
+        self.assertTrue(obj.allows(user, 'widget.restricted.view'))
+
+    def test_allows_member(self):
+        user = FakeAuthorization('ralph')
+        obj = TestObj(roles={"auth-group": "member"})
+        self.assertTrue(obj.allows(user, 'hub.public.view'))
+        self.assertTrue(obj.allows(user, 'hub.preview.view'))
+        self.assertTrue(obj.allows(user, 'hub.private.view'))
+        self.assertFalse(obj.allows(user, 'hub.users.manage'))
+        self.assertFalse(obj.allows(user, 'hub.config'))
+        self.assertTrue(obj.allows(user, 'widget.public.view'))
+        self.assertTrue(obj.allows(user, 'widget.restricted.view'))
+
+    def test_allows_sponsor(self):
+        user = FakeAuthorization('ralph')
+        obj = TestObj(roles={"auth-group": "sponsor"})
+        self.assertTrue(obj.allows(user, 'hub.public.view'))
+        self.assertTrue(obj.allows(user, 'hub.preview.view'))
+        self.assertTrue(obj.allows(user, 'hub.private.view'))
+        self.assertTrue(obj.allows(user, 'hub.users.manage'))
+        self.assertFalse(obj.allows(user, 'hub.config'))
+        self.assertTrue(obj.allows(user, 'widget.public.view'))
+        self.assertTrue(obj.allows(user, 'widget.restricted.view'))
+
+    def test_allows_owner(self):
+        user = FakeAuthorization('ralph')
+        obj = TestObj(roles={"auth-group": "owner"})
+        self.assertTrue(obj.allows(user, 'hub.public.view'))
+        self.assertTrue(obj.allows(user, 'hub.preview.view'))
+        self.assertTrue(obj.allows(user, 'hub.private.view'))
+        self.assertTrue(obj.allows(user, 'hub.users.manage'))
+        self.assertTrue(obj.allows(user, 'hub.config'))
+        self.assertTrue(obj.allows(user, 'widget.public.view'))
+        self.assertTrue(obj.allows(user, 'widget.restricted.view'))
+
+    def test_allows_site_admin(self):
+        user = FakeAuthorization('admin')
+        obj = TestObj()
+        for perm in PERMISSIONS.keys():
+            self.assertTrue(obj.allows(user, perm))
diff --git a/hubs/tests/test_fedora_hubs_flask_api.py b/hubs/tests/test_fedora_hubs_flask_api.py
index 29b91fc..a0a0d62 100644
--- a/hubs/tests/test_fedora_hubs_flask_api.py
+++ b/hubs/tests/test_fedora_hubs_flask_api.py
@@ -14,6 +14,7 @@ import hubs.models
 
 
 class HubsAPITest(hubs.tests.APPTest):
+
     def test_index_logged_out(self):
         result = self.app.get('/', follow_redirects=False)
         self.assertEqual(result.status_code, 302)
@@ -65,6 +66,29 @@ class HubsAPITest(hubs.tests.APPTest):
                 'Not logged in.  Click to <a href="/login">login</a>',
                 result.get_data(as_text=True))
 
+    def test_hub_preview(self):
+        hub = hubs.models.Hub.by_name('ralph')
+        hub.config.visibility = "preview"
+        # Preview hubs are accessible to anonymous users (but some
+        # widgets may be restricted).
+        with tests.auth_set(app, None):
+            result = self.app.get('/ralph', follow_redirects=True)
+            self.assertEqual(result.status_code, 200)
+
+    def test_hub_private(self):
+        hub = hubs.models.Hub.by_name('ralph')
+        hub.config.visibility = "private"
+        self.session.commit()
+        # Private hubs are not accessible to anonymous users.
+        with tests.auth_set(app, None):
+            result = self.app.get('/ralph', follow_redirects=True)
+            self.assertEqual(result.status_code, 403)
+        # But they are accessible to members.
+        user = tests.FakeAuthorization('ralph')
+        with tests.auth_set(app, user):
+            result = self.app.get('/ralph', follow_redirects=True)
+            self.assertEqual(result.status_code, 200)
+
     def test_hub_json(self):
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
@@ -80,6 +104,7 @@ class HubsAPITest(hubs.tests.APPTest):
                 'chat_domain': None,
                 "left_width": 8,
                 "summary": "Ralph",
+                "visibility": "public",
             },
             "members": ["ralph"],
             "name": "ralph",
@@ -89,46 +114,49 @@ class HubsAPITest(hubs.tests.APPTest):
         }
         self.assertDictEqual(data, json.loads(result.get_data(as_text=True)))
 
-    @unittest.skip("Authorization layer not present yet")
+    def test_hub_private_json(self):
+        hub = hubs.models.Hub.by_name('ralph')
+        hub.config.visibility = "private"
+        self.session.commit()
+        # Private hubs are not accessible to anonymous users.
+        with tests.auth_set(app, None):
+            result = self.app.get('/ralph/json', follow_redirects=True)
+            self.assertEqual(result.status_code, 403)
+        # But they are accessible to members.
+        user = tests.FakeAuthorization('ralph')
+        with tests.auth_set(app, user):
+            result = self.app.get('/ralph/json', follow_redirects=True)
+            self.assertEqual(result.status_code, 200)
+
     def test_hub_edit_get_logged_out(self):
-        # FAILING: Authorization layer not present yet
         with tests.auth_set(app, None):
-            result = self.app.get('/ralph/edit', follow_redirects=True)
-            self.assertEqual(result.status_code, 403)  # failing right
-            # We do not have the authorization layer in place yet
+            result = self.app.get('/ralph/edit', follow_redirects=False)
+        self.assertEqual(result.status_code, 302)
+        self.assertEqual(urlparse(result.location).path, "/login")
 
-    @unittest.skip("Authorization layer not present yet")
     def test_hub_edit_get_logged_in_not_owner(self):
-        # FAILING: Authorization layer not present yet
         user = tests.FakeAuthorization('not_ralph')
         with tests.auth_set(app, user):
             result = self.app.get('/ralph/edit', follow_redirects=True)
-            self.assertEqual(result.status_code, 403)  # failing right
-            # We do not have the authorization layer in place yet
+            self.assertEqual(result.status_code, 403)
 
     def test_hub_edit_get_logged_in_owner(self):
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
             result = self.app.get('/ralph/edit', follow_redirects=True)
             self.assertEqual(result.status_code, 200)
-            # We do not have the authorization layer so everyone can edit
 
-    @unittest.skip("Authorization layer not present yet")
     def test_hub_edit_post_logged_out(self):
-        # FAILING: Authorization layer not present yet
         with tests.auth_set(app, None):
-            result = self.app.post('/ralph/edit', follow_redirects=True)
-            self.assertEqual(result.status_code, 403)  # failing right
-            # We do not have the authorization layer in place yet
+            result = self.app.post('/ralph/edit', follow_redirects=False)
+        self.assertEqual(result.status_code, 302)
+        self.assertEqual(urlparse(result.location).path, "/login")
 
-    @unittest.skip("Authorization layer not present yet")
     def test_hub_edit_post_logged_in_not_owner(self):
-        # FAILING: Authorization layer not present yet
         user = tests.FakeAuthorization('not_ralph')
         with tests.auth_set(app, user):
             result = self.app.post('/ralph/edit', follow_redirects=True)
-            self.assertEqual(result.status_code, 403)  # failing right
-            # We do not have the authorization layer in place yet
+            self.assertEqual(result.status_code, 403)
 
     def test_hub_edit_post_logged_in_owner_empty_data(self):
         user = tests.FakeAuthorization('ralph')
@@ -303,10 +331,10 @@ class HubsAPITest(hubs.tests.APPTest):
             expected_str = '/ralph/37/edit'
             self.assertIn(expected_str, result.get_data(as_text=True))
 
-    @unittest.skip("Authorization layer not present yet")
     def test_hub_edit_widget_get_logged_out(self):
-        result = self.app.get('/ralph/31/edit', follow_redirects=True)
-        self.assertEqual(result.status_code, 403)
+        result = self.app.get('/ralph/31/edit', follow_redirects=False)
+        self.assertEqual(result.status_code, 302)
+        self.assertEqual(urlparse(result.location).path, "/login")
 
     def test_hub_edit_widget_post_empty_data_logged_in(self):
         user = tests.FakeAuthorization('ralph')
@@ -411,10 +439,12 @@ class HubsAPITest(hubs.tests.APPTest):
                     ),
                 "chat_channel": None,
                 "chat_domain": None,
+                "visibility": "public",
             },
             "general": {
                 "chat_networks": app.config["CHAT_NETWORKS"],
                 "roles": ["owner", "member"],
+                "hub_visibility": hubs.models.HubConfig.VISIBILITY,
             },
             "users": {
                 "member": [],
@@ -484,8 +514,14 @@ class HubsAPITest(hubs.tests.APPTest):
             "Unsupported chat domain."
             )
 
-    @unittest.skip("Authorization layer not present yet")
-    def test_hub_config_unauthorized(self):
+    def test_hub_config_get_unauthorized(self):
+        user = tests.FakeAuthorization('ralph')
+        with tests.auth_set(app, user):
+            url = '/decause/config'
+            result = self.app.get(url)
+        self.assertEqual(result.status_code, 403)
+
+    def test_hub_config_post_unauthorized(self):
         user = tests.FakeAuthorization('ralph')
         with tests.auth_set(app, user):
             url = '/decause/config?category=config'
diff --git a/hubs/tests/test_models.py b/hubs/tests/test_models.py
index c3279cd..d6cc11c 100644
--- a/hubs/tests/test_models.py
+++ b/hubs/tests/test_models.py
@@ -1,9 +1,11 @@
 from __future__ import unicode_literals
 
+import fedmsg.config
+
 import hubs
 import hubs.models
 import hubs.tests
-import fedmsg.config
+from hubs.authz import AccessLevel
 
 fedmsg_config = fedmsg.config.load_config()
 
@@ -153,3 +155,81 @@ class ModelTest(hubs.tests.APPTest):
                           session=self.session,
                           username=username,
                           visited_hub=hub)
+
+    def test_auth_hub_widget_access_level(self):
+        username = 'ralph'
+        ralph = hubs.models.User.get(username)
+        hub_ralph = hubs.models.Hub.get(username)
+        widget_ralph = hubs.models.Widget.query.filter_by(
+            hub=hub_ralph, plugin="contact").one()
+        hub_decause = hubs.models.Hub.get("decause")
+        widget_decause = hubs.models.Widget.query.filter_by(
+            hub=hub_decause, plugin="contact").one()
+        self.assertEqual(
+            hub_decause._get_auth_access_level(ralph),
+            AccessLevel.logged_in)
+        self.assertEqual(
+            widget_decause._get_auth_access_level(ralph),
+            AccessLevel.logged_in)
+        self.assertEqual(
+            hub_ralph._get_auth_access_level(ralph),
+            AccessLevel.owner)
+        self.assertEqual(
+            widget_ralph._get_auth_access_level(ralph),
+            AccessLevel.owner)
+
+    def test_auth_hub_auth_group(self):
+        username = "ralph"
+        hub = hubs.models.Hub.get(username)
+        self.assertEqual(hub._get_auth_group(), username)
+        # Uncomment this when CAIAPI is active.
+        # hub.config.auth_group = "testing"
+        # self.assertEqual(hub._get_auth_group(), "testing")
+
+    def test_auth_hub_permission_name(self):
+        hub = hubs.models.Hub.get("ralph")
+        self.assertEqual(
+            hub._get_auth_permission_name("view"), "hub.public.view")
+        self.assertEqual(
+            hub._get_auth_permission_name("users.manage"), "hub.users.manage")
+        self.assertEqual(
+            hub._get_auth_permission_name("config"), "hub.config")
+        hub.config.visibility = "preview"
+        self.assertEqual(
+            hub._get_auth_permission_name("view"), "hub.preview.view")
+        hub.config.visibility = "private"
+        self.assertEqual(
+            hub._get_auth_permission_name("view"), "hub.private.view")
+
+    def test_auth_hub_widget_user_roles(self):
+        username = "ralph"
+        ralph = hubs.models.User.get(username)
+        hub = hubs.models.Hub.get(username)
+        widget = hubs.models.Widget.query.filter_by(
+            hub=hub, plugin="contact").one()
+        assert len(hub.associations) == 1
+        assert hub.associations[0].user.username == "ralph"
+        for role in ["owner", "sponsor", "member"]:
+            hub.associations[0].role = role
+            self.assertEqual(
+                hub._get_auth_user_roles(ralph), {"ralph": role})
+            self.assertEqual(
+                widget._get_auth_user_roles(ralph), {"ralph": role})
+
+    def test_auth_widget_permission_name(self):
+        hub = hubs.models.Hub.get("ralph")
+        widget = hubs.models.Widget.query.filter_by(
+            hub=hub, plugin="contact").one()
+        self.assertEqual(
+            widget._get_auth_permission_name("view"), "hub.public.view")
+        hub.config.visibility = "private"
+        self.session.commit()
+        self.assertEqual(
+            widget._get_auth_permission_name("view"), "hub.private.view")
+        hub.config.visibility = "preview"
+        assert widget.visibility == "public"
+        self.assertEqual(
+            widget._get_auth_permission_name("view"), "widget.public.view")
+        widget.visibility = "restricted"
+        self.assertEqual(
+            widget._get_auth_permission_name("view"), "widget.restricted.view")
diff --git a/hubs/tests/test_view_utils.py b/hubs/tests/test_view_utils.py
index d5e8218..0101299 100644
--- a/hubs/tests/test_view_utils.py
+++ b/hubs/tests/test_view_utils.py
@@ -1,11 +1,11 @@
 from __future__ import unicode_literals
 
 import hubs.models
-import hubs.tests
+from hubs.tests import APPTest, FakeAuthorization, auth_set
 from hubs.views.utils import get_visible_widgets
 
 
-class ModelTest(hubs.tests.APPTest):
+class ViewUtilsTest(APPTest):
 
     def test_removed_widget(self):
         hub = hubs.models.Hub.get("ralph")
@@ -13,8 +13,7 @@ class ModelTest(hubs.tests.APPTest):
             hub=hub, plugin="does-not-exist",
             left=True, index=-1, _config="{}")
         self.session.add(widget)
-        user = hubs.tests.FakeAuthorization('ralph')
-        with hubs.tests.auth_set(self.app.application, user):
+        with auth_set(self.app.application, FakeAuthorization('ralph')):
             with self.app.application.app_context():
                 widgets = get_visible_widgets(hub)
         self.assertNotIn(
@@ -22,3 +21,62 @@ class ModelTest(hubs.tests.APPTest):
         widget.left = False
         self.assertNotIn(
             "does-not-exist", [w.plugin for w in widgets["right"]])
+
+    def test_get_visible_widgets(self):
+        hub = hubs.models.Hub.get('ralph')
+        with auth_set(self.app.application, None):
+            with self.app.application.app_context():
+                widgets = get_visible_widgets(hub)
+        self.assertListEqual(
+            [w.plugin for w in widgets["left"]],
+            ["feed"])
+        self.assertListEqual(
+            [w.plugin for w in widgets["right"]],
+            ['contact', 'meetings', 'fedmsgstats', 'workflow.updates2stable',
+             'workflow.pendingacls', 'subscriptions', 'badges', 'pagure_pr',
+             'github_pr', 'bugzilla', 'halp', 'about']
+            )
+
+    def test_get_visible_widgets_preview(self):
+        hub = hubs.models.Hub.by_name('ralph')
+        hub.config.visibility = "preview"
+        meetings = hubs.models.Widget.query.filter_by(
+            hub=hub, plugin="meetings").first()
+        # make sure the test is valid
+        assert meetings.left is False
+        # set the widget to "restricted"
+        meetings.visibility = "restricted"
+        # Restricted widgets in preview hubs aren't displayed to
+        # anonymous users.
+        with auth_set(self.app.application, None):
+            with self.app.application.app_context():
+                widgets = get_visible_widgets(hub)
+        self.assertNotIn(
+            "meetings", [w.plugin for w in widgets["right"]])
+        # But they are displayed to members.
+        with auth_set(self.app.application, FakeAuthorization('ralph')):
+            with self.app.application.app_context():
+                widgets = get_visible_widgets(hub)
+        self.assertIn(
+            "meetings", [w.plugin for w in widgets["right"]])
+
+    def test_get_visible_widgets_private(self):
+        hub = hubs.models.Hub.by_name('ralph')
+        hub.config.visibility = "private"
+        meetings = hubs.models.Widget.query.filter_by(
+            hub=hub, plugin="meetings").first()
+        # make sure the test is valid
+        assert meetings.left is False
+        # Widgets in private hubs aren't displayed to
+        # anonymous users.
+        with auth_set(self.app.application, None):
+            with self.app.application.app_context():
+                widgets = get_visible_widgets(hub)
+        self.assertEqual(widgets["left"], [])
+        self.assertEqual(widgets["right"], [])
+        # But they are displayed to members.
+        with auth_set(self.app.application, FakeAuthorization('ralph')):
+            with self.app.application.app_context():
+                widgets = get_visible_widgets(hub)
+        self.assertNotEqual(len(widgets["left"]), 0)
+        self.assertNotEqual(len(widgets["right"]), 0)
diff --git a/hubs/tests/test_widgets/__init__.py b/hubs/tests/test_widgets/__init__.py
index 3297022..7d171e4 100644
--- a/hubs/tests/test_widgets/__init__.py
+++ b/hubs/tests/test_widgets/__init__.py
@@ -1,13 +1,16 @@
 from __future__ import unicode_literals
 
 import json
+import unittest
 
 import hubs.models
-from hubs.tests import APPTest
+from hubs.tests import APPTest, FakeAuthorization
 
 
 class WidgetTest(APPTest):
 
+    plugin = None  # The name in hubs.widgets.registry
+
     def populate(self):
         super(WidgetTest, self).populate()
         hub = hubs.models.Hub.by_name('ralph')
@@ -18,4 +21,27 @@ class WidgetTest(APPTest):
             _config=json.dumps({"urls": "ralph/"})
         )
         hub.widgets.append(widget)
-        hubs.app.session.commit()
+        self.session.commit()
+
+    def _test_view_authz(self):
+        # Test authorizations on the root view.
+        if not self.plugin:
+            raise unittest.SkipTest
+        hub = hubs.models.Hub.query.get("ralph")
+        widget = self.widget_instance(hub.name, self.plugin)
+        url = '/%s/w/%s/%i/' % (hub.name, self.plugin, widget.idx)
+        # Public
+        self.check_url(url, None, 200)
+        # Preview
+        hub.config.visibility = "preview"
+        widget.visibility = "restricted"
+        self.session.commit()
+        self.check_url(url, None, 403)  # anonymous
+        self.check_url(url, FakeAuthorization('decause'), 200)  # logged-in
+        # Private
+        widget.hub.config.visibility = "private"
+        self.session.commit()
+        self.check_url(url, None, 403)  # anonymous
+        self.check_url(url, FakeAuthorization('decause'), 403)  # logged-in
+        hub.subscribe(hubs.models.User.query.get("decause"), "member")
+        self.check_url(url, FakeAuthorization('decause'), 200)  # member
diff --git a/hubs/tests/test_widgets/test_about.py b/hubs/tests/test_widgets/test_about.py
index edab332..dd00e20 100644
--- a/hubs/tests/test_widgets/test_about.py
+++ b/hubs/tests/test_widgets/test_about.py
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
 
 import json
 
-from hubs.tests import FakeAuthorization, auth_set
-from hubs.tests.test_widgets import WidgetTest
+from hubs.tests import FakeAuthorization
+from . import WidgetTest
 
 
 class TestAbout(WidgetTest):
@@ -12,12 +12,13 @@ class TestAbout(WidgetTest):
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/ralph/%i/json' % widget.idx)
-        self.assertEqual(response.status_code, 200)
+        response = self.check_url('/ralph/%i/json' % widget.idx, user)
         data = json.loads(response.get_data(as_text=True))
         self.assertDictEqual(data['data'], {
             'text': 'Testing.',
             'title': "<span class='glyphicon glyphicon-info-sign' "
                      "aria-hidden='true'></span> About",
         })
+
+    def test_view_authz(self):
+        self._test_view_authz()
diff --git a/hubs/tests/test_widgets/test_badges.py b/hubs/tests/test_widgets/test_badges.py
index 30284fd..3cd2ee2 100644
--- a/hubs/tests/test_widgets/test_badges.py
+++ b/hubs/tests/test_widgets/test_badges.py
@@ -3,8 +3,8 @@ from __future__ import unicode_literals
 import json
 
 import hubs.widgets
-from hubs.tests import FakeAuthorization, auth_set
-from hubs.tests.test_widgets import WidgetTest
+from hubs.tests import FakeAuthorization
+from . import WidgetTest
 
 
 class TestBadges(WidgetTest):
@@ -13,9 +13,7 @@ class TestBadges(WidgetTest):
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/ralph/%i/json' % widget.idx)
-        self.assertEqual(response.status_code, 200)
+        response = self.check_url('/ralph/%i/json' % widget.idx, user)
         data = json.loads(response.get_data(as_text=True))
         self.assertEquals(data['plugin'], 'badges')
         self.assertIn('assertions', data['data'].keys())
@@ -58,3 +56,6 @@ class TestBadges(WidgetTest):
         func = module.get_cached_functions()['GetBadges']
         result = func(widget).should_invalidate(msg)
         self.assertTrue(result)
+
+    def test_view_authz(self):
+        self._test_view_authz()
diff --git a/hubs/tests/test_widgets/test_contact.py b/hubs/tests/test_widgets/test_contact.py
index 3e63953..3d885d1 100644
--- a/hubs/tests/test_widgets/test_contact.py
+++ b/hubs/tests/test_widgets/test_contact.py
@@ -7,8 +7,8 @@ import mock
 
 import hubs
 from hubs.models import Hub, Widget
-from hubs.tests import APPTest, FakeAuthorization, auth_set
-from hubs.tests.test_widgets import WidgetTest
+from hubs.tests import FakeAuthorization, auth_set
+from . import WidgetTest
 
 
 def mocked_requests_get(*args, **kwargs):
@@ -56,7 +56,7 @@ def mocked_requests_post(*args, **kwargs):
     return MockResponse({}, 404)
 
 
-class ContactsTest(APPTest):
+class ContactsTest(WidgetTest):
 
     plugin = "contact"
 
@@ -74,9 +74,7 @@ class ContactsTest(APPTest):
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/ralph/%i/json' % widget.idx)
-        self.assertEqual(response.status_code, 200)
+        response = self.check_url('/ralph/%i/json' % widget.idx, user)
         data = json.loads(response.get_data(as_text=True))
         self.assertDictEqual(data['data'], {
             'account_age': 'Oct 2010',
@@ -89,6 +87,9 @@ class ContactsTest(APPTest):
             'usergroup': True,
         })
 
+    def test_view_authz(self):
+        self._test_view_authz()
+
     @mock.patch('requests.get', side_effect=mocked_requests_get)
     def test_plus_plus_get_valid(self, mock_get):
         widget = self.widget_instance("ralph", self.plugin)
diff --git a/hubs/tests/test_widgets/test_fedmsgstats.py b/hubs/tests/test_widgets/test_fedmsgstats.py
index 4861b49..87b5ae0 100644
--- a/hubs/tests/test_widgets/test_fedmsgstats.py
+++ b/hubs/tests/test_widgets/test_fedmsgstats.py
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
 
 import json
 
-from hubs.tests import FakeAuthorization, auth_set
-from hubs.tests.test_widgets import WidgetTest
+from hubs.tests import FakeAuthorization
+from . import WidgetTest
 
 
 class TestFedmsgStats(WidgetTest):
@@ -12,9 +12,7 @@ class TestFedmsgStats(WidgetTest):
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/ralph/%i/json' % widget.idx)
-        self.assertEqual(response.status_code, 200)
+        response = self.check_url('/ralph/%i/json' % widget.idx, user)
         data = json.loads(response.get_data(as_text=True))
         self.assertDictEqual(data['data'], {
             u'fedmsgs': 83854,
@@ -27,3 +25,6 @@ class TestFedmsgStats(WidgetTest):
             u'hub_subscribe_url': u'/api/hub/ralph/subscribe',
             u'hub_unsubscribe_url': u'/api/hub/ralph/unsubscribe',
         })
+
+    def test_view_authz(self):
+        self._test_view_authz()
diff --git a/hubs/tests/test_widgets/test_halp.py b/hubs/tests/test_widgets/test_halp.py
index 81c1831..ed60356 100644
--- a/hubs/tests/test_widgets/test_halp.py
+++ b/hubs/tests/test_widgets/test_halp.py
@@ -2,10 +2,10 @@ from __future__ import unicode_literals, absolute_import
 
 import json
 
-from hubs.models import Hub
-from hubs.tests import captured_templates
-from hubs.tests.test_widgets import WidgetTest
+from hubs.models import Hub, User
+from hubs.tests import FakeAuthorization
 from hubs.widgets import registry
+from . import WidgetTest
 
 
 SAMPLE_DATA = [
@@ -97,6 +97,7 @@ class HalpViewsTestCase(WidgetTest):
         dhrish = Hub.query.get("dhrish")
         dhrish.config.chat_channel = "#foss2serve"
         self.session.commit()
+        self.session.refresh(self.widget)
 
     def _get_sample_data_with_hub_keys(self):
         hub_names = ["infra", "devyani7", "dhrish", "decause", "decause"]
@@ -109,12 +110,11 @@ class HalpViewsTestCase(WidgetTest):
         return data
 
     def test_root(self):
-        with captured_templates(self.widget.module) as templates:
-            response = self.app.get('/ralph/w/halp/%i/' % self.widget.idx)
-        self.assertEqual(response.status_code, 200)
-        self.assertEqual(len(templates), 1)
-        _template, context = templates[0]
-        self.assertEqual(context["hubs"], ['fedora-devel'])
+        response = self.check_url('/ralph/w/halp/%i/' % self.widget.idx)
+        self.assertEqual(response.context["hubs"], ['fedora-devel'])
+
+    def test_view_authz(self):
+        self._test_view_authz()
 
     def test_json(self):
         response = self.app.get('/ralph/%i/json' % self.widget.idx)
@@ -124,30 +124,48 @@ class HalpViewsTestCase(WidgetTest):
         self.assertIn('hubs', data['data'].keys())
 
     def test_config_add(self):
-        with captured_templates(self.widget.module) as templates:
-            response = self.app.get('/ralph/w/halp/add?position=right')
-        self.assertEqual(response.status_code, 200)
-        self.assertEqual(len(templates), 1)
-        _template, context = templates[0]
-        self.assertEqual(context["mode"], "add")
-        self.assertEqual(context["initial"], {
+        user = FakeAuthorization('ralph')
+        response = self.check_url('/ralph/w/halp/add?position=right', user)
+        self.assertEqual(response.context["mode"], "add")
+        self.assertEqual(response.context["initial"], {
             "hubs": ["ralph"], "per_page": 4,
             })
-        self.assertEqual(context["url"], "/ralph/add/halp?position=right")
+        self.assertEqual(
+            response.context["url"], "/ralph/add/halp?position=right")
 
     def test_config_edit(self):
-        with captured_templates(self.widget.module) as templates:
-            response = self.app.get('/ralph/w/halp/%i/config'
-                                    % self.widget.idx)
-        self.assertEqual(response.status_code, 200)
-        self.assertEqual(len(templates), 1)
-        _template, context = templates[0]
-        self.assertEqual(context["mode"], "edit")
-        self.assertEqual(context["initial"], {
+        user = FakeAuthorization('ralph')
+        url = '/ralph/w/halp/%i/config' % self.widget.idx
+        response = self.check_url(url, user)
+        self.assertEqual(response.context["mode"], "edit")
+        self.assertEqual(response.context["initial"], {
             'hubs': ['fedora-devel'],
             'per_page': 3,
             })
-        self.assertEqual(context["url"], "/ralph/%i/edit" % self.widget.idx)
+        self.assertEqual(
+            response.context["url"], "/ralph/%i/edit" % self.widget.idx)
+
+    def test_config_authz(self):
+        urls = [
+            '/ralph/w/halp/add',
+            '/ralph/w/halp/%i/config' % self.widget.idx,
+            ]
+        # Anonymous
+        for url in urls:
+            self.check_url(url, None, 403)
+        # Logged-in
+        user = FakeAuthorization('decause')
+        for url in urls:
+            self.check_url(url, user, 403)
+        # Member
+        hub = Hub.query.get("ralph")
+        hub.subscribe(User.query.get("decause"), "member")
+        for url in urls:
+            self.check_url(url, user, 403)
+        # Owner
+        user = FakeAuthorization('ralph')
+        for url in urls:
+            self.check_url(url, user, 200)
 
     def test_config_hubs_suggest(self):
         response = self.app.get('/w/halp/hubs')
diff --git a/hubs/tests/test_widgets/test_library.py b/hubs/tests/test_widgets/test_library.py
index eaac331..6d85ad2 100644
--- a/hubs/tests/test_widgets/test_library.py
+++ b/hubs/tests/test_widgets/test_library.py
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
 
 import json
 
-from hubs.tests import FakeAuthorization, auth_set
-from hubs.tests.test_widgets import WidgetTest
+from hubs.tests import FakeAuthorization
+from . import WidgetTest
 
 
 class TestLibrary(WidgetTest):
@@ -12,9 +12,7 @@ class TestLibrary(WidgetTest):
     def test_data_simple(self):
         widget = self.widget_instance('ralph', self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/ralph/%i/json' % widget.idx)
-        self.assertEqual(response.status_code, 200)
+        response = self.check_url('/ralph/%i/json' % widget.idx, user)
         data = json.loads(response.get_data(as_text=True))
         expected_dict = {
             "config": {
@@ -34,3 +32,6 @@ class TestLibrary(WidgetTest):
             "plugin": "library"
         }
         self.assertDictEqual(data['data'], expected_dict['data'])
+
+    def test_view_authz(self):
+        self._test_view_authz()
diff --git a/hubs/tests/test_widgets/test_meetings.py b/hubs/tests/test_widgets/test_meetings.py
index 8c2784b..49f088b 100644
--- a/hubs/tests/test_widgets/test_meetings.py
+++ b/hubs/tests/test_widgets/test_meetings.py
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
 
 import json
 
-from hubs.tests import FakeAuthorization, auth_set
-from hubs.tests.test_widgets import WidgetTest
+from hubs.tests import FakeAuthorization
+from . import WidgetTest
 
 
 class TestMeetings(WidgetTest):
@@ -13,9 +13,7 @@ class TestMeetings(WidgetTest):
         team = 'i18n'
         widget = self.widget_instance(team, self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/%s/%i/json' % (team, widget.idx))
-        self.assertEqual(200, response.status_code)
+        response = self.check_url('/%s/%i/json' % (team, widget.idx), user)
         data = json.loads(response.get_data(as_text=True))
         calendar_name = data['data']['calendar']
         self.assertIn(team, calendar_name)
@@ -24,10 +22,11 @@ class TestMeetings(WidgetTest):
         team = 'i18n'
         widget = self.widget_instance(team, self.plugin)
         user = FakeAuthorization('ralph')
-        with auth_set(self.app.application, user):
-            response = self.app.get('/%s/w/%s/%i/'
-                                    % (team, self.plugin, widget.idx))
-        self.assertEqual(200, response.status_code)
+        url = '/%s/w/%s/%i/' % (team, self.plugin, widget.idx)
+        response = self.check_url(url, user)
         self.assertIn('i18n', response.get_data(as_text=True))
         self.assertIn('<strong>Request A New Meeting</strong>',
                       response.get_data(as_text=True))
+
+    def test_view_authz(self):
+        self._test_view_authz()
diff --git a/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_badges.TestBadges.test_view_authz b/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_badges.TestBadges.test_view_authz
new file mode 100644
index 0000000..cf740f8
--- /dev/null
+++ b/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_badges.TestBadges.test_view_authz
@@ -0,0 +1,2705 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://badges.fedoraproject.org/user/ralph/json
+  response:
+    body: {string: !!python/unicode '{"user_count": 44887, "rank": 5, "avatar": "https://seccdn.libravatar.org/avatar/9c9f7784935381befc302fe3c814f9136e7a33953d0318761669b8643f4df55c?s=100&d=https%3A%2F%2Fbadges.fedoraproject.org%2Fstatic%2Fimg%2Fbadger_avatar.png",
+        "percentile": "0.01", "assertions": [{"description": "You attended Flock 2013,
+        the Fedora Contributor Conference", "tags": "flock,event,", "issued": 1375373082.0,
+        "image": "https://badges.fedoraproject.org/pngs/flock-2013-attendee.png",
+        "first_awarded": 1375373082.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "rbergero", "last_awarded": 1473972160.0, "percent_earned": 0.18034465868103486,
+        "id": "flock-2013-attendee", "times_awarded": 81, "name": "Flock 2013 Attendee"},
+        {"description": "Voted on 50 tags for packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1375450118.0, "image": "https://badges.fedoraproject.org/pngs/tagger-02.png",
+        "first_awarded": 1375450118.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "imabug", "last_awarded": 1490198996.0, "percent_earned": 0.663490225764795,
+        "id": "tagger-tagger-ii", "times_awarded": 298, "name": "Tagger (Tagger II)"},
+        {"description": "Voted on 10 tags for packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1375450119.0, "image": "https://badges.fedoraproject.org/pngs/tagger-01.png",
+        "first_awarded": 1375450119.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "bbroad", "last_awarded": 1491002037.0, "percent_earned": 1.0508972703388697,
+        "id": "junior-tagger-tagger-i", "times_awarded": 472, "name": "Junior Tagger
+        (Tagger I)"}, {"description": "You signed the Fedora Project Contributor Agreement
+        (FPCA)", "tags": "account,community,", "issued": 1375572218.0, "image": "https://badges.fedoraproject.org/pngs/involvement.png",
+        "first_awarded": 1375572176.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "ylarguex", "last_awarded": 1491516838.0, "percent_earned": 53.357527719641986,
+        "id": "involvement", "times_awarded": 23965, "name": "Involvement"}, {"description":
+        "Participated in an IRC meeting.", "tags": "irc,community,", "issued": 1375734057.0,
+        "image": "https://badges.fedoraproject.org/pngs/irc-speak-up.png", "first_awarded":
+        1375734057.0, "first_awarded_person": "ralph", "last_awarded_person": "icole",
+        "last_awarded": 1491505117.0, "percent_earned": 3.9564501046444316, "id":
+        "speak-up!", "times_awarded": 1777, "name": "Speak Up!"}, {"description":
+        "Edited at least 10 wiki pages.", "tags": "content,wiki,", "issued": 1375888314.0,
+        "image": "https://badges.fedoraproject.org/pngs/junior-editor.png", "first_awarded":
+        1375888314.0, "first_awarded_person": "ralph", "last_awarded_person": "kdudka",
+        "last_awarded": 1491484751.0, "percent_earned": 3.3129981742886407, "id":
+        "junior-editor", "times_awarded": 1488, "name": "Junior Editor"}, {"description":
+        "Edited at least 20 wiki pages.", "tags": "content,wiki,", "issued": 1375888315.0,
+        "image": "https://badges.fedoraproject.org/pngs/associate-editor.png", "first_awarded":
+        1375888315.0, "first_awarded_person": "ralph", "last_awarded_person": "ankit1997",
+        "last_awarded": 1491242577.0, "percent_earned": 2.108473972480741, "id": "associate-editor",
+        "times_awarded": 947, "name": "Associate Editor"}, {"description": "Chaired
+        an IRC meeting.", "tags": "irc,community,", "issued": 1375889493.0, "image":
+        "https://badges.fedoraproject.org/pngs/irc-secretary-general.png", "first_awarded":
+        1375889493.0, "first_awarded_person": "ralph", "last_awarded_person": "yzhang",
+        "last_awarded": 1491431124.0, "percent_earned": 1.5451752237609653, "id":
+        "secretary-general", "times_awarded": 694, "name": "Secretary General"}, {"description":
+        "Voted on 250 tags for packages in Fedora Tagger.", "tags": "quality,tagger,",
+        "issued": 1375898650.0, "image": "https://badges.fedoraproject.org/pngs/tagger-03.png",
+        "first_awarded": 1375898650.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "imabug", "last_awarded": 1490198974.0, "percent_earned": 0.3139332947410607,
+        "id": "senior-tagger-tagger-iii", "times_awarded": 141, "name": "Senior Tagger
+        (Tagger III)"}, {"description": "Succesfully completed 250 koji builds.",
+        "tags": "development,koji,", "issued": 1377742978.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-04.png",
+        "first_awarded": 1377711940.0, "first_awarded_person": "remi", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491425650.0, "percent_earned": 0.5454869305784388,
+        "id": "if-you-build-it...-koji-success-iv", "times_awarded": 245, "name":
+        "If you build it... (Koji Success IV)"}, {"description": "Succesfully completed
+        a koji build.", "tags": "development,koji,", "issued": 1377742982.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-01.png", "first_awarded":
+        1377708898.0, "first_awarded_person": "mcpierce", "last_awarded_person": "nickboldt",
+        "last_awarded": 1490915696.0, "percent_earned": 2.858796811684553, "id": "if-you-build-it...-koji-success-i",
+        "times_awarded": 1284, "name": "If you build it... (Koji Success I)"}, {"description":
+        "Succesfully completed 10 koji builds.", "tags": "development,koji,", "issued":
+        1377742977.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-02.png",
+        "first_awarded": 1377708893.0, "first_awarded_person": "mcpierce", "last_awarded_person":
+        "sjenning", "last_awarded": 1491017600.0, "percent_earned": 2.3288952219797836,
+        "id": "if-you-build-it...-koji-success-ii", "times_awarded": 1046, "name":
+        "If you build it... (Koji Success II)"}, {"description": "Succesfully completed
+        50 koji builds.", "tags": "development,koji,", "issued": 1377742981.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-03.png", "first_awarded":
+        1377708896.0, "first_awarded_person": "mcpierce", "last_awarded_person": "tartare",
+        "last_awarded": 1491481451.0, "percent_earned": 1.4138130649686065, "id":
+        "if-you-build-it...-koji-success-iii", "times_awarded": 635, "name": "If you
+        build it... (Koji Success III)"}, {"description": "Ten or more questions and
+        answers on the Ask Fedora system.", "tags": "ask fedora,community,", "issued":
+        1377794097.0, "image": "https://badges.fedoraproject.org/pngs/askbot-curious-penguin-02.png",
+        "first_awarded": 1375889645.0, "first_awarded_person": "hmaarrfk", "last_awarded_person":
+        "fedo25", "last_awarded": 1491440137.0, "percent_earned": 1.4182660195039407,
+        "id": "curious-penguin-ask-fedora-ii", "times_awarded": 637, "name": "Curious
+        Penguin (Ask Fedora II)"}, {"description": "You had your first interaction
+        with the Ask Fedora forum system.", "tags": "ask fedora,community,", "issued":
+        1377794096.0, "image": "https://badges.fedoraproject.org/pngs/askbot-curious-penguin-01.png",
+        "first_awarded": 1375889644.0, "first_awarded_person": "hmaarrfk", "last_awarded_person":
+        "andre", "last_awarded": 1491511008.0, "percent_earned": 9.738611568775884,
+        "id": "curious-penguin-ask-fedora-i", "times_awarded": 4374, "name": "Curious
+        Penguin (Ask Fedora I)"}, {"description": "You updated your Security Question
+        with the Fedora Account System (FAS)", "tags": "account,community,", "issued":
+        1375988994.0, "image": "https://badges.fedoraproject.org/pngs/fas-riddle-me-this.png",
+        "first_awarded": 1375988994.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dtemirgaziyev", "last_awarded": 1491482061.0, "percent_earned": 1.903638063855368,
+        "id": "riddle-me-this", "times_awarded": 855, "name": "Riddle Me This"}, {"description":
+        "Pushed 30 commits to a Fedora package repository.", "tags": "development,",
+        "issued": 1376076329.0, "image": "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-02.png",
+        "first_awarded": 1375887918.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "mhonek", "last_awarded": 1490899614.0, "percent_earned": 2.0127354499710557,
+        "id": "you-can-call-me-patches-scm-ii", "times_awarded": 904, "name": "You
+        can call me \"Patches\" (SCM II)"}, {"description": "Submitted a koji build
+        that failed to build.", "tags": "development,oops,koji,", "issued": 1377887821.0,
+        "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-01.png",
+        "first_awarded": 1377711403.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "nickboldt", "last_awarded": 1491244448.0, "percent_earned": 1.94816760920871,
+        "id": "what-goes-up...-koji-failure-i", "times_awarded": 875, "name": "What
+        goes up... (Koji Failure I)"}, {"description": "Pushed 5 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076328.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-01.png", "first_awarded":
+        1375887918.0, "first_awarded_person": "jforbes", "last_awarded_person": "jscotka",
+        "last_awarded": 1491289110.0, "percent_earned": 2.6695462439328494, "id":
+        "you-can-call-me-patches-scm-i", "times_awarded": 1199, "name": "You can call
+        me \"Patches\" (SCM I)"}, {"description": "Pushed 400 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076329.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-04.png", "first_awarded":
+        1375892509.0, "first_awarded_person": "rdieter", "last_awarded_person": "berrange",
+        "last_awarded": 1491242273.0, "percent_earned": 0.5588457941844414, "id":
+        "you-can-call-me-patches-scm-iv", "times_awarded": 251, "name": "You can call
+        me \"Patches\" (SCM IV)"}, {"description": "Pushed 90 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076329.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-03.png", "first_awarded":
+        1375889053.0, "first_awarded_person": "mhlavink", "last_awarded_person": "cydrobolt",
+        "last_awarded": 1490744785.0, "percent_earned": 1.3603776105445964, "id":
+        "you-can-call-me-patches-scm-iii", "times_awarded": 611, "name": "You can
+        call me \"Patches\" (SCM III)"}, {"description": "Submitted 20 or more koji
+        builds that failed to build.", "tags": "development,oops,koji,", "issued":
+        1377887820.0, "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-03.png",
+        "first_awarded": 1377711401.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491441515.0, "percent_earned": 0.6523578394264594,
+        "id": "what-goes-up...-koji-failure-iii", "times_awarded": 293, "name": "What
+        goes up... (Koji Failure III)"}, {"description": "Submitted 5 or more koji
+        builds that failed to build.", "tags": "development,oops,koji,", "issued":
+        1377887822.0, "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-02.png",
+        "first_awarded": 1377711406.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "tkrizek", "last_awarded": 1490376511.0, "percent_earned": 1.329206928797257,
+        "id": "what-goes-up...-koji-failure-ii", "times_awarded": 597, "name": "What
+        goes up... (Koji Failure II)"}, {"description": "Pushed 100 bodhi updates
+        to the testing repositories.", "tags": "development", "issued": 1376077757.0,
+        "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-03.png",
+        "first_awarded": 1375892382.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491441872.0, "percent_earned": 0.5299015897047691,
+        "id": "is-this-thing-on-updates-testing-iii", "times_awarded": 238, "name":
+        "Is this thing on? (Updates-Testing III)"}, {"description": "*(a path to follow)*\nTested
+        and added karma to 5 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1376171387.0, "image": "https://badges.fedoraproject.org/pngs/tester-02.png",
+        "first_awarded": 1375889658.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "toshio", "last_awarded": 1491516269.0, "percent_earned": 2.70739635748319,
+        "id": "discovery-of-the-footprints-tester-ii", "times_awarded": 1216, "name":
+        "Discovery of the Footprints (Tester II)"}, {"description": "You got your
+        first 10 Fedora Badges", "tags": "community,badges,", "issued": 1376171392.0,
+        "image": "https://badges.fedoraproject.org/pngs/badger-01.png", "first_awarded":
+        1376118882.0, "first_awarded_person": "ignatenkobrain", "last_awarded_person":
+        "apollo7", "last_awarded": 1491362331.0, "percent_earned": 7.547757937391459,
+        "id": "junior-badger-badger-i", "times_awarded": 3390, "name": "Junior Badger
+        (Badger I)"}, {"description": "You pushed your first bodhi update to the stable
+        repositories.", "tags": "development,", "issued": 1376171906.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-01.png",
+        "first_awarded": 1375891204.0, "first_awarded_person": "blakegardner", "last_awarded_person":
+        "scox", "last_awarded": 1491419635.0, "percent_earned": 2.562675335084829,
+        "id": "like-a-rock-updates-stable-i", "times_awarded": 1151, "name": "Like
+        a Rock (Updates-Stable I)"}, {"description": "*(but only its rear, not its
+        head)*\nTested and added karma to 10 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1376171388.0, "image": "https://badges.fedoraproject.org/pngs/tester-03.png",
+        "first_awarded": 1375889660.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "alciregi", "last_awarded": 1491294835.0, "percent_earned": 1.6631785189473214,
+        "id": "perceiving-the-bull-tester-iii", "times_awarded": 747, "name": "Perceiving
+        the Bull (Tester III)"}, {"description": "Pushed 100 bodhi updates to the
+        stable repositories.", "tags": "development,", "issued": 1376171907.0, "image":
+        "https://badges.fedoraproject.org/pngs/like-a-rock-03.png", "first_awarded":
+        1375930621.0, "first_awarded_person": "cicku", "last_awarded_person": "mystro256",
+        "last_awarded": 1491340270.0, "percent_earned": 0.39408647637707617, "id":
+        "like-a-rock-updates-stable-iii", "times_awarded": 177, "name": "Like a Rock
+        (Updates-Stable III)"}, {"description": "*(aimless searching, only the sound
+        of cicadas)*\nTested and added karma to 1 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1376171388.0, "image": "https://badges.fedoraproject.org/pngs/tester-01.png",
+        "first_awarded": 1375889660.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "landgraf", "last_awarded": 1491498009.0, "percent_earned": 6.412254530881239,
+        "id": "in-search-of-the-bull-tester-i", "times_awarded": 2880, "name": "In
+        Search of the Bull (Tester I)"}, {"description": "Pushed 20 bodhi updates
+        to the stable repositories.", "tags": "development,", "issued": 1376171906.0,
+        "image": "https://badges.fedoraproject.org/pngs/like-a-rock-02.png", "first_awarded":
+        1375893154.0, "first_awarded_person": "pingou", "last_awarded_person": "cqi",
+        "last_awarded": 1491420777.0, "percent_earned": 1.2379213608229058, "id":
+        "like-a-rock-updates-stable-ii", "times_awarded": 556, "name": "Like a Rock
+        (Updates-Stable II)"}, {"description": "Partied with Flock 2013 (at the South
+        Carolina Aquarium)", "tags": "party,flock,event,", "issued": 1376252202.0,
+        "image": "https://badges.fedoraproject.org/pngs/tanked.png", "first_awarded":
+        1376252202.0, "first_awarded_person": "ralph", "last_awarded_person": "gnokii",
+        "last_awarded": 1411656869.0, "percent_earned": 0.14026806786302712, "id":
+        "tanked!", "times_awarded": 63, "name": "Tanked!"}, {"description": "You proposed
+        a new idea for a Fedora Badge.  Thanks!", "tags": "community,badges,", "issued":
+        1376261121.0, "image": "https://badges.fedoraproject.org/pngs/badge-muse-01.png",
+        "first_awarded": 1376261121.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "frantisekz", "last_awarded": 1484316413.0, "percent_earned": 0.3161597720087278,
+        "id": "badge-muse-badge-ideas-i", "times_awarded": 142, "name": "Badge Muse
+        (Badge Ideas I)"}, {"description": "You got a new koji certificate!", "tags":
+        "account,community,", "issued": 1376347603.0, "image": "https://badges.fedoraproject.org/pngs/fas-bona-fide.png",
+        "first_awarded": 1375891140.0, "first_awarded_person": "ralphlange", "last_awarded_person":
+        "dtemirgaziyev", "last_awarded": 1491477255.0, "percent_earned": 5.508304760208398,
+        "id": "bona-fide", "times_awarded": 2474, "name": "Bona Fide"}, {"description":
+        " Submitted a Supplemental Wallpaper Idea for Fedora 20.", "tags": "content,design,nuancier,",
+        "issued": 1378218417.0, "image": "https://badges.fedoraproject.org/pngs/design-keepin-fedora-beautiful-f20.png",
+        "first_awarded": 1377269416.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "iwan14", "last_awarded": 1378218427.0, "percent_earned": 0.05788840895934453,
+        "id": "keepin-fedora-beautiful-f20", "times_awarded": 26, "name": "Keepin
+        Fedora Beautiful (F20)"}, {"description": "You got (about) 50 Fedora Badges.  Way
+        to go!", "tags": "community,badges,", "issued": 1378504718.0, "image": "https://badges.fedoraproject.org/pngs/badger-02.png",
+        "first_awarded": 1377470469.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "mailga", "last_awarded": 1491037049.0, "percent_earned": 0.823796589036826,
+        "id": "senior-badger-badger-ii", "times_awarded": 370, "name": "Senior Badger
+        (Badger II)"}, {"description": "You pushed your first bodhi update to the
+        testing repositories.", "tags": "development", "issued": 1376077757.0, "image":
+        "https://badges.fedoraproject.org/pngs/is-this-thing-on-01.png", "first_awarded":
+        1375889661.0, "first_awarded_person": "fantom", "last_awarded_person": "olem",
+        "last_awarded": 1491070078.0, "percent_earned": 2.658413857594514, "id": "is-this-thing-on-updates-testing-i",
+        "times_awarded": 1194, "name": "Is this thing on? (Updates-Testing I)"}, {"description":
+        "Pushed 20 bodhi updates to the testing repositories.", "tags": "development",
+        "issued": 1376077757.0, "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-02.png",
+        "first_awarded": 1375889878.0, "first_awarded_person": "stefw", "last_awarded_person":
+        "dustymabe", "last_awarded": 1491432891.0, "percent_earned": 1.467248519392617,
+        "id": "is-this-thing-on-updates-testing-ii", "times_awarded": 659, "name":
+        "Is this thing on? (Updates-Testing II)"}, {"description": "You added a co-maintainer
+        to a package. BFF!", "tags": "development,", "issued": 1376531564.0, "image":
+        "https://badges.fedoraproject.org/pngs/pkgdb-partners-in-crime.png", "first_awarded":
+        1376531564.0, "first_awarded_person": "ralph", "last_awarded_person": "dustymabe",
+        "last_awarded": 1491406327.0, "percent_earned": 1.714387496103665, "id": "partners-in-crime",
+        "times_awarded": 770, "name": "Partners in Crime"}, {"description": "*(a great
+        struggle, repeated escapes, discipline required)*\nTested and added karma
+        to 20 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1378251927.0, "image": "https://badges.fedoraproject.org/pngs/tester-04.png",
+        "first_awarded": 1375889657.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "mcepl", "last_awarded": 1491490795.0, "percent_earned": 0.961838179632186,
+        "id": "catching-the-bull-tester-iv", "times_awarded": 432, "name": "Catching
+        the Bull (Tester IV)"}, {"description": "Voted on 1000 tags for packages in
+        Fedora Tagger.", "tags": "quality,tagger,", "issued": 1378613794.0, "image":
+        "https://badges.fedoraproject.org/pngs/tagger-04.png", "first_awarded": 1376814408.0,
+        "first_awarded_person": "jamielinux", "last_awarded_person": "bt0dotninja",
+        "last_awarded": 1489638271.0, "percent_earned": 0.16253284053969808, "id":
+        "master-tagger-tagger-iv", "times_awarded": 73, "name": "Master Tagger (Tagger
+        IV)"}, {"description": "A member of the sysadmin-badges group.  Nothing to
+        see here..  move along.", "tags": "infrastructure,badges,community,", "issued":
+        1378319150.0, "image": "https://badges.fedoraproject.org/pngs/fas-inside-job.png",
+        "first_awarded": 1378319146.0, "first_awarded_person": "oddshocks", "last_awarded_person":
+        "jflory7", "last_awarded": 1472154166.0, "percent_earned": 0.02226477267667097,
+        "id": "inside-job", "times_awarded": 10, "name": "Inside Job"}, {"description":
+        "Indicated your timezone in FAS so everyone can sync with you", "tags": "account,community,",
+        "issued": 1378320160.0, "image": "https://badges.fedoraproject.org/pngs/fas-white-rabbit.png",
+        "first_awarded": 1378313171.0, "first_awarded_person": "churchyard", "last_awarded_person":
+        "salvadorhm", "last_awarded": 1491509400.0, "percent_earned": 8.144453845126241,
+        "id": "white-rabbit", "times_awarded": 3658, "name": "White Rabbit"}, {"description":
+        "You have a Libravatar associated with your Fedora OpenID.", "tags": "libravatar,community,",
+        "issued": 1376946138.0, "image": "https://badges.fedoraproject.org/pngs/mugshot.png",
+        "first_awarded": 1376946138.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jcholast", "last_awarded": 1491423845.0, "percent_earned": 3.061406243042258,
+        "id": "mugshot", "times_awarded": 1375, "name": "Mugshot"}, {"description":
+        "A member of sysadmin-main.", "tags": "development,infrastructure,", "issued":
+        1378320106.0, "image": "https://badges.fedoraproject.org/pngs/fas-trust-me-i-know-what-i-am-doing.png",
+        "first_awarded": 1378320079.0, "first_awarded_person": "lmacken", "last_awarded_person":
+        "pbrobinson", "last_awarded": 1430409877.0, "percent_earned": 0.03785011355034065,
+        "id": "trust-me,-i-know-what-i-am-doing", "times_awarded": 17, "name": "Trust
+        me, I know what I am doing"}, {"description": "Has used an ansible playbook
+        to deploy to Fedora Infrastructure", "tags": "development,infrastructure,",
+        "issued": 1378321037.0, "image": "https://badges.fedoraproject.org/pngs/ansible-maestro.png",
+        "first_awarded": 1378321037.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jkaluza", "last_awarded": 1490254934.0, "percent_earned": 0.10241795431268647,
+        "id": "maestro", "times_awarded": 46, "name": "Maestro"}, {"description":
+        "Created your own user profile page on the Fedora Wiki", "tags": "content,",
+        "issued": 1378321704.0, "image": "https://badges.fedoraproject.org/pngs/wiki-let-me-introduce-myself.png",
+        "first_awarded": 1378321704.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "cra", "last_awarded": 1491437628.0, "percent_earned": 4.250345103976488,
+        "id": "let-me-introduce-myself", "times_awarded": 1909, "name": "Let Me Introduce
+        Myself"}, {"description": "You submitted a bodhi update including multiple
+        packages.", "tags": "development,", "issued": 1377206257.0, "image": "https://badges.fedoraproject.org/pngs/bodhi-binary-star.png",
+        "first_awarded": 1376049444.0, "first_awarded_person": "jwrdegoede", "last_awarded_person":
+        "ngompa", "last_awarded": 1490659963.0, "percent_earned": 0.7614552255421472,
+        "id": "binary-star", "times_awarded": 342, "name": "Binary Star"}, {"description":
+        "Became a sponsor of new packagers.", "tags": "development,account,", "issued":
+        1377283223.0, "image": "https://badges.fedoraproject.org/pngs/fas-packager-sponsor.png",
+        "first_awarded": 1377283156.0, "first_awarded_person": "abompard", "last_awarded_person":
+        "xavierb", "last_awarded": 1484573828.0, "percent_earned": 0.32506568107939615,
+        "id": "packager-sponsor", "times_awarded": 146, "name": "Packager Sponsor"},
+        {"description": "You posted 5 or more things to the Fedora Planet!", "tags":
+        "blog,community,", "issued": 1377697248.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-02.png",
+        "first_awarded": 1375902038.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468491.0, "percent_earned": 1.3470187469385937,
+        "id": "bloggin-it!-planet-ii", "times_awarded": 605, "name": "Bloggin'' it!
+        (Planet II)"}, {"description": "You posted 10 or more things to the Fedora
+        Planet!", "tags": "blog,community,", "issued": 1377697247.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-03.png",
+        "first_awarded": 1375902037.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468509.0, "percent_earned": 1.1399563610455536,
+        "id": "bloggin-it!-planet-iii", "times_awarded": 512, "name": "Bloggin'' it!
+        (Planet III)"}, {"description": "You posted something to the Fedora Planet.",
+        "tags": "blog,community,", "issued": 1377697248.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-01.png",
+        "first_awarded": 1375902037.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468543.0, "percent_earned": 1.6231019281293138,
+        "id": "bloggin-it!-planet-i", "times_awarded": 729, "name": "Bloggin'' it!
+        (Planet I)"}, {"description": "You submitted a bodhi update with type \"security\"",
+        "tags": "development,security,", "issued": 1380316519.0, "image": "https://badges.fedoraproject.org/pngs/white-hat.png",
+        "first_awarded": 1379357860.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "dtimms", "last_awarded": 1491484910.0, "percent_earned": 0.939573406955515,
+        "id": "white-hat", "times_awarded": 422, "name": "White Hat"}, {"description":
+        "Voted in a supplemental wallpapers election for a Fedora release", "tags":
+        "election,community,nuancier,", "issued": 1380637092.0, "image": "https://badges.fedoraproject.org/pngs/nauncier.png",
+        "first_awarded": 1380617342.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "duffy", "last_awarded": 1381233461.0, "percent_earned": 0.17589170414570066,
+        "id": "nuancier", "times_awarded": 79, "name": "Nuancier"}, {"description":
+        "You''ve had a FAS account for over two years", "tags": "lifecycle,community,",
+        "issued": 1389056166.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-embryo.png",
+        "first_awarded": 1389043685.0, "first_awarded_person": "joev", "last_awarded_person":
+        "andrespol", "last_awarded": 1491360716.0, "percent_earned": 20.256490181235247,
+        "id": "embryo", "times_awarded": 9098, "name": "Embryo"}, {"description":
+        "*(less straying/discipline, bull becomes gentle/obedient)*\nTested and added
+        karma to 40 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1381160713.0, "image": "https://badges.fedoraproject.org/pngs/tester-05.png",
+        "first_awarded": 1375896222.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "piotrdrag", "last_awarded": 1491478479.0, "percent_earned": 0.549939885113773,
+        "id": "taming-the-bull-tester-v", "times_awarded": 247, "name": "Taming the
+        Bull (Tester V)"}, {"description": "You contributed to a Common Bugs page.
+        Bugs? What bugs?", "tags": "content,", "issued": 1381324210.0, "image": "https://badges.fedoraproject.org/pngs/wiki-common-bugs.png",
+        "first_awarded": 1381324210.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ferdnyc", "last_awarded": 1484744055.0, "percent_earned": 0.1469474996660284,
+        "id": "common-bugs-contributor", "times_awarded": 66, "name": "Common Bugs
+        Contributor"}, {"description": "You have logged in to the Fedora Badges app",
+        "tags": "community,badges,", "issued": 1382755079.0, "image": "https://badges.fedoraproject.org/pngs/baby-badger.png",
+        "first_awarded": 1382755079.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "metonymy", "last_awarded": 1491502807.0, "percent_earned": 6.249721690341541,
+        "id": "baby-badger", "times_awarded": 2807, "name": "Baby Badger"}, {"description":
+        "Pushed 500 bodhi updates to the testing repositories.", "tags": "development",
+        "issued": 1384804767.0, "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-04.png",
+        "first_awarded": 1376840497.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "martinkg", "last_awarded": 1489655875.0, "percent_earned": 0.09351204524201807,
+        "id": "is-this-thing-on-updates-testing-iv", "times_awarded": 42, "name":
+        "Is this thing on? (Updates-Testing IV)"}, {"description": "You retired a
+        package from the distribution. It hurt, but it had to be done.", "tags": "development,ownership,",
+        "issued": 1389043285.0, "image": "https://badges.fedoraproject.org/pngs/pkgdb-old-yeller.png",
+        "first_awarded": 1382533838.0, "first_awarded_person": "notting", "last_awarded_person":
+        "pwalter", "last_awarded": 1491445231.0, "percent_earned": 0.6167342031437859,
+        "id": "old-yeller", "times_awarded": 277, "name": "Old Yeller"}, {"description":
+        "  You gave a presentation at Flock 2013, the Fedora Contributor Conference",
+        "tags": "event,speaker,", "issued": 1386089550.0, "image": "https://badges.fedoraproject.org/pngs/flockpresenter.png",
+        "first_awarded": 1386089406.0, "first_awarded_person": "sundaram", "last_awarded_person":
+        "pjones", "last_awarded": 1407317187.0, "percent_earned": 0.1580798860043639,
+        "id": "flock-2013-speaker", "times_awarded": 71, "name": "Flock 2013 Speaker"},
+        {"description": "You posted 20 or more things to the Fedora Planet!", "tags":
+        "blog,community,", "issued": 1386909731.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-04.png",
+        "first_awarded": 1375904463.0, "first_awarded_person": "paulmellors", "last_awarded_person":
+        "stefw", "last_awarded": 1491468562.0, "percent_earned": 0.7547757937391459,
+        "id": "bloggin-it!-planet-iv", "times_awarded": 339, "name": "Bloggin'' it!
+        (Planet IV)"}, {"description": "Edited at least 50 wiki pages.", "tags": "content,wiki,",
+        "issued": 1389045512.0, "image": "https://badges.fedoraproject.org/pngs/senior-editor.png",
+        "first_awarded": 1375891555.0, "first_awarded_person": "ppisar", "last_awarded_person":
+        "augenauf", "last_awarded": 1491316933.0, "percent_earned": 1.013047156788529,
+        "id": "senior-editor", "times_awarded": 455, "name": "Senior Editor"}, {"description":
+        "You''ve had a FAS account for over three years", "tags": "lifecycle,community,",
+        "issued": 1389064377.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-tadpole.png",
+        "first_awarded": 1389061835.0, "first_awarded_person": "jwboyer", "last_awarded_person":
+        "avichi", "last_awarded": 1491361425.0, "percent_earned": 14.18933962684241,
+        "id": "tadpole", "times_awarded": 6373, "name": "Tadpole"}, {"description":
+        "You''ve had a FAS account for over one year", "tags": "lifecycle,community,",
+        "issued": 1389070973.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-egg.png",
+        "first_awarded": 1389068846.0, "first_awarded_person": "darthvader", "last_awarded_person":
+        "amogh23k", "last_awarded": 1491362307.0, "percent_earned": 28.41430288996749,
+        "id": "egg", "times_awarded": 12762, "name": "Egg"}, {"description": "You''ve
+        had a FAS account for over 5 years", "tags": "lifecycle,community,", "issued":
+        1389077372.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-tadpole-with-legs.png",
+        "first_awarded": 1389076692.0, "first_awarded_person": "ftpfree", "last_awarded_person":
+        "akopacsi", "last_awarded": 1490757692.0, "percent_earned": 7.30952486975108,
+        "id": "tadpole-with-legs", "times_awarded": 3283, "name": "Tadpole with Legs"},
+        {"description": "You''re a developer contributing to the Fedora Badges stack",
+        "tags": "development,badges,", "issued": 1389131503.0, "image": "https://badges.fedoraproject.org/pngs/badge-off.png",
+        "first_awarded": 1389131479.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "lsedlar", "last_awarded": 1476326553.0, "percent_earned": 0.05120897715634323,
+        "id": "badge-off!", "times_awarded": 23, "name": "Badge Off!"}, {"description":
+        "*(great joy)*\nTested and added karma to 80 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1389368853.0, "image": "https://badges.fedoraproject.org/pngs/tester-06.png",
+        "first_awarded": 1375896221.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "sassam", "last_awarded": 1491148875.0, "percent_earned": 0.3072538629380594,
+        "id": "riding-the-bull-home-tester-vi", "times_awarded": 138, "name": "Riding
+        the Bull Home (Tester VI)"}, {"description": "You were sponsored into the
+        Fedora Infrastructure Apprentice group", "tags": "development,infrastructure,",
+        "issued": 1389889699.0, "image": "https://badges.fedoraproject.org/pngs/no-ronin.png",
+        "first_awarded": 1389889653.0, "first_awarded_person": "jam3s", "last_awarded_person":
+        "metonymy", "last_awarded": 1491476646.0, "percent_earned": 0.4208042035890813,
+        "id": "no-longer-a-ronin", "times_awarded": 189, "name": "No Longer a Ronin"},
+        {"description": "Added 250 new tags to packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1390419184.0, "image": "https://badges.fedoraproject.org/pngs/master-package-tagger.png",
+        "first_awarded": 1390419184.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "bt0dotninja", "last_awarded": 1489643141.0, "percent_earned": 0.13358863606002583,
+        "id": "master-package-tagger-package-tagger-iv", "times_awarded": 60, "name":
+        "Master Package Tagger (Package Tagger IV)"}, {"description": "Added a new
+        tag for a package in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419187.0, "image": "https://badges.fedoraproject.org/pngs/junior-package-tagger.png",
+        "first_awarded": 1390412894.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "bbroad", "last_awarded": 1491002111.0, "percent_earned": 0.8750055661931692,
+        "id": "junior-package-tagger-package-tagger-i", "times_awarded": 393, "name":
+        "Junior Package Tagger (Package Tagger I)"}, {"description": "Added 50 new
+        tags to packages in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419190.0, "image": "https://badges.fedoraproject.org/pngs/senior-package-tagger.png",
+        "first_awarded": 1390412897.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "ignacio", "last_awarded": 1485888954.0, "percent_earned": 0.28944204479672264,
+        "id": "senior-package-tagger-package-tagger-iii", "times_awarded": 130, "name":
+        "Senior Package Tagger (Package Tagger III)"}, {"description": "Added 10 new
+        tags for packages in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419193.0, "image": "https://badges.fedoraproject.org/pngs/package-tagger.png",
+        "first_awarded": 1390412900.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "imabug", "last_awarded": 1490199130.0, "percent_earned": 0.5120897715634324,
+        "id": "package-tagger-package-tagger-ii", "times_awarded": 230, "name": "Package
+        Tagger (Package Tagger II)"}, {"description": "You gave a presentation at
+        DevConf.cz, 2014", "tags": "devconf,speaker,event,", "issued": 1390422108.0,
+        "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter.png", "first_awarded":
+        1390422108.0, "first_awarded_person": "ralph", "last_awarded_person": "siddharths",
+        "last_awarded": 1420480194.0, "percent_earned": 0.10241795431268647, "id":
+        "devconf-2014-speaker", "times_awarded": 46, "name": "DevConf 2014 Speaker"},
+        {"description": "Pushed 1000 commits to a Fedora package repository.", "tags":
+        "development,", "issued": 1390944539.0, "image": "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-05.png",
+        "first_awarded": 1375892508.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "terjeros", "last_awarded": 1491331717.0, "percent_earned": 0.26717727212005166,
+        "id": "patches-scm-v", "times_awarded": 120, "name": "Patches (SCM V)"}, {"description":
+        "Created a buildroot override in the koji build system", "tags": "development,",
+        "issued": 1391016371.0, "image": "https://badges.fedoraproject.org/pngs/override-you-say.png",
+        "first_awarded": 1390489685.0, "first_awarded_person": "notting", "last_awarded_person":
+        "decathorpe", "last_awarded": 1491331634.0, "percent_earned": 0.7480963619361446,
+        "id": "override,-you-say", "times_awarded": 336, "name": "Override, you say?"},
+        {"description": "You attended the 2014 iteration of devconf.cz, a yearly Linux
+        and JBoss conference in the Czech Republic", "tags": "event,devconf,", "issued":
+        1391763090.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-attendee.png",
+        "first_awarded": 1391760446.0, "first_awarded_person": "churchyard", "last_awarded_person":
+        "siddharths", "last_awarded": 1419207296.0, "percent_earned": 0.13804159059536003,
+        "id": "devconf-attendee", "times_awarded": 62, "name": "DevConf 2014 Attendee"},
+        {"description": "You are a member of the videos team", "tags": "video,community,",
+        "issued": 1392217171.0, "image": "https://badges.fedoraproject.org/pngs/videographer.png",
+        "first_awarded": 1392217167.0, "first_awarded_person": "williamjmorenor",
+        "last_awarded_person": "swilson", "last_awarded": 1458943142.0, "percent_earned":
+        0.03562363628267355, "id": "videographer", "times_awarded": 16, "name": "Videographer"},
+        {"description": "Succesfully completed 1000 koji builds.", "tags": "development,koji,",
+        "issued": 1392827319.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-05.png",
+        "first_awarded": 1377712281.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "zbyszek", "last_awarded": 1490388487.0, "percent_earned": 0.15585340873669679,
+        "id": "all-your-$arch-are-belong-to-us-koji-success-v", "times_awarded": 70,
+        "name": "All your $arch are belong to us (Koji Success V)"}, {"description":
+        "Successfully completed a copr build", "tags": "development,copr,", "issued":
+        1392676733.0, "image": "https://badges.fedoraproject.org/pngs/copr-build.png",
+        "first_awarded": 1389199510.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "shosca", "last_awarded": 1491441908.0, "percent_earned": 3.958676581912099,
+        "id": "building-the-outer-ring-copr-build-i", "times_awarded": 1778, "name":
+        "Building the Outer Ring (Copr Build I)"}, {"description": "Someone gave one
+        of your updates good karma", "tags": "development,karma,", "issued": 1392712116.0,
+        "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around.png",
+        "first_awarded": 1392392577.0, "first_awarded_person": "spot", "last_awarded_person":
+        "cydrobolt", "last_awarded": 1491503467.0, "percent_earned": 2.046132608986062,
+        "id": "what-goes-around-comes-around-karma-i", "times_awarded": 919, "name":
+        "What goes around comes around (Karma I)"}, {"description": "Tested an update
+        for an older stable release of Fedora", "tags": "quality,", "issued": 1392752446.0,
+        "image": "https://badges.fedoraproject.org/pngs/it-still-works.png", "first_awarded":
+        1386126231.0, "first_awarded_person": "kevin", "last_awarded_person": "caiqian",
+        "last_awarded": 1481903131.0, "percent_earned": 1.6765373825533243, "id":
+        "it-still-works!", "times_awarded": 753, "name": "It still works!"}, {"description":
+        "You submitted an update to bodhi with one or more bugzilla bug numbers attached",
+        "tags": "development,", "issued": 1394463577.0, "image": "https://badges.fedoraproject.org/pngs/take-this.png",
+        "first_awarded": 1393128956.0, "first_awarded_person": "mooninite", "last_awarded_person":
+        "cydrobolt", "last_awarded": 1491442200.0, "percent_earned": 2.1641359041724186,
+        "id": "take-this-and-call-me-in-the-morning", "times_awarded": 972, "name":
+        "Take this and call me in the morning"}, {"description": "You posted 40 or
+        more things to the Fedora Planet!", "tags": "blog,community,", "issued": 1398960087.0,
+        "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-05.png",
+        "first_awarded": 1375963234.0, "first_awarded_person": "rjones", "last_awarded_person":
+        "stefw", "last_awarded": 1491468526.0, "percent_earned": 0.35623636282673554,
+        "id": "bob-loblaw-planet-v", "times_awarded": 160, "name": "Bob Loblaw (Planet
+        V)"}, {"description": "Pushed 500 bodhi updates to the stable repositories.",
+        "tags": "development,", "issued": 1396123360.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-04.png",
+        "first_awarded": 1376079014.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "dfateyev", "last_awarded": 1490465620.0, "percent_earned": 0.06902079529768002,
+        "id": "like-a-rock-updates-stable-iv", "times_awarded": 31, "name": "Like
+        a Rock (Updates-Stable IV)"}, {"description": "You set up a meeting in the
+        Fedora Calendar", "tags": "community,", "issued": 1397054728.0, "image": "https://badges.fedoraproject.org/pngs/lets-do-lunch.png",
+        "first_awarded": 1391191973.0, "first_awarded_person": "lbazan", "last_awarded_person":
+        "ignatenkobrain", "last_awarded": 1485808919.0, "percent_earned": 0.14472102239836132,
+        "id": "lets-do-lunch!", "times_awarded": 65, "name": "Let''s do lunch!"},
+        {"description": "Sip the lemonade away (You deserve it for remembering your
+        responsibility)!", "tags": "community,", "issued": 1397059347.0, "image":
+        "https://badges.fedoraproject.org/pngs/beach-bum.png", "first_awarded": 1394486757.0,
+        "first_awarded_person": "shaiton", "last_awarded_person": "mythcat", "last_awarded":
+        1484694397.0, "percent_earned": 0.4208042035890813, "id": "vacation", "times_awarded":
+        189, "name": "Vacation"}, {"description": "You visited the Fedora booth at
+        Pycon, 2014", "tags": "python,event,pycon,", "issued": 1397168025.0, "image":
+        "https://badges.fedoraproject.org/pngs/pycon_2014_attendee.png", "first_awarded":
+        1397168025.0, "first_awarded_person": "ralph", "last_awarded_person": "abompard",
+        "last_awarded": 1397573900.0, "percent_earned": 0.02226477267667097, "id":
+        "pycon-2014-attendee", "times_awarded": 10, "name": "Pycon 2014 Attendee"},
+        {"description": "You submitted feedback for an update in Bodhi after it was
+        pushed to stable", "tags": "quality,oops,", "issued": 1397684618.0, "image":
+        "https://badges.fedoraproject.org/pngs/missed-the-train.png", "first_awarded":
+        1392396477.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "thozza", "last_awarded": 1439904242.0, "percent_earned": 0.6345460212851227,
+        "id": "missed-the-train", "times_awarded": 285, "name": "Missed the Train"},
+        {"description": "You built a package owned by someone else.  It takes a village...",
+        "tags": "development,", "issued": 1400246660.0, "image": "https://badges.fedoraproject.org/pngs/helping_hand.png",
+        "first_awarded": 1394038498.0, "first_awarded_person": "sochotni", "last_awarded_person":
+        "nickboldt", "last_awarded": 1490915668.0, "percent_earned": 0.984102952308857,
+        "id": "helping-hand", "times_awarded": 442, "name": "Helping Hand"}, {"description":
+        "You''ve been granted the blessing of the Fedora Project Leader.", "tags":
+        "fpl,miscellaneous,", "issued": 1401833399.0, "image": "https://badges.fedoraproject.org/pngs/fpl-blessing.png",
+        "first_awarded": 1376269552.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "skottler", "last_awarded": 1401833697.0, "percent_earned": 0.20483590862537293,
+        "id": "the-blessing-of-the-fpl", "times_awarded": 92, "name": "The Blessing
+        of the FPL"}, {"description": "Submitted 100 or more koji builds that failed
+        to build.", "tags": "development,oops,koji,", "issued": 1403195400.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-what-goes-up-04.png", "first_awarded":
+        1377715853.0, "first_awarded_person": "vicodan", "last_awarded_person": "wtaymans",
+        "last_awarded": 1490906731.0, "percent_earned": 0.20038295409003873, "id":
+        "what-goes-up...-koji-failure-iv", "times_awarded": 90, "name": "What goes
+        up... (Koji Failure IV)"}, {"description": "You updated your password with
+        the Fedora Account System (FAS)", "tags": "account,community,", "issued":
+        1404948910.0, "image": "https://badges.fedoraproject.org/pngs/fas-paranoid-panda.png",
+        "first_awarded": 1376708721.0, "first_awarded_person": "rstrode", "last_awarded_person":
+        "payasr", "last_awarded": 1491512765.0, "percent_earned": 61.86712383666563,
+        "id": "paranoid-panda", "times_awarded": 27787, "name": "Paranoid Panda"},
+        {"description": "      You gave a presentation at Flock 2014, the Fedora Contributor
+        Conference", "tags": "event,flock,speaker,", "issued": 1405362797.0, "image":
+        "https://badges.fedoraproject.org/pngs/flock-2014-speaker.png", "first_awarded":
+        1405362496.0, "first_awarded_person": "abompard", "last_awarded_person": "amita",
+        "last_awarded": 1408553413.0, "percent_earned": 0.16253284053969808, "id":
+        "flock-2014-speaker", "times_awarded": 73, "name": "Flock 2014 Speaker"},
+        {"description": "You have received 10 or more good karma on your bodhi updates",
+        "tags": "development,karma,", "issued": 1406215355.0, "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-10.png",
+        "first_awarded": 1392397378.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "jvymazal", "last_awarded": 1491284602.0, "percent_earned": 1.099879770227546,
+        "id": "what-goes-around-comes-around-karma-ii", "times_awarded": 494, "name":
+        "What goes around comes around (Karma II)"}, {"description": "Edited at least
+        100 wiki pages.", "tags": "content,wiki,", "issued": 1406329746.0, "image":
+        "https://badges.fedoraproject.org/pngs/master-editor.png", "first_awarded":
+        1375891554.0, "first_awarded_person": "ppisar", "last_awarded_person": "athoscr",
+        "last_awarded": 1490991464.0, "percent_earned": 0.5900164759317807, "id":
+        "master-editor", "times_awarded": 265, "name": "Master Editor"}, {"description":
+        "You have received 50 or more good karma on your bodhi updates", "tags": "development,karma,",
+        "issued": 1406630310.0, "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-50.png",
+        "first_awarded": 1393287975.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "terjeros", "last_awarded": 1491384354.0, "percent_earned": 0.389633521841742,
+        "id": "what-goes-around-comes-around-karma-iii", "times_awarded": 175, "name":
+        "What goes around comes around (Karma III)"}, {"description": "  You attended
+        Flock 2014, the Fedora Contributor Conference", "tags": "event,flock,", "issued":
+        1407308400.0, "image": "https://badges.fedoraproject.org/pngs/flock-2014-attendee.png",
+        "first_awarded": 1405296000.0, "first_awarded_person": "marinaz", "last_awarded_person":
+        "rathann", "last_awarded": 1426267409.0, "percent_earned": 0.27163022665538583,
+        "id": "flock-2014-attendee", "times_awarded": 122, "name": "Flock 2014 Attendee"},
+        {"description": "You completed a run of the kernel regression test suite",
+        "tags": "kernel,quality,", "issued": 1407409472.0, "image": "https://badges.fedoraproject.org/pngs/kernel-tester.png",
+        "first_awarded": 1407405561.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "pperron", "last_awarded": 1490999828.0, "percent_earned": 0.49873090795742975,
+        "id": "science-kernel-tester-i", "times_awarded": 224, "name": "Science (Kernel
+        Tester I)"}, {"description": "Ahoy! You attended the boat party at the Flock
+        2014 conference in Prague!", "tags": "event,flock,", "issued": 1407430507.0,
+        "image": "https://badges.fedoraproject.org/pngs/party-pirate.png", "first_awarded":
+        1407430497.0, "first_awarded_person": "lmacken", "last_awarded_person": "amsharma",
+        "last_awarded": 1408665317.0, "percent_earned": 0.19815647682237164, "id":
+        "party-pirate", "times_awarded": 89, "name": "Party Pirate"}, {"description":
+        "You submitted 200 Bodhi updates for an EPEL branch.", "tags": "development,epel,",
+        "issued": 1407498349.0, "image": "https://badges.fedoraproject.org/pngs/corporate-overlord.png",
+        "first_awarded": 1407423571.0, "first_awarded_person": "remi", "last_awarded_person":
+        "pghmcfc", "last_awarded": 1485788654.0, "percent_earned": 0.03339715901500646,
+        "id": "corporate-overlord", "times_awarded": 15, "name": "Corporate Overlord"},
+        {"description": "You submitted 50 Bodhi updates for an EPEL branch.", "tags":
+        "development,epel,", "issued": 1407498359.0, "image": "https://badges.fedoraproject.org/pngs/corporate-shill.png",
+        "first_awarded": 1407423581.0, "first_awarded_person": "remi", "last_awarded_person":
+        "kdudka", "last_awarded": 1491214689.0, "percent_earned": 0.18925056775170326,
+        "id": "corporate-shill", "times_awarded": 85, "name": "Corporate Shill"},
+        {"description": "You submitted an Bodhi update for an EPEL branch.", "tags":
+        "development,epel,", "issued": 1407498367.0, "image": "https://badges.fedoraproject.org/pngs/corporate-drone.png",
+        "first_awarded": 1407423591.0, "first_awarded_person": "remi", "last_awarded_person":
+        "dsommers", "last_awarded": 1490298016.0, "percent_earned": 1.3024892015852518,
+        "id": "corporate-drone", "times_awarded": 585, "name": "Corporate Drone"},
+        {"description": "You got 100 Fedora Badges!  Wow!", "tags": "community,badges,",
+        "issued": 1407498391.0, "image": "https://badges.fedoraproject.org/pngs/badger-03.png",
+        "first_awarded": 1394558737.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "vondruch", "last_awarded": 1491296529.0, "percent_earned": 0.14026806786302712,
+        "id": "master-badger-badger-iii", "times_awarded": 63, "name": "Master Badger
+        (Badger III)"}, {"description": "*(the bull forgotten, discipline''s whip
+        is idle; stillness)*\nTested and added karma to 125 updates-testing updates
+        in Bodhi.\n", "tags": "ten bulls,quality,", "issued": 1408115651.0, "image":
+        "https://badges.fedoraproject.org/pngs/tester-07.png", "first_awarded": 1375896222.0,
+        "first_awarded_person": "kevin", "last_awarded_person": "fale", "last_awarded":
+        1490321832.0, "percent_earned": 0.2248742040343768, "id": "the-bull-transcended-tester-vii",
+        "times_awarded": 101, "name": "The Bull Transcended (Tester VII)"}, {"description":
+        "You danced with Toshio (abadger1999) at a Fedora event", "tags": "event,freshness,",
+        "issued": 1408122340.0, "image": "https://badges.fedoraproject.org/pngs/dancing_with_toshio.png",
+        "first_awarded": 1408121695.0, "first_awarded_person": "abompard", "last_awarded_person":
+        "rbergero", "last_awarded": 1455959914.0, "percent_earned": 0.0712472725653471,
+        "id": "dancing-with-toshio", "times_awarded": 32, "name": "Dancing with Toshio"},
+        {"description": "Voted in a supplemental wallpapers election for the Fedora
+        21 release", "tags": "election,community,nuancier,", "issued": 1408150877.0,
+        "image": "https://badges.fedoraproject.org/pngs/nuancier-f21-voted.png", "first_awarded":
+        1408147284.0, "first_awarded_person": "gnokii", "last_awarded_person": "pravins",
+        "last_awarded": 1409902273.0, "percent_earned": 0.2449124994433807, "id":
+        "nuancier-21", "times_awarded": 110, "name": "Nuancier 21"}, {"description":
+        "We''re sorry, but you had one of your badges revoked.  Something must have
+        gone wrong.", "tags": "sad trombone,badges,community,", "issued": 1408725704.0,
+        "image": "https://badges.fedoraproject.org/pngs/consolation-prize.png", "first_awarded":
+        1392255607.0, "first_awarded_person": "dmalcolm", "last_awarded_person": "keitellf",
+        "last_awarded": 1465418173.0, "percent_earned": 0.8972703388698402, "id":
+        "consolation-prize", "times_awarded": 403, "name": "Consolation Prize"}, {"description":
+        "You updated your GPG key with the Fedora Account System (FAS)", "tags": "account,community,",
+        "issued": 1408455462.0, "image": "https://badges.fedoraproject.org/pngs/fas-crypto-panda.png",
+        "first_awarded": 1375891835.0, "first_awarded_person": "itorres", "last_awarded_person":
+        "usuha", "last_awarded": 1491448085.0, "percent_earned": 6.430066349022577,
+        "id": "crypto-panda", "times_awarded": 2888, "name": "Crypto Panda"}, {"description":
+        "Successfully completed 20 copr builds", "tags": "development,copr,", "issued":
+        1408587109.0, "image": "https://badges.fedoraproject.org/pngs/copr-build-20.png",
+        "first_awarded": 1389199520.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "shosca", "last_awarded": 1491505320.0, "percent_earned": 2.3711982900654585,
+        "id": "building-the-outer-ring-copr-build-ii", "times_awarded": 1065, "name":
+        "Building the Outer Ring (Copr Build II)"}, {"description": "Pushed 1000 bodhi
+        updates to the testing repositories.", "tags": "development,", "issued": 1409321568.0,
+        "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-05.png",
+        "first_awarded": 1398006872.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "pghmcfc", "last_awarded": 1490811747.0, "percent_earned": 0.03562363628267355,
+        "id": "mic-check!-updates-testing-v", "times_awarded": 16, "name": "Mic Check!
+        (Updates-Testing V)"}, {"description": "You took ownership of a previously
+        orphaned package.", "tags": "development,ownership,", "issued": 1418228616.0,
+        "image": "https://badges.fedoraproject.org/pngs/pkgdb-the-cat-came-back.png",
+        "first_awarded": 1381330499.0, "first_awarded_person": "tmraz", "last_awarded_person":
+        "sailer", "last_awarded": 1490344262.0, "percent_earned": 0.7525493164714788,
+        "id": "the-cat-came-back...", "times_awarded": 338, "name": "The cat came
+        back..."}, {"description": "You designed a Fedora Badge! Welcome to Florence,
+        apprentice badge artist.", "tags": "content,design,badges", "issued": 1420755671.0,
+        "image": "https://badges.fedoraproject.org/pngs/artist-01-apprentice.png",
+        "first_awarded": 1420755606.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "michalrud", "last_awarded": 1470317213.0, "percent_earned": 0.04675602262100904,
+        "id": "apprentice-badge-artist-i", "times_awarded": 21, "name": "Apprentice
+        (Badge Artist I)"}, {"description": "You have received 100 or more good karma
+        on your bodhi updates", "tags": "development,karma,", "issued": 1421316923.0,
+        "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-100.png",
+        "first_awarded": 1396803342.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "kkeithle", "last_awarded": 1491364811.0, "percent_earned": 0.20706238589304005,
+        "id": "what-goes-around-comes-around-karma-iv", "times_awarded": 93, "name":
+        "What goes around comes around (Karma IV)"}, {"description": "You participated
+        in the Design Team FAD, 2015", "tags": "event,fad,design,", "issued": 1421420485.0,
+        "image": "https://badges.fedoraproject.org/pngs/fad_design.png", "first_awarded":
+        1421419806.0, "first_awarded_person": "chrisroberts", "last_awarded_person":
+        "rbergero", "last_awarded": 1421432734.0, "percent_earned": 0.04007659081800775,
+        "id": "design-ninja", "times_awarded": 18, "name": "Design Ninja"}, {"description":
+        "You gave a presentation at Flock 2015, the Fedora Contributor Conference",
+        "tags": "event,flock,speaker,", "issued": 1439502241.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-speaker.png",
+        "first_awarded": 1439502092.0, "first_awarded_person": "spot", "last_awarded_person":
+        "jzeleny", "last_awarded": 1439503832.0, "percent_earned": 0.13804159059536003,
+        "id": "flock-2015-speaker", "times_awarded": 62, "name": "Flock 2015 Speaker"},
+        {"description": "Pushed 1000 bodhi updates to the stable repositories.", "tags":
+        "development,", "issued": 1423235515.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-05.png",
+        "first_awarded": 1413266082.0, "first_awarded_person": "remi", "last_awarded_person":
+        "orion", "last_awarded": 1490756246.0, "percent_earned": 0.024491249944338066,
+        "id": "tectonic!-updates-stable-v", "times_awarded": 11, "name": "Tectonic!
+        (Updates-Stable V)"}, {"description": "You got 25 or more Fedora Badges",
+        "tags": "community,badges,", "issued": 1423235523.0, "image": "https://badges.fedoraproject.org/pngs/badger-01.5.png",
+        "first_awarded": 1421694162.0, "first_awarded_person": "rhughes", "last_awarded_person":
+        "pcreech17", "last_awarded": 1491432940.0, "percent_earned": 2.3756512446007925,
+        "id": "associate-badger-badger-1.5", "times_awarded": 1067, "name": "Associate
+        Badger (Badger 1.5)"}, {"description": "You attended Flock 2015, the Fedora
+        Contributor Conference", "tags": "conference,rochester,events,flock,flock
+        2015,event,", "issued": 1439318156.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-attendee.png",
+        "first_awarded": 1438119680.0, "first_awarded_person": "decause", "last_awarded_person":
+        "kellin", "last_awarded": 1461547496.0, "percent_earned": 0.18479761321636906,
+        "id": "flock-2015-attendee", "times_awarded": 83, "name": "Flock 2015 Attendee"},
+        {"description": "You''ve been given a cookie for helping someone out.  Thanks!",
+        "tags": "cookie,community,", "issued": 1426702546.0, "image": "https://badges.fedoraproject.org/pngs/macaroncookie.png",
+        "first_awarded": 1426702242.0, "first_awarded_person": "riecatnor", "last_awarded_person":
+        "gtierney", "last_awarded": 1491405719.0, "percent_earned": 1.852429086699025,
+        "id": "macaron-cookie-i", "times_awarded": 832, "name": "Macaron (Cookie I)"},
+        {"description": "Ten people think you''re great, because they''ve given you
+        cookies. Yummy!", "tags": "cookie,community,", "issued": 1426796321.0, "image":
+        "https://badges.fedoraproject.org/pngs/chocolatechipcookie.png", "first_awarded":
+        1426796321.0, "first_awarded_person": "ralph", "last_awarded_person": "mizdebsk",
+        "last_awarded": 1491500198.0, "percent_earned": 0.3606893173620697, "id":
+        "chocolate-chip-cookie-ii", "times_awarded": 162, "name": "Chocolate Chip
+        (Cookie II)"}, {"description": "Voted in the supplemental wallpapers election
+        for the Fedora 22 release", "tags": "election,community,nuancier,", "issued":
+        1426883250.0, "image": "https://badges.fedoraproject.org/pngs/nuancier-f22-voted.png",
+        "first_awarded": 1426835617.0, "first_awarded_person": "gnokii", "last_awarded_person":
+        "sarupbanskota", "last_awarded": 1427224635.0, "percent_earned": 0.18257113594870197,
+        "id": "nuancier-f22", "times_awarded": 82, "name": "Nuancier (F22)"}, {"description":
+        "You pinned your country in FAS!", "tags": "account,community,", "issued":
+        1427991763.0, "image": "https://badges.fedoraproject.org/pngs/fas-country.png",
+        "first_awarded": 1427832337.0, "first_awarded_person": "aeperezt", "last_awarded_person":
+        "schwankek", "last_awarded": 1491486520.0, "percent_earned": 17.635926437191078,
+        "id": "origin", "times_awarded": 7921, "name": "Origin"}, {"description":
+        "You added a new package to the distribution.", "tags": "development,", "issued":
+        1377267893.0, "image": "https://badges.fedoraproject.org/pngs/delivery.png",
+        "first_awarded": 1376569056.0, "first_awarded_person": "besser82", "last_awarded_person":
+        "nmilosev", "last_awarded": 1491213323.0, "percent_earned": 0.919535111546511,
+        "id": "delivery", "times_awarded": 413, "name": "Delivery"}, {"description":
+        "You mapped an upstream project to a Fedora package on release-monitoring.org",
+        "tags": "quality,anitya,", "issued": 1430855052.0, "image": "https://badges.fedoraproject.org/pngs/telegraph.png",
+        "first_awarded": 1430855052.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dustymabe", "last_awarded": 1475240820.0, "percent_earned": 0.5655252259874427,
+        "id": "telegraphist-upstream-release-monitoring-i", "times_awarded": 254,
+        "name": "Telegraphist (Upstream Release Monitoring I)"}, {"description": "Mmmmm....
+        cookies. You''ve helped out 25 Fedorans! Go you!", "tags": "cookies,cookie,community,",
+        "issued": 1430923797.0, "image": "https://badges.fedoraproject.org/pngs/blackandwhitecookie.png",
+        "first_awarded": 1430923797.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dmsimard", "last_awarded": 1491321510.0, "percent_earned": 0.14472102239836132,
+        "id": "black-and-white-cookie-iii", "times_awarded": 65, "name": "Black and
+        White (Cookie III)"}, {"description": "Submitted 250 or more koji builds that
+        failed to build.", "tags": "development,oops,koji,", "issued": 1431713399.0,
+        "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-05.png",
+        "first_awarded": 1378300711.0, "first_awarded_person": "ppisar", "last_awarded_person":
+        "smani", "last_awarded": 1485976444.0, "percent_earned": 0.08460613617134968,
+        "id": "hindenburg-koji-failure-v", "times_awarded": 38, "name": "Hindenburg
+        (Koji Failure V)"}, {"description": "Twenty Fedora Badges owe their existence
+        to you.  Prolific!", "tags": "community,badges,", "issued": 1433267989.0,
+        "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_20.png",
+        "first_awarded": 1433267989.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jflory7", "last_awarded": 1475509860.0, "percent_earned": 0.006679431803001291,
+        "id": "badge-muse-badges-released-iv", "times_awarded": 3, "name": "Badge
+        Muse (Badges Released IV)"}, {"description": "Five of your new Fedora Badge
+        ideas were approved and released.  Superb!", "tags": "community,badges,",
+        "issued": 1433268002.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_5.png",
+        "first_awarded": 1423674135.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "pravins", "last_awarded": 1472553692.0, "percent_earned": 0.03562363628267355,
+        "id": "badge-muse-badges-released-ii", "times_awarded": 16, "name": "Badge
+        Muse (Badges Released II)"}, {"description": "Your idea for a new Fedora Badge
+        was approved and released.  Awesome!", "tags": "community,badges,", "issued":
+        1433268014.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved.png",
+        "first_awarded": 1423674153.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "frantisekz", "last_awarded": 1485462295.0, "percent_earned": 0.10019147704501936,
+        "id": "badge-muse-badges-released-i", "times_awarded": 45, "name": "Badge
+        Muse (Badges Released I)"}, {"description": "Ten of your new Fedora Badge
+        ideas were approved and released.  Impressive!", "tags": "community,badges,",
+        "issued": 1433267996.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_10.png",
+        "first_awarded": 1424466072.0, "first_awarded_person": "gnokii", "last_awarded_person":
+        "jflory7", "last_awarded": 1475509873.0, "percent_earned": 0.013358863606002582,
+        "id": "badge-muse-badges-released-iii", "times_awarded": 6, "name": "Badge
+        Muse (Badges Released III)"}, {"description": "Each unique like a snowflake,
+        fifty of your Fedora Badge ideas were implemented.", "tags": "community,badges,",
+        "issued": 1433268008.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_50.png",
+        "first_awarded": 1433268008.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ralph", "last_awarded": 1433268008.0, "percent_earned": 0.002226477267667097,
+        "id": "badge-muse-badges-released-v", "times_awarded": 1, "name": "Badge Muse
+        (Badges Released V)"}, {"description": "Beep boop!  You attended the Museum
+        of Play party at the Flock 2015 conference in Rochester!", "tags": "event,flock,",
+        "issued": 1440122051.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-party.png",
+        "first_awarded": 1439510783.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "kellin", "last_awarded": 1461547520.0, "percent_earned": 0.16921227234269937,
+        "id": "nom-nom-nom", "times_awarded": 76, "name": "Nom Nom Nom"}, {"description":
+        "Successfully completed 60 copr builds", "tags": "development,copr,", "issued":
+        1441220012.0, "image": "https://badges.fedoraproject.org/pngs/copr-build-60.png",
+        "first_awarded": 1389199513.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "rluzynski", "last_awarded": 1491442095.0, "percent_earned": 1.5763459055083047,
+        "id": "building-the-outer-ring-copr-build-iii", "times_awarded": 708, "name":
+        "Building the Outer Ring (Copr Build III)"}, {"description": "You maintain
+        the Fedora websites.  The Web is yours!", "tags": "web,community,", "issued":
+        1442348476.0, "image": "https://badges.fedoraproject.org/pngs/rock-the-web.png",
+        "first_awarded": 1393598200.0, "first_awarded_person": "sundaram", "last_awarded_person":
+        "dustymabe", "last_awarded": 1491326903.0, "percent_earned": 0.12468272698935744,
+        "id": "rock-the-web!", "times_awarded": 56, "name": "Rock the Web!"}, {"description":
+        "You can speak Python and and you''ve ported at least one package to Python
+        3", "tags": "python,miscellaneous,", "issued": 1447943105.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth.png",
+        "first_awarded": 1438695566.0, "first_awarded_person": "rkuska", "last_awarded_person":
+        "crobinso", "last_awarded": 1491217608.0, "percent_earned": 0.33842454468539873,
+        "id": "parselmouth", "times_awarded": 152, "name": "Parselmouth I"}, {"description":
+        "Edited at least 250 wiki pages.", "tags": "content,wiki,", "issued": 1444762324.0,
+        "image": "https://badges.fedoraproject.org/pngs/wikiedit-250.png", "first_awarded":
+        1409692963.0, "first_awarded_person": "hhorak", "last_awarded_person": "jonatoni",
+        "last_awarded": 1490211890.0, "percent_earned": 0.22710068130204392, "id":
+        "chief-editor", "times_awarded": 102, "name": "Chief Editor"}, {"description":
+        "You''ve had a FAS account for over seven years", "tags": "lifecycle,community,",
+        "issued": 1446001652.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-froglet.png",
+        "first_awarded": 1389060935.0, "first_awarded_person": "jwboyer", "last_awarded_person":
+        "apahim", "last_awarded": 1491360865.0, "percent_earned": 4.628846239479895,
+        "id": "froglet", "times_awarded": 2079, "name": "Froglet"}, {"description":
+        "50 Fedorans have recognized your achievements by giving you cookies.", "tags":
+        "cookie,community,", "issued": 1447693727.0, "image": "https://badges.fedoraproject.org/pngs/pizzellecookie.png",
+        "first_awarded": 1447693727.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ryanlerch", "last_awarded": 1490719235.0, "percent_earned": 0.03117068174733936,
+        "id": "pizzelle-cookie-iv", "times_awarded": 14, "name": "Pizzelle (Cookie
+        IV)"}, {"description": "You held second place on the Fedora Badges leaderboard",
+        "tags": "meta,community,", "issued": 1452873723.0, "image": "https://badges.fedoraproject.org/pngs/2_badge_340.png",
+        "first_awarded": 1452873723.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "churchyard", "last_awarded": 1475564266.0, "percent_earned": 0.011132386338335486,
+        "id": "silver", "times_awarded": 5, "name": "Silver"}, {"description": "You
+        ranked among the top 10 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875167.0, "image": "https://badges.fedoraproject.org/pngs/10_badge_340.png",
+        "first_awarded": 1452875035.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "fale", "last_awarded": 1489622707.0, "percent_earned": 0.03117068174733936,
+        "id": "top-10", "times_awarded": 14, "name": "Top 10"}, {"description": "You
+        ranked among the top 100 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875191.0, "image": "https://badges.fedoraproject.org/pngs/100_badge_340.png",
+        "first_awarded": 1452875133.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "tflink", "last_awarded": 1480461776.0, "percent_earned": 0.25159193124638196,
+        "id": "top-100", "times_awarded": 113, "name": "Top 100"}, {"description":
+        "You ranked among the top 25 contributors on the Fedora Badges leaderboard",
+        "tags": "meta,community,", "issued": 1452875198.0, "image": "https://badges.fedoraproject.org/pngs/25_badge_340.png",
+        "first_awarded": 1452875140.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "mrunge", "last_awarded": 1490730635.0, "percent_earned": 0.06679431803001291,
+        "id": "top-25", "times_awarded": 30, "name": "Top 25"}, {"description": "You
+        ranked among the top 500 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875206.0, "image": "https://badges.fedoraproject.org/pngs/500_badge_340.png",
+        "first_awarded": 1452875147.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "zsun", "last_awarded": 1490677027.0, "percent_earned": 1.2000712472725654,
+        "id": "top-500", "times_awarded": 539, "name": "Top 500"}, {"description":
+        "You visited the Fedora table at BrickHack 2016!", "tags": "brickhack,event,hackathon,events,",
+        "issued": 1457234130.0, "image": "http://infrastructure.fedoraproject.org/infra/badges/pngs/events-brickhack-2016.png",
+        "first_awarded": 1455845197.0, "first_awarded_person": "jflory7", "last_awarded_person":
+        "brandondgr8", "last_awarded": 1461299769.0, "percent_earned": 0.05566193169167742,
+        "id": "brickhack-2016-attendee", "times_awarded": 25, "name": "BrickHack 2016
+        Attendee"}, {"description": "You gave a presentation at DevConf.cz, 2016",
+        "tags": "devconf,speaker,event,", "issued": 1455008224.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter-2016.png",
+        "first_awarded": 1454667336.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "thozza", "last_awarded": 1459500320.0, "percent_earned": 0.2137418176960413,
+        "id": "devconf-2016-speaker", "times_awarded": 96, "name": "DevConf 2016 Speaker"},
+        {"description": "You attended the 2016 iteration of DevConf.cz, a yearly Linux
+        and JBoss conference in the Czech Republic", "tags": "event, devconf,", "issued":
+        1455120228.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-attendee-2016.png",
+        "first_awarded": 1454600601.0, "first_awarded_person": "eischmann", "last_awarded_person":
+        "dkaspar", "last_awarded": 1461940547.0, "percent_earned": 0.18925056775170326,
+        "id": "devconf-2016-attendee", "times_awarded": 85, "name": "DevConf 2016
+        Attendee"}, {"description": "You got a \"Change\" accepted into the Fedora
+        24 Change list", "tags": "development,FESCo,process,", "issued": 1458652673.0,
+        "image": "https://badges.fedoraproject.org/pngs/changes-f24.png", "first_awarded":
+        1458651465.0, "first_awarded_person": "eischmann", "last_awarded_person":
+        "pnemade", "last_awarded": 1459240914.0, "percent_earned": 0.10019147704501936,
+        "id": "fedora-24-change-accepted", "times_awarded": 45, "name": "Fedora 24
+        Change Accepted"}, {"description": "You held third place on the Fedora Badges
+        leaderboard", "tags": "meta,community,", "issued": 1462904993.0, "image":
+        "https://badges.fedoraproject.org/pngs/3_badge_340.png", "first_awarded":
+        1452875229.0, "first_awarded_person": "pnemade", "last_awarded_person": "kevin",
+        "last_awarded": 1489692901.0, "percent_earned": 0.011132386338335486, "id":
+        "bronze", "times_awarded": 5, "name": "Bronze"}, {"description": "You burned
+        a yubikey and linked it with your Fedora Account", "tags": "account,community,",
+        "issued": 1462904755.0, "image": "https://badges.fedoraproject.org/pngs/yubi-badge.png",
+        "first_awarded": 1422906928.0, "first_awarded_person": "plautrba", "last_awarded_person":
+        "findarato", "last_awarded": 1491408410.0, "percent_earned": 0.4052188627154117,
+        "id": "horcrux", "times_awarded": 182, "name": "Horcrux"}, {"description":
+        "You hacked with the Fedora crew at PyCon US 2016", "tags": "event, python,pycon,",
+        "issued": 1464988862.0, "image": "https://badges.fedoraproject.org/pngs/pycon_2016_attendee.png",
+        "first_awarded": 1464988862.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "maxamillion", "last_awarded": 1464991242.0, "percent_earned": 0.028944204479672265,
+        "id": "pycon-2016-attendee", "times_awarded": 13, "name": "PyCon 2016 Attendee"},
+        {"description": "You got 150 Fedora Badges!  Wow!", "tags": "badges,community,",
+        "issued": 1468058613.0, "image": "https://badges.fedoraproject.org/pngs/badger-04.png",
+        "first_awarded": 1457979017.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "pbrobinson", "last_awarded": 1490919191.0, "percent_earned": 0.020038295409003874,
+        "id": "badge-junkie-badger-iv", "times_awarded": 9, "name": "Badge Junkie
+        (Badger IV)"}, {"description": "You can speak Python and and you''ve ported
+        at least five packages to Python 3", "tags": "python,miscellaneous,", "issued":
+        1468058067.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth-II.png",
+        "first_awarded": 1467988651.0, "first_awarded_person": "cstratak", "last_awarded_person":
+        "ishcherb", "last_awarded": 1488193805.0, "percent_earned": 0.028944204479672265,
+        "id": "parselmouth-ii", "times_awarded": 13, "name": "Parselmouth II"}, {"description":
+        "You can speak Python and and you''ve ported at least ten packages to Python
+        3", "tags": "python,miscellaneous,", "issued": 1468058072.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth-III.png",
+        "first_awarded": 1467997850.0, "first_awarded_person": "cstratak", "last_awarded_person":
+        "ishcherb", "last_awarded": 1489753361.0, "percent_earned": 0.02226477267667097,
+        "id": "parselmouth-iii", "times_awarded": 10, "name": "Parselmouth III"},
+        {"description": "You attended Flock 2016, the Fedora Contributor Conference",
+        "tags": "flock,event,events,", "issued": 1470404480.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/flock-2016-attendee.png",
+        "first_awarded": 1468984748.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "adrian", "last_awarded": 1486483875.0, "percent_earned": 0.2360065903727123,
+        "id": "flock-2016-attendee", "times_awarded": 106, "name": "Flock 2016 Attendee"},
+        {"description": "You attended the party at Flock 2016 in Krakow!", "tags":
+        "flock,krakow,event,events,", "issued": 1470404592.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/krakow-brews.png",
+        "first_awarded": 1468963417.0, "first_awarded_person": "duffy", "last_awarded_person":
+        "kittykat", "last_awarded": 1486217490.0, "percent_earned": 0.18257113594870197,
+        "id": "krakow-brews!", "times_awarded": 82, "name": "Krakow Brews!"}, {"description":
+        "You gave a presentation at Flock 2016, the Fedora Contributor Conference",
+        "tags": "flock,speaker,event,", "issued": 1471354751.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/flock-2016-speaker.png",
+        "first_awarded": 1470941077.0, "first_awarded_person": "mattdm", "last_awarded_person":
+        "kittykat", "last_awarded": 1486217546.0, "percent_earned": 0.19592999955470453,
+        "id": "flock-2016-speaker", "times_awarded": 88, "name": "Flock 2016 Speaker"},
+        {"description": "You''re a member of the Modularity Working Group!", "tags":
+        "modularity,community,", "issued": 1475082397.0, "image": "https://badges.fedoraproject.org/pngs/fas-modularity-wg.png",
+        "first_awarded": 1475082098.0, "first_awarded_person": "asamalik", "last_awarded_person":
+        "jscotka", "last_awarded": 1491213584.0, "percent_earned": 0.0801531816360155,
+        "id": "modularity-wg-member", "times_awarded": 36, "name": "Modularity WG
+        Member"}, {"description": "You got a \"Change\" accepted into the Fedora 26
+        Change list", "tags": "development, FESCo, process,", "issued": 1479749896.0,
+        "image": "https://badges.fedoraproject.org/pngs/changes-f26.png", "first_awarded":
+        1473331018.0, "first_awarded_person": "cstratak", "last_awarded_person": "ncoghlan",
+        "last_awarded": 1489656918.0, "percent_earned": 0.11577681791868906, "id":
+        "fedora-26-change-accepted", "times_awarded": 52, "name": "Fedora 26 Change
+        Accepted"}, {"description": "You gave a presentation at DevConf.cz, 2017",
+        "tags": "speaker,devconf,event,", "issued": 1486037155.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter-2017.png",
+        "first_awarded": 1486033923.0, "first_awarded_person": "jflory7", "last_awarded_person":
+        "siddharths", "last_awarded": 1486077040.0, "percent_earned": 0.24713897671104776,
+        "id": "devconf-2017-speaker", "times_awarded": 111, "name": "DevConf 2017
+        Speaker"}, {"description": "You have helped so many Fedorans you''ve got over
+        100 cookies. Don''t eat them all at once!", "tags": "cookie,community,", "issued":
+        1486153659.0, "image": "https://badges.fedoraproject.org/pngs/rainbowcookie.png",
+        "first_awarded": 1468278719.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "ralph", "last_awarded": 1486153659.0, "percent_earned": 0.011132386338335486,
+        "id": "rainbow-cookie-v", "times_awarded": 5, "name": "Rainbow (Cookie V)"}],
+        "percent_earned": 41.81818181818181, "user": "ralph"}'}
+    headers:
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=74663696]
+      connection: [Keep-Alive]
+      content-length: ['74203']
+      content-type: [application/json; charset=UTF-8]
+      date: ['Thu, 06 Apr 2017 22:31:35 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://badges.fedoraproject.org/user/ralph/json
+  response:
+    body: {string: !!python/unicode '{"user_count": 44887, "rank": 5, "avatar": "https://seccdn.libravatar.org/avatar/9c9f7784935381befc302fe3c814f9136e7a33953d0318761669b8643f4df55c?s=100&d=https%3A%2F%2Fbadges.fedoraproject.org%2Fstatic%2Fimg%2Fbadger_avatar.png",
+        "percentile": "0.01", "assertions": [{"description": "You attended Flock 2013,
+        the Fedora Contributor Conference", "tags": "flock,event,", "issued": 1375373082.0,
+        "image": "https://badges.fedoraproject.org/pngs/flock-2013-attendee.png",
+        "first_awarded": 1375373082.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "rbergero", "last_awarded": 1473972160.0, "percent_earned": 0.18034465868103486,
+        "id": "flock-2013-attendee", "times_awarded": 81, "name": "Flock 2013 Attendee"},
+        {"description": "Voted on 50 tags for packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1375450118.0, "image": "https://badges.fedoraproject.org/pngs/tagger-02.png",
+        "first_awarded": 1375450118.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "imabug", "last_awarded": 1490198996.0, "percent_earned": 0.663490225764795,
+        "id": "tagger-tagger-ii", "times_awarded": 298, "name": "Tagger (Tagger II)"},
+        {"description": "Voted on 10 tags for packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1375450119.0, "image": "https://badges.fedoraproject.org/pngs/tagger-01.png",
+        "first_awarded": 1375450119.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "bbroad", "last_awarded": 1491002037.0, "percent_earned": 1.0508972703388697,
+        "id": "junior-tagger-tagger-i", "times_awarded": 472, "name": "Junior Tagger
+        (Tagger I)"}, {"description": "You signed the Fedora Project Contributor Agreement
+        (FPCA)", "tags": "account,community,", "issued": 1375572218.0, "image": "https://badges.fedoraproject.org/pngs/involvement.png",
+        "first_awarded": 1375572176.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "ylarguex", "last_awarded": 1491516838.0, "percent_earned": 53.357527719641986,
+        "id": "involvement", "times_awarded": 23965, "name": "Involvement"}, {"description":
+        "Participated in an IRC meeting.", "tags": "irc,community,", "issued": 1375734057.0,
+        "image": "https://badges.fedoraproject.org/pngs/irc-speak-up.png", "first_awarded":
+        1375734057.0, "first_awarded_person": "ralph", "last_awarded_person": "icole",
+        "last_awarded": 1491505117.0, "percent_earned": 3.9564501046444316, "id":
+        "speak-up!", "times_awarded": 1777, "name": "Speak Up!"}, {"description":
+        "Edited at least 10 wiki pages.", "tags": "content,wiki,", "issued": 1375888314.0,
+        "image": "https://badges.fedoraproject.org/pngs/junior-editor.png", "first_awarded":
+        1375888314.0, "first_awarded_person": "ralph", "last_awarded_person": "kdudka",
+        "last_awarded": 1491484751.0, "percent_earned": 3.3129981742886407, "id":
+        "junior-editor", "times_awarded": 1488, "name": "Junior Editor"}, {"description":
+        "Edited at least 20 wiki pages.", "tags": "content,wiki,", "issued": 1375888315.0,
+        "image": "https://badges.fedoraproject.org/pngs/associate-editor.png", "first_awarded":
+        1375888315.0, "first_awarded_person": "ralph", "last_awarded_person": "ankit1997",
+        "last_awarded": 1491242577.0, "percent_earned": 2.108473972480741, "id": "associate-editor",
+        "times_awarded": 947, "name": "Associate Editor"}, {"description": "Chaired
+        an IRC meeting.", "tags": "irc,community,", "issued": 1375889493.0, "image":
+        "https://badges.fedoraproject.org/pngs/irc-secretary-general.png", "first_awarded":
+        1375889493.0, "first_awarded_person": "ralph", "last_awarded_person": "yzhang",
+        "last_awarded": 1491431124.0, "percent_earned": 1.5451752237609653, "id":
+        "secretary-general", "times_awarded": 694, "name": "Secretary General"}, {"description":
+        "Voted on 250 tags for packages in Fedora Tagger.", "tags": "quality,tagger,",
+        "issued": 1375898650.0, "image": "https://badges.fedoraproject.org/pngs/tagger-03.png",
+        "first_awarded": 1375898650.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "imabug", "last_awarded": 1490198974.0, "percent_earned": 0.3139332947410607,
+        "id": "senior-tagger-tagger-iii", "times_awarded": 141, "name": "Senior Tagger
+        (Tagger III)"}, {"description": "Succesfully completed 250 koji builds.",
+        "tags": "development,koji,", "issued": 1377742978.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-04.png",
+        "first_awarded": 1377711940.0, "first_awarded_person": "remi", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491425650.0, "percent_earned": 0.5454869305784388,
+        "id": "if-you-build-it...-koji-success-iv", "times_awarded": 245, "name":
+        "If you build it... (Koji Success IV)"}, {"description": "Succesfully completed
+        a koji build.", "tags": "development,koji,", "issued": 1377742982.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-01.png", "first_awarded":
+        1377708898.0, "first_awarded_person": "mcpierce", "last_awarded_person": "nickboldt",
+        "last_awarded": 1490915696.0, "percent_earned": 2.858796811684553, "id": "if-you-build-it...-koji-success-i",
+        "times_awarded": 1284, "name": "If you build it... (Koji Success I)"}, {"description":
+        "Succesfully completed 10 koji builds.", "tags": "development,koji,", "issued":
+        1377742977.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-02.png",
+        "first_awarded": 1377708893.0, "first_awarded_person": "mcpierce", "last_awarded_person":
+        "sjenning", "last_awarded": 1491017600.0, "percent_earned": 2.3288952219797836,
+        "id": "if-you-build-it...-koji-success-ii", "times_awarded": 1046, "name":
+        "If you build it... (Koji Success II)"}, {"description": "Succesfully completed
+        50 koji builds.", "tags": "development,koji,", "issued": 1377742981.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-03.png", "first_awarded":
+        1377708896.0, "first_awarded_person": "mcpierce", "last_awarded_person": "tartare",
+        "last_awarded": 1491481451.0, "percent_earned": 1.4138130649686065, "id":
+        "if-you-build-it...-koji-success-iii", "times_awarded": 635, "name": "If you
+        build it... (Koji Success III)"}, {"description": "Ten or more questions and
+        answers on the Ask Fedora system.", "tags": "ask fedora,community,", "issued":
+        1377794097.0, "image": "https://badges.fedoraproject.org/pngs/askbot-curious-penguin-02.png",
+        "first_awarded": 1375889645.0, "first_awarded_person": "hmaarrfk", "last_awarded_person":
+        "fedo25", "last_awarded": 1491440137.0, "percent_earned": 1.4182660195039407,
+        "id": "curious-penguin-ask-fedora-ii", "times_awarded": 637, "name": "Curious
+        Penguin (Ask Fedora II)"}, {"description": "You had your first interaction
+        with the Ask Fedora forum system.", "tags": "ask fedora,community,", "issued":
+        1377794096.0, "image": "https://badges.fedoraproject.org/pngs/askbot-curious-penguin-01.png",
+        "first_awarded": 1375889644.0, "first_awarded_person": "hmaarrfk", "last_awarded_person":
+        "andre", "last_awarded": 1491511008.0, "percent_earned": 9.738611568775884,
+        "id": "curious-penguin-ask-fedora-i", "times_awarded": 4374, "name": "Curious
+        Penguin (Ask Fedora I)"}, {"description": "You updated your Security Question
+        with the Fedora Account System (FAS)", "tags": "account,community,", "issued":
+        1375988994.0, "image": "https://badges.fedoraproject.org/pngs/fas-riddle-me-this.png",
+        "first_awarded": 1375988994.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dtemirgaziyev", "last_awarded": 1491482061.0, "percent_earned": 1.903638063855368,
+        "id": "riddle-me-this", "times_awarded": 855, "name": "Riddle Me This"}, {"description":
+        "Pushed 30 commits to a Fedora package repository.", "tags": "development,",
+        "issued": 1376076329.0, "image": "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-02.png",
+        "first_awarded": 1375887918.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "mhonek", "last_awarded": 1490899614.0, "percent_earned": 2.0127354499710557,
+        "id": "you-can-call-me-patches-scm-ii", "times_awarded": 904, "name": "You
+        can call me \"Patches\" (SCM II)"}, {"description": "Submitted a koji build
+        that failed to build.", "tags": "development,oops,koji,", "issued": 1377887821.0,
+        "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-01.png",
+        "first_awarded": 1377711403.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "nickboldt", "last_awarded": 1491244448.0, "percent_earned": 1.94816760920871,
+        "id": "what-goes-up...-koji-failure-i", "times_awarded": 875, "name": "What
+        goes up... (Koji Failure I)"}, {"description": "Pushed 5 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076328.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-01.png", "first_awarded":
+        1375887918.0, "first_awarded_person": "jforbes", "last_awarded_person": "jscotka",
+        "last_awarded": 1491289110.0, "percent_earned": 2.6695462439328494, "id":
+        "you-can-call-me-patches-scm-i", "times_awarded": 1199, "name": "You can call
+        me \"Patches\" (SCM I)"}, {"description": "Pushed 400 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076329.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-04.png", "first_awarded":
+        1375892509.0, "first_awarded_person": "rdieter", "last_awarded_person": "berrange",
+        "last_awarded": 1491242273.0, "percent_earned": 0.5588457941844414, "id":
+        "you-can-call-me-patches-scm-iv", "times_awarded": 251, "name": "You can call
+        me \"Patches\" (SCM IV)"}, {"description": "Pushed 90 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076329.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-03.png", "first_awarded":
+        1375889053.0, "first_awarded_person": "mhlavink", "last_awarded_person": "cydrobolt",
+        "last_awarded": 1490744785.0, "percent_earned": 1.3603776105445964, "id":
+        "you-can-call-me-patches-scm-iii", "times_awarded": 611, "name": "You can
+        call me \"Patches\" (SCM III)"}, {"description": "Submitted 20 or more koji
+        builds that failed to build.", "tags": "development,oops,koji,", "issued":
+        1377887820.0, "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-03.png",
+        "first_awarded": 1377711401.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491441515.0, "percent_earned": 0.6523578394264594,
+        "id": "what-goes-up...-koji-failure-iii", "times_awarded": 293, "name": "What
+        goes up... (Koji Failure III)"}, {"description": "Submitted 5 or more koji
+        builds that failed to build.", "tags": "development,oops,koji,", "issued":
+        1377887822.0, "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-02.png",
+        "first_awarded": 1377711406.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "tkrizek", "last_awarded": 1490376511.0, "percent_earned": 1.329206928797257,
+        "id": "what-goes-up...-koji-failure-ii", "times_awarded": 597, "name": "What
+        goes up... (Koji Failure II)"}, {"description": "Pushed 100 bodhi updates
+        to the testing repositories.", "tags": "development", "issued": 1376077757.0,
+        "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-03.png",
+        "first_awarded": 1375892382.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491441872.0, "percent_earned": 0.5299015897047691,
+        "id": "is-this-thing-on-updates-testing-iii", "times_awarded": 238, "name":
+        "Is this thing on? (Updates-Testing III)"}, {"description": "*(a path to follow)*\nTested
+        and added karma to 5 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1376171387.0, "image": "https://badges.fedoraproject.org/pngs/tester-02.png",
+        "first_awarded": 1375889658.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "toshio", "last_awarded": 1491516269.0, "percent_earned": 2.70739635748319,
+        "id": "discovery-of-the-footprints-tester-ii", "times_awarded": 1216, "name":
+        "Discovery of the Footprints (Tester II)"}, {"description": "You got your
+        first 10 Fedora Badges", "tags": "community,badges,", "issued": 1376171392.0,
+        "image": "https://badges.fedoraproject.org/pngs/badger-01.png", "first_awarded":
+        1376118882.0, "first_awarded_person": "ignatenkobrain", "last_awarded_person":
+        "apollo7", "last_awarded": 1491362331.0, "percent_earned": 7.547757937391459,
+        "id": "junior-badger-badger-i", "times_awarded": 3390, "name": "Junior Badger
+        (Badger I)"}, {"description": "You pushed your first bodhi update to the stable
+        repositories.", "tags": "development,", "issued": 1376171906.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-01.png",
+        "first_awarded": 1375891204.0, "first_awarded_person": "blakegardner", "last_awarded_person":
+        "scox", "last_awarded": 1491419635.0, "percent_earned": 2.562675335084829,
+        "id": "like-a-rock-updates-stable-i", "times_awarded": 1151, "name": "Like
+        a Rock (Updates-Stable I)"}, {"description": "*(but only its rear, not its
+        head)*\nTested and added karma to 10 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1376171388.0, "image": "https://badges.fedoraproject.org/pngs/tester-03.png",
+        "first_awarded": 1375889660.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "alciregi", "last_awarded": 1491294835.0, "percent_earned": 1.6631785189473214,
+        "id": "perceiving-the-bull-tester-iii", "times_awarded": 747, "name": "Perceiving
+        the Bull (Tester III)"}, {"description": "Pushed 100 bodhi updates to the
+        stable repositories.", "tags": "development,", "issued": 1376171907.0, "image":
+        "https://badges.fedoraproject.org/pngs/like-a-rock-03.png", "first_awarded":
+        1375930621.0, "first_awarded_person": "cicku", "last_awarded_person": "mystro256",
+        "last_awarded": 1491340270.0, "percent_earned": 0.39408647637707617, "id":
+        "like-a-rock-updates-stable-iii", "times_awarded": 177, "name": "Like a Rock
+        (Updates-Stable III)"}, {"description": "*(aimless searching, only the sound
+        of cicadas)*\nTested and added karma to 1 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1376171388.0, "image": "https://badges.fedoraproject.org/pngs/tester-01.png",
+        "first_awarded": 1375889660.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "landgraf", "last_awarded": 1491498009.0, "percent_earned": 6.412254530881239,
+        "id": "in-search-of-the-bull-tester-i", "times_awarded": 2880, "name": "In
+        Search of the Bull (Tester I)"}, {"description": "Pushed 20 bodhi updates
+        to the stable repositories.", "tags": "development,", "issued": 1376171906.0,
+        "image": "https://badges.fedoraproject.org/pngs/like-a-rock-02.png", "first_awarded":
+        1375893154.0, "first_awarded_person": "pingou", "last_awarded_person": "cqi",
+        "last_awarded": 1491420777.0, "percent_earned": 1.2379213608229058, "id":
+        "like-a-rock-updates-stable-ii", "times_awarded": 556, "name": "Like a Rock
+        (Updates-Stable II)"}, {"description": "Partied with Flock 2013 (at the South
+        Carolina Aquarium)", "tags": "party,flock,event,", "issued": 1376252202.0,
+        "image": "https://badges.fedoraproject.org/pngs/tanked.png", "first_awarded":
+        1376252202.0, "first_awarded_person": "ralph", "last_awarded_person": "gnokii",
+        "last_awarded": 1411656869.0, "percent_earned": 0.14026806786302712, "id":
+        "tanked!", "times_awarded": 63, "name": "Tanked!"}, {"description": "You proposed
+        a new idea for a Fedora Badge.  Thanks!", "tags": "community,badges,", "issued":
+        1376261121.0, "image": "https://badges.fedoraproject.org/pngs/badge-muse-01.png",
+        "first_awarded": 1376261121.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "frantisekz", "last_awarded": 1484316413.0, "percent_earned": 0.3161597720087278,
+        "id": "badge-muse-badge-ideas-i", "times_awarded": 142, "name": "Badge Muse
+        (Badge Ideas I)"}, {"description": "You got a new koji certificate!", "tags":
+        "account,community,", "issued": 1376347603.0, "image": "https://badges.fedoraproject.org/pngs/fas-bona-fide.png",
+        "first_awarded": 1375891140.0, "first_awarded_person": "ralphlange", "last_awarded_person":
+        "dtemirgaziyev", "last_awarded": 1491477255.0, "percent_earned": 5.508304760208398,
+        "id": "bona-fide", "times_awarded": 2474, "name": "Bona Fide"}, {"description":
+        " Submitted a Supplemental Wallpaper Idea for Fedora 20.", "tags": "content,design,nuancier,",
+        "issued": 1378218417.0, "image": "https://badges.fedoraproject.org/pngs/design-keepin-fedora-beautiful-f20.png",
+        "first_awarded": 1377269416.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "iwan14", "last_awarded": 1378218427.0, "percent_earned": 0.05788840895934453,
+        "id": "keepin-fedora-beautiful-f20", "times_awarded": 26, "name": "Keepin
+        Fedora Beautiful (F20)"}, {"description": "You got (about) 50 Fedora Badges.  Way
+        to go!", "tags": "community,badges,", "issued": 1378504718.0, "image": "https://badges.fedoraproject.org/pngs/badger-02.png",
+        "first_awarded": 1377470469.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "mailga", "last_awarded": 1491037049.0, "percent_earned": 0.823796589036826,
+        "id": "senior-badger-badger-ii", "times_awarded": 370, "name": "Senior Badger
+        (Badger II)"}, {"description": "You pushed your first bodhi update to the
+        testing repositories.", "tags": "development", "issued": 1376077757.0, "image":
+        "https://badges.fedoraproject.org/pngs/is-this-thing-on-01.png", "first_awarded":
+        1375889661.0, "first_awarded_person": "fantom", "last_awarded_person": "olem",
+        "last_awarded": 1491070078.0, "percent_earned": 2.658413857594514, "id": "is-this-thing-on-updates-testing-i",
+        "times_awarded": 1194, "name": "Is this thing on? (Updates-Testing I)"}, {"description":
+        "Pushed 20 bodhi updates to the testing repositories.", "tags": "development",
+        "issued": 1376077757.0, "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-02.png",
+        "first_awarded": 1375889878.0, "first_awarded_person": "stefw", "last_awarded_person":
+        "dustymabe", "last_awarded": 1491432891.0, "percent_earned": 1.467248519392617,
+        "id": "is-this-thing-on-updates-testing-ii", "times_awarded": 659, "name":
+        "Is this thing on? (Updates-Testing II)"}, {"description": "You added a co-maintainer
+        to a package. BFF!", "tags": "development,", "issued": 1376531564.0, "image":
+        "https://badges.fedoraproject.org/pngs/pkgdb-partners-in-crime.png", "first_awarded":
+        1376531564.0, "first_awarded_person": "ralph", "last_awarded_person": "dustymabe",
+        "last_awarded": 1491406327.0, "percent_earned": 1.714387496103665, "id": "partners-in-crime",
+        "times_awarded": 770, "name": "Partners in Crime"}, {"description": "*(a great
+        struggle, repeated escapes, discipline required)*\nTested and added karma
+        to 20 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1378251927.0, "image": "https://badges.fedoraproject.org/pngs/tester-04.png",
+        "first_awarded": 1375889657.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "mcepl", "last_awarded": 1491490795.0, "percent_earned": 0.961838179632186,
+        "id": "catching-the-bull-tester-iv", "times_awarded": 432, "name": "Catching
+        the Bull (Tester IV)"}, {"description": "Voted on 1000 tags for packages in
+        Fedora Tagger.", "tags": "quality,tagger,", "issued": 1378613794.0, "image":
+        "https://badges.fedoraproject.org/pngs/tagger-04.png", "first_awarded": 1376814408.0,
+        "first_awarded_person": "jamielinux", "last_awarded_person": "bt0dotninja",
+        "last_awarded": 1489638271.0, "percent_earned": 0.16253284053969808, "id":
+        "master-tagger-tagger-iv", "times_awarded": 73, "name": "Master Tagger (Tagger
+        IV)"}, {"description": "A member of the sysadmin-badges group.  Nothing to
+        see here..  move along.", "tags": "infrastructure,badges,community,", "issued":
+        1378319150.0, "image": "https://badges.fedoraproject.org/pngs/fas-inside-job.png",
+        "first_awarded": 1378319146.0, "first_awarded_person": "oddshocks", "last_awarded_person":
+        "jflory7", "last_awarded": 1472154166.0, "percent_earned": 0.02226477267667097,
+        "id": "inside-job", "times_awarded": 10, "name": "Inside Job"}, {"description":
+        "Indicated your timezone in FAS so everyone can sync with you", "tags": "account,community,",
+        "issued": 1378320160.0, "image": "https://badges.fedoraproject.org/pngs/fas-white-rabbit.png",
+        "first_awarded": 1378313171.0, "first_awarded_person": "churchyard", "last_awarded_person":
+        "salvadorhm", "last_awarded": 1491509400.0, "percent_earned": 8.144453845126241,
+        "id": "white-rabbit", "times_awarded": 3658, "name": "White Rabbit"}, {"description":
+        "You have a Libravatar associated with your Fedora OpenID.", "tags": "libravatar,community,",
+        "issued": 1376946138.0, "image": "https://badges.fedoraproject.org/pngs/mugshot.png",
+        "first_awarded": 1376946138.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jcholast", "last_awarded": 1491423845.0, "percent_earned": 3.061406243042258,
+        "id": "mugshot", "times_awarded": 1375, "name": "Mugshot"}, {"description":
+        "A member of sysadmin-main.", "tags": "development,infrastructure,", "issued":
+        1378320106.0, "image": "https://badges.fedoraproject.org/pngs/fas-trust-me-i-know-what-i-am-doing.png",
+        "first_awarded": 1378320079.0, "first_awarded_person": "lmacken", "last_awarded_person":
+        "pbrobinson", "last_awarded": 1430409877.0, "percent_earned": 0.03785011355034065,
+        "id": "trust-me,-i-know-what-i-am-doing", "times_awarded": 17, "name": "Trust
+        me, I know what I am doing"}, {"description": "Has used an ansible playbook
+        to deploy to Fedora Infrastructure", "tags": "development,infrastructure,",
+        "issued": 1378321037.0, "image": "https://badges.fedoraproject.org/pngs/ansible-maestro.png",
+        "first_awarded": 1378321037.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jkaluza", "last_awarded": 1490254934.0, "percent_earned": 0.10241795431268647,
+        "id": "maestro", "times_awarded": 46, "name": "Maestro"}, {"description":
+        "Created your own user profile page on the Fedora Wiki", "tags": "content,",
+        "issued": 1378321704.0, "image": "https://badges.fedoraproject.org/pngs/wiki-let-me-introduce-myself.png",
+        "first_awarded": 1378321704.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "cra", "last_awarded": 1491437628.0, "percent_earned": 4.250345103976488,
+        "id": "let-me-introduce-myself", "times_awarded": 1909, "name": "Let Me Introduce
+        Myself"}, {"description": "You submitted a bodhi update including multiple
+        packages.", "tags": "development,", "issued": 1377206257.0, "image": "https://badges.fedoraproject.org/pngs/bodhi-binary-star.png",
+        "first_awarded": 1376049444.0, "first_awarded_person": "jwrdegoede", "last_awarded_person":
+        "ngompa", "last_awarded": 1490659963.0, "percent_earned": 0.7614552255421472,
+        "id": "binary-star", "times_awarded": 342, "name": "Binary Star"}, {"description":
+        "Became a sponsor of new packagers.", "tags": "development,account,", "issued":
+        1377283223.0, "image": "https://badges.fedoraproject.org/pngs/fas-packager-sponsor.png",
+        "first_awarded": 1377283156.0, "first_awarded_person": "abompard", "last_awarded_person":
+        "xavierb", "last_awarded": 1484573828.0, "percent_earned": 0.32506568107939615,
+        "id": "packager-sponsor", "times_awarded": 146, "name": "Packager Sponsor"},
+        {"description": "You posted 5 or more things to the Fedora Planet!", "tags":
+        "blog,community,", "issued": 1377697248.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-02.png",
+        "first_awarded": 1375902038.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468491.0, "percent_earned": 1.3470187469385937,
+        "id": "bloggin-it!-planet-ii", "times_awarded": 605, "name": "Bloggin'' it!
+        (Planet II)"}, {"description": "You posted 10 or more things to the Fedora
+        Planet!", "tags": "blog,community,", "issued": 1377697247.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-03.png",
+        "first_awarded": 1375902037.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468509.0, "percent_earned": 1.1399563610455536,
+        "id": "bloggin-it!-planet-iii", "times_awarded": 512, "name": "Bloggin'' it!
+        (Planet III)"}, {"description": "You posted something to the Fedora Planet.",
+        "tags": "blog,community,", "issued": 1377697248.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-01.png",
+        "first_awarded": 1375902037.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468543.0, "percent_earned": 1.6231019281293138,
+        "id": "bloggin-it!-planet-i", "times_awarded": 729, "name": "Bloggin'' it!
+        (Planet I)"}, {"description": "You submitted a bodhi update with type \"security\"",
+        "tags": "development,security,", "issued": 1380316519.0, "image": "https://badges.fedoraproject.org/pngs/white-hat.png",
+        "first_awarded": 1379357860.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "dtimms", "last_awarded": 1491484910.0, "percent_earned": 0.939573406955515,
+        "id": "white-hat", "times_awarded": 422, "name": "White Hat"}, {"description":
+        "Voted in a supplemental wallpapers election for a Fedora release", "tags":
+        "election,community,nuancier,", "issued": 1380637092.0, "image": "https://badges.fedoraproject.org/pngs/nauncier.png",
+        "first_awarded": 1380617342.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "duffy", "last_awarded": 1381233461.0, "percent_earned": 0.17589170414570066,
+        "id": "nuancier", "times_awarded": 79, "name": "Nuancier"}, {"description":
+        "You''ve had a FAS account for over two years", "tags": "lifecycle,community,",
+        "issued": 1389056166.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-embryo.png",
+        "first_awarded": 1389043685.0, "first_awarded_person": "joev", "last_awarded_person":
+        "andrespol", "last_awarded": 1491360716.0, "percent_earned": 20.256490181235247,
+        "id": "embryo", "times_awarded": 9098, "name": "Embryo"}, {"description":
+        "*(less straying/discipline, bull becomes gentle/obedient)*\nTested and added
+        karma to 40 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1381160713.0, "image": "https://badges.fedoraproject.org/pngs/tester-05.png",
+        "first_awarded": 1375896222.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "piotrdrag", "last_awarded": 1491478479.0, "percent_earned": 0.549939885113773,
+        "id": "taming-the-bull-tester-v", "times_awarded": 247, "name": "Taming the
+        Bull (Tester V)"}, {"description": "You contributed to a Common Bugs page.
+        Bugs? What bugs?", "tags": "content,", "issued": 1381324210.0, "image": "https://badges.fedoraproject.org/pngs/wiki-common-bugs.png",
+        "first_awarded": 1381324210.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ferdnyc", "last_awarded": 1484744055.0, "percent_earned": 0.1469474996660284,
+        "id": "common-bugs-contributor", "times_awarded": 66, "name": "Common Bugs
+        Contributor"}, {"description": "You have logged in to the Fedora Badges app",
+        "tags": "community,badges,", "issued": 1382755079.0, "image": "https://badges.fedoraproject.org/pngs/baby-badger.png",
+        "first_awarded": 1382755079.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "metonymy", "last_awarded": 1491502807.0, "percent_earned": 6.249721690341541,
+        "id": "baby-badger", "times_awarded": 2807, "name": "Baby Badger"}, {"description":
+        "Pushed 500 bodhi updates to the testing repositories.", "tags": "development",
+        "issued": 1384804767.0, "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-04.png",
+        "first_awarded": 1376840497.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "martinkg", "last_awarded": 1489655875.0, "percent_earned": 0.09351204524201807,
+        "id": "is-this-thing-on-updates-testing-iv", "times_awarded": 42, "name":
+        "Is this thing on? (Updates-Testing IV)"}, {"description": "You retired a
+        package from the distribution. It hurt, but it had to be done.", "tags": "development,ownership,",
+        "issued": 1389043285.0, "image": "https://badges.fedoraproject.org/pngs/pkgdb-old-yeller.png",
+        "first_awarded": 1382533838.0, "first_awarded_person": "notting", "last_awarded_person":
+        "pwalter", "last_awarded": 1491445231.0, "percent_earned": 0.6167342031437859,
+        "id": "old-yeller", "times_awarded": 277, "name": "Old Yeller"}, {"description":
+        "  You gave a presentation at Flock 2013, the Fedora Contributor Conference",
+        "tags": "event,speaker,", "issued": 1386089550.0, "image": "https://badges.fedoraproject.org/pngs/flockpresenter.png",
+        "first_awarded": 1386089406.0, "first_awarded_person": "sundaram", "last_awarded_person":
+        "pjones", "last_awarded": 1407317187.0, "percent_earned": 0.1580798860043639,
+        "id": "flock-2013-speaker", "times_awarded": 71, "name": "Flock 2013 Speaker"},
+        {"description": "You posted 20 or more things to the Fedora Planet!", "tags":
+        "blog,community,", "issued": 1386909731.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-04.png",
+        "first_awarded": 1375904463.0, "first_awarded_person": "paulmellors", "last_awarded_person":
+        "stefw", "last_awarded": 1491468562.0, "percent_earned": 0.7547757937391459,
+        "id": "bloggin-it!-planet-iv", "times_awarded": 339, "name": "Bloggin'' it!
+        (Planet IV)"}, {"description": "Edited at least 50 wiki pages.", "tags": "content,wiki,",
+        "issued": 1389045512.0, "image": "https://badges.fedoraproject.org/pngs/senior-editor.png",
+        "first_awarded": 1375891555.0, "first_awarded_person": "ppisar", "last_awarded_person":
+        "augenauf", "last_awarded": 1491316933.0, "percent_earned": 1.013047156788529,
+        "id": "senior-editor", "times_awarded": 455, "name": "Senior Editor"}, {"description":
+        "You''ve had a FAS account for over three years", "tags": "lifecycle,community,",
+        "issued": 1389064377.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-tadpole.png",
+        "first_awarded": 1389061835.0, "first_awarded_person": "jwboyer", "last_awarded_person":
+        "avichi", "last_awarded": 1491361425.0, "percent_earned": 14.18933962684241,
+        "id": "tadpole", "times_awarded": 6373, "name": "Tadpole"}, {"description":
+        "You''ve had a FAS account for over one year", "tags": "lifecycle,community,",
+        "issued": 1389070973.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-egg.png",
+        "first_awarded": 1389068846.0, "first_awarded_person": "darthvader", "last_awarded_person":
+        "amogh23k", "last_awarded": 1491362307.0, "percent_earned": 28.41430288996749,
+        "id": "egg", "times_awarded": 12762, "name": "Egg"}, {"description": "You''ve
+        had a FAS account for over 5 years", "tags": "lifecycle,community,", "issued":
+        1389077372.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-tadpole-with-legs.png",
+        "first_awarded": 1389076692.0, "first_awarded_person": "ftpfree", "last_awarded_person":
+        "akopacsi", "last_awarded": 1490757692.0, "percent_earned": 7.30952486975108,
+        "id": "tadpole-with-legs", "times_awarded": 3283, "name": "Tadpole with Legs"},
+        {"description": "You''re a developer contributing to the Fedora Badges stack",
+        "tags": "development,badges,", "issued": 1389131503.0, "image": "https://badges.fedoraproject.org/pngs/badge-off.png",
+        "first_awarded": 1389131479.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "lsedlar", "last_awarded": 1476326553.0, "percent_earned": 0.05120897715634323,
+        "id": "badge-off!", "times_awarded": 23, "name": "Badge Off!"}, {"description":
+        "*(great joy)*\nTested and added karma to 80 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1389368853.0, "image": "https://badges.fedoraproject.org/pngs/tester-06.png",
+        "first_awarded": 1375896221.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "sassam", "last_awarded": 1491148875.0, "percent_earned": 0.3072538629380594,
+        "id": "riding-the-bull-home-tester-vi", "times_awarded": 138, "name": "Riding
+        the Bull Home (Tester VI)"}, {"description": "You were sponsored into the
+        Fedora Infrastructure Apprentice group", "tags": "development,infrastructure,",
+        "issued": 1389889699.0, "image": "https://badges.fedoraproject.org/pngs/no-ronin.png",
+        "first_awarded": 1389889653.0, "first_awarded_person": "jam3s", "last_awarded_person":
+        "metonymy", "last_awarded": 1491476646.0, "percent_earned": 0.4208042035890813,
+        "id": "no-longer-a-ronin", "times_awarded": 189, "name": "No Longer a Ronin"},
+        {"description": "Added 250 new tags to packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1390419184.0, "image": "https://badges.fedoraproject.org/pngs/master-package-tagger.png",
+        "first_awarded": 1390419184.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "bt0dotninja", "last_awarded": 1489643141.0, "percent_earned": 0.13358863606002583,
+        "id": "master-package-tagger-package-tagger-iv", "times_awarded": 60, "name":
+        "Master Package Tagger (Package Tagger IV)"}, {"description": "Added a new
+        tag for a package in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419187.0, "image": "https://badges.fedoraproject.org/pngs/junior-package-tagger.png",
+        "first_awarded": 1390412894.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "bbroad", "last_awarded": 1491002111.0, "percent_earned": 0.8750055661931692,
+        "id": "junior-package-tagger-package-tagger-i", "times_awarded": 393, "name":
+        "Junior Package Tagger (Package Tagger I)"}, {"description": "Added 50 new
+        tags to packages in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419190.0, "image": "https://badges.fedoraproject.org/pngs/senior-package-tagger.png",
+        "first_awarded": 1390412897.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "ignacio", "last_awarded": 1485888954.0, "percent_earned": 0.28944204479672264,
+        "id": "senior-package-tagger-package-tagger-iii", "times_awarded": 130, "name":
+        "Senior Package Tagger (Package Tagger III)"}, {"description": "Added 10 new
+        tags for packages in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419193.0, "image": "https://badges.fedoraproject.org/pngs/package-tagger.png",
+        "first_awarded": 1390412900.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "imabug", "last_awarded": 1490199130.0, "percent_earned": 0.5120897715634324,
+        "id": "package-tagger-package-tagger-ii", "times_awarded": 230, "name": "Package
+        Tagger (Package Tagger II)"}, {"description": "You gave a presentation at
+        DevConf.cz, 2014", "tags": "devconf,speaker,event,", "issued": 1390422108.0,
+        "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter.png", "first_awarded":
+        1390422108.0, "first_awarded_person": "ralph", "last_awarded_person": "siddharths",
+        "last_awarded": 1420480194.0, "percent_earned": 0.10241795431268647, "id":
+        "devconf-2014-speaker", "times_awarded": 46, "name": "DevConf 2014 Speaker"},
+        {"description": "Pushed 1000 commits to a Fedora package repository.", "tags":
+        "development,", "issued": 1390944539.0, "image": "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-05.png",
+        "first_awarded": 1375892508.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "terjeros", "last_awarded": 1491331717.0, "percent_earned": 0.26717727212005166,
+        "id": "patches-scm-v", "times_awarded": 120, "name": "Patches (SCM V)"}, {"description":
+        "Created a buildroot override in the koji build system", "tags": "development,",
+        "issued": 1391016371.0, "image": "https://badges.fedoraproject.org/pngs/override-you-say.png",
+        "first_awarded": 1390489685.0, "first_awarded_person": "notting", "last_awarded_person":
+        "decathorpe", "last_awarded": 1491331634.0, "percent_earned": 0.7480963619361446,
+        "id": "override,-you-say", "times_awarded": 336, "name": "Override, you say?"},
+        {"description": "You attended the 2014 iteration of devconf.cz, a yearly Linux
+        and JBoss conference in the Czech Republic", "tags": "event,devconf,", "issued":
+        1391763090.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-attendee.png",
+        "first_awarded": 1391760446.0, "first_awarded_person": "churchyard", "last_awarded_person":
+        "siddharths", "last_awarded": 1419207296.0, "percent_earned": 0.13804159059536003,
+        "id": "devconf-attendee", "times_awarded": 62, "name": "DevConf 2014 Attendee"},
+        {"description": "You are a member of the videos team", "tags": "video,community,",
+        "issued": 1392217171.0, "image": "https://badges.fedoraproject.org/pngs/videographer.png",
+        "first_awarded": 1392217167.0, "first_awarded_person": "williamjmorenor",
+        "last_awarded_person": "swilson", "last_awarded": 1458943142.0, "percent_earned":
+        0.03562363628267355, "id": "videographer", "times_awarded": 16, "name": "Videographer"},
+        {"description": "Succesfully completed 1000 koji builds.", "tags": "development,koji,",
+        "issued": 1392827319.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-05.png",
+        "first_awarded": 1377712281.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "zbyszek", "last_awarded": 1490388487.0, "percent_earned": 0.15585340873669679,
+        "id": "all-your-$arch-are-belong-to-us-koji-success-v", "times_awarded": 70,
+        "name": "All your $arch are belong to us (Koji Success V)"}, {"description":
+        "Successfully completed a copr build", "tags": "development,copr,", "issued":
+        1392676733.0, "image": "https://badges.fedoraproject.org/pngs/copr-build.png",
+        "first_awarded": 1389199510.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "shosca", "last_awarded": 1491441908.0, "percent_earned": 3.958676581912099,
+        "id": "building-the-outer-ring-copr-build-i", "times_awarded": 1778, "name":
+        "Building the Outer Ring (Copr Build I)"}, {"description": "Someone gave one
+        of your updates good karma", "tags": "development,karma,", "issued": 1392712116.0,
+        "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around.png",
+        "first_awarded": 1392392577.0, "first_awarded_person": "spot", "last_awarded_person":
+        "cydrobolt", "last_awarded": 1491503467.0, "percent_earned": 2.046132608986062,
+        "id": "what-goes-around-comes-around-karma-i", "times_awarded": 919, "name":
+        "What goes around comes around (Karma I)"}, {"description": "Tested an update
+        for an older stable release of Fedora", "tags": "quality,", "issued": 1392752446.0,
+        "image": "https://badges.fedoraproject.org/pngs/it-still-works.png", "first_awarded":
+        1386126231.0, "first_awarded_person": "kevin", "last_awarded_person": "caiqian",
+        "last_awarded": 1481903131.0, "percent_earned": 1.6765373825533243, "id":
+        "it-still-works!", "times_awarded": 753, "name": "It still works!"}, {"description":
+        "You submitted an update to bodhi with one or more bugzilla bug numbers attached",
+        "tags": "development,", "issued": 1394463577.0, "image": "https://badges.fedoraproject.org/pngs/take-this.png",
+        "first_awarded": 1393128956.0, "first_awarded_person": "mooninite", "last_awarded_person":
+        "cydrobolt", "last_awarded": 1491442200.0, "percent_earned": 2.1641359041724186,
+        "id": "take-this-and-call-me-in-the-morning", "times_awarded": 972, "name":
+        "Take this and call me in the morning"}, {"description": "You posted 40 or
+        more things to the Fedora Planet!", "tags": "blog,community,", "issued": 1398960087.0,
+        "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-05.png",
+        "first_awarded": 1375963234.0, "first_awarded_person": "rjones", "last_awarded_person":
+        "stefw", "last_awarded": 1491468526.0, "percent_earned": 0.35623636282673554,
+        "id": "bob-loblaw-planet-v", "times_awarded": 160, "name": "Bob Loblaw (Planet
+        V)"}, {"description": "Pushed 500 bodhi updates to the stable repositories.",
+        "tags": "development,", "issued": 1396123360.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-04.png",
+        "first_awarded": 1376079014.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "dfateyev", "last_awarded": 1490465620.0, "percent_earned": 0.06902079529768002,
+        "id": "like-a-rock-updates-stable-iv", "times_awarded": 31, "name": "Like
+        a Rock (Updates-Stable IV)"}, {"description": "You set up a meeting in the
+        Fedora Calendar", "tags": "community,", "issued": 1397054728.0, "image": "https://badges.fedoraproject.org/pngs/lets-do-lunch.png",
+        "first_awarded": 1391191973.0, "first_awarded_person": "lbazan", "last_awarded_person":
+        "ignatenkobrain", "last_awarded": 1485808919.0, "percent_earned": 0.14472102239836132,
+        "id": "lets-do-lunch!", "times_awarded": 65, "name": "Let''s do lunch!"},
+        {"description": "Sip the lemonade away (You deserve it for remembering your
+        responsibility)!", "tags": "community,", "issued": 1397059347.0, "image":
+        "https://badges.fedoraproject.org/pngs/beach-bum.png", "first_awarded": 1394486757.0,
+        "first_awarded_person": "shaiton", "last_awarded_person": "mythcat", "last_awarded":
+        1484694397.0, "percent_earned": 0.4208042035890813, "id": "vacation", "times_awarded":
+        189, "name": "Vacation"}, {"description": "You visited the Fedora booth at
+        Pycon, 2014", "tags": "python,event,pycon,", "issued": 1397168025.0, "image":
+        "https://badges.fedoraproject.org/pngs/pycon_2014_attendee.png", "first_awarded":
+        1397168025.0, "first_awarded_person": "ralph", "last_awarded_person": "abompard",
+        "last_awarded": 1397573900.0, "percent_earned": 0.02226477267667097, "id":
+        "pycon-2014-attendee", "times_awarded": 10, "name": "Pycon 2014 Attendee"},
+        {"description": "You submitted feedback for an update in Bodhi after it was
+        pushed to stable", "tags": "quality,oops,", "issued": 1397684618.0, "image":
+        "https://badges.fedoraproject.org/pngs/missed-the-train.png", "first_awarded":
+        1392396477.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "thozza", "last_awarded": 1439904242.0, "percent_earned": 0.6345460212851227,
+        "id": "missed-the-train", "times_awarded": 285, "name": "Missed the Train"},
+        {"description": "You built a package owned by someone else.  It takes a village...",
+        "tags": "development,", "issued": 1400246660.0, "image": "https://badges.fedoraproject.org/pngs/helping_hand.png",
+        "first_awarded": 1394038498.0, "first_awarded_person": "sochotni", "last_awarded_person":
+        "nickboldt", "last_awarded": 1490915668.0, "percent_earned": 0.984102952308857,
+        "id": "helping-hand", "times_awarded": 442, "name": "Helping Hand"}, {"description":
+        "You''ve been granted the blessing of the Fedora Project Leader.", "tags":
+        "fpl,miscellaneous,", "issued": 1401833399.0, "image": "https://badges.fedoraproject.org/pngs/fpl-blessing.png",
+        "first_awarded": 1376269552.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "skottler", "last_awarded": 1401833697.0, "percent_earned": 0.20483590862537293,
+        "id": "the-blessing-of-the-fpl", "times_awarded": 92, "name": "The Blessing
+        of the FPL"}, {"description": "Submitted 100 or more koji builds that failed
+        to build.", "tags": "development,oops,koji,", "issued": 1403195400.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-what-goes-up-04.png", "first_awarded":
+        1377715853.0, "first_awarded_person": "vicodan", "last_awarded_person": "wtaymans",
+        "last_awarded": 1490906731.0, "percent_earned": 0.20038295409003873, "id":
+        "what-goes-up...-koji-failure-iv", "times_awarded": 90, "name": "What goes
+        up... (Koji Failure IV)"}, {"description": "You updated your password with
+        the Fedora Account System (FAS)", "tags": "account,community,", "issued":
+        1404948910.0, "image": "https://badges.fedoraproject.org/pngs/fas-paranoid-panda.png",
+        "first_awarded": 1376708721.0, "first_awarded_person": "rstrode", "last_awarded_person":
+        "payasr", "last_awarded": 1491512765.0, "percent_earned": 61.86712383666563,
+        "id": "paranoid-panda", "times_awarded": 27787, "name": "Paranoid Panda"},
+        {"description": "      You gave a presentation at Flock 2014, the Fedora Contributor
+        Conference", "tags": "event,flock,speaker,", "issued": 1405362797.0, "image":
+        "https://badges.fedoraproject.org/pngs/flock-2014-speaker.png", "first_awarded":
+        1405362496.0, "first_awarded_person": "abompard", "last_awarded_person": "amita",
+        "last_awarded": 1408553413.0, "percent_earned": 0.16253284053969808, "id":
+        "flock-2014-speaker", "times_awarded": 73, "name": "Flock 2014 Speaker"},
+        {"description": "You have received 10 or more good karma on your bodhi updates",
+        "tags": "development,karma,", "issued": 1406215355.0, "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-10.png",
+        "first_awarded": 1392397378.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "jvymazal", "last_awarded": 1491284602.0, "percent_earned": 1.099879770227546,
+        "id": "what-goes-around-comes-around-karma-ii", "times_awarded": 494, "name":
+        "What goes around comes around (Karma II)"}, {"description": "Edited at least
+        100 wiki pages.", "tags": "content,wiki,", "issued": 1406329746.0, "image":
+        "https://badges.fedoraproject.org/pngs/master-editor.png", "first_awarded":
+        1375891554.0, "first_awarded_person": "ppisar", "last_awarded_person": "athoscr",
+        "last_awarded": 1490991464.0, "percent_earned": 0.5900164759317807, "id":
+        "master-editor", "times_awarded": 265, "name": "Master Editor"}, {"description":
+        "You have received 50 or more good karma on your bodhi updates", "tags": "development,karma,",
+        "issued": 1406630310.0, "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-50.png",
+        "first_awarded": 1393287975.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "terjeros", "last_awarded": 1491384354.0, "percent_earned": 0.389633521841742,
+        "id": "what-goes-around-comes-around-karma-iii", "times_awarded": 175, "name":
+        "What goes around comes around (Karma III)"}, {"description": "  You attended
+        Flock 2014, the Fedora Contributor Conference", "tags": "event,flock,", "issued":
+        1407308400.0, "image": "https://badges.fedoraproject.org/pngs/flock-2014-attendee.png",
+        "first_awarded": 1405296000.0, "first_awarded_person": "marinaz", "last_awarded_person":
+        "rathann", "last_awarded": 1426267409.0, "percent_earned": 0.27163022665538583,
+        "id": "flock-2014-attendee", "times_awarded": 122, "name": "Flock 2014 Attendee"},
+        {"description": "You completed a run of the kernel regression test suite",
+        "tags": "kernel,quality,", "issued": 1407409472.0, "image": "https://badges.fedoraproject.org/pngs/kernel-tester.png",
+        "first_awarded": 1407405561.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "pperron", "last_awarded": 1490999828.0, "percent_earned": 0.49873090795742975,
+        "id": "science-kernel-tester-i", "times_awarded": 224, "name": "Science (Kernel
+        Tester I)"}, {"description": "Ahoy! You attended the boat party at the Flock
+        2014 conference in Prague!", "tags": "event,flock,", "issued": 1407430507.0,
+        "image": "https://badges.fedoraproject.org/pngs/party-pirate.png", "first_awarded":
+        1407430497.0, "first_awarded_person": "lmacken", "last_awarded_person": "amsharma",
+        "last_awarded": 1408665317.0, "percent_earned": 0.19815647682237164, "id":
+        "party-pirate", "times_awarded": 89, "name": "Party Pirate"}, {"description":
+        "You submitted 200 Bodhi updates for an EPEL branch.", "tags": "development,epel,",
+        "issued": 1407498349.0, "image": "https://badges.fedoraproject.org/pngs/corporate-overlord.png",
+        "first_awarded": 1407423571.0, "first_awarded_person": "remi", "last_awarded_person":
+        "pghmcfc", "last_awarded": 1485788654.0, "percent_earned": 0.03339715901500646,
+        "id": "corporate-overlord", "times_awarded": 15, "name": "Corporate Overlord"},
+        {"description": "You submitted 50 Bodhi updates for an EPEL branch.", "tags":
+        "development,epel,", "issued": 1407498359.0, "image": "https://badges.fedoraproject.org/pngs/corporate-shill.png",
+        "first_awarded": 1407423581.0, "first_awarded_person": "remi", "last_awarded_person":
+        "kdudka", "last_awarded": 1491214689.0, "percent_earned": 0.18925056775170326,
+        "id": "corporate-shill", "times_awarded": 85, "name": "Corporate Shill"},
+        {"description": "You submitted an Bodhi update for an EPEL branch.", "tags":
+        "development,epel,", "issued": 1407498367.0, "image": "https://badges.fedoraproject.org/pngs/corporate-drone.png",
+        "first_awarded": 1407423591.0, "first_awarded_person": "remi", "last_awarded_person":
+        "dsommers", "last_awarded": 1490298016.0, "percent_earned": 1.3024892015852518,
+        "id": "corporate-drone", "times_awarded": 585, "name": "Corporate Drone"},
+        {"description": "You got 100 Fedora Badges!  Wow!", "tags": "community,badges,",
+        "issued": 1407498391.0, "image": "https://badges.fedoraproject.org/pngs/badger-03.png",
+        "first_awarded": 1394558737.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "vondruch", "last_awarded": 1491296529.0, "percent_earned": 0.14026806786302712,
+        "id": "master-badger-badger-iii", "times_awarded": 63, "name": "Master Badger
+        (Badger III)"}, {"description": "*(the bull forgotten, discipline''s whip
+        is idle; stillness)*\nTested and added karma to 125 updates-testing updates
+        in Bodhi.\n", "tags": "ten bulls,quality,", "issued": 1408115651.0, "image":
+        "https://badges.fedoraproject.org/pngs/tester-07.png", "first_awarded": 1375896222.0,
+        "first_awarded_person": "kevin", "last_awarded_person": "fale", "last_awarded":
+        1490321832.0, "percent_earned": 0.2248742040343768, "id": "the-bull-transcended-tester-vii",
+        "times_awarded": 101, "name": "The Bull Transcended (Tester VII)"}, {"description":
+        "You danced with Toshio (abadger1999) at a Fedora event", "tags": "event,freshness,",
+        "issued": 1408122340.0, "image": "https://badges.fedoraproject.org/pngs/dancing_with_toshio.png",
+        "first_awarded": 1408121695.0, "first_awarded_person": "abompard", "last_awarded_person":
+        "rbergero", "last_awarded": 1455959914.0, "percent_earned": 0.0712472725653471,
+        "id": "dancing-with-toshio", "times_awarded": 32, "name": "Dancing with Toshio"},
+        {"description": "Voted in a supplemental wallpapers election for the Fedora
+        21 release", "tags": "election,community,nuancier,", "issued": 1408150877.0,
+        "image": "https://badges.fedoraproject.org/pngs/nuancier-f21-voted.png", "first_awarded":
+        1408147284.0, "first_awarded_person": "gnokii", "last_awarded_person": "pravins",
+        "last_awarded": 1409902273.0, "percent_earned": 0.2449124994433807, "id":
+        "nuancier-21", "times_awarded": 110, "name": "Nuancier 21"}, {"description":
+        "We''re sorry, but you had one of your badges revoked.  Something must have
+        gone wrong.", "tags": "sad trombone,badges,community,", "issued": 1408725704.0,
+        "image": "https://badges.fedoraproject.org/pngs/consolation-prize.png", "first_awarded":
+        1392255607.0, "first_awarded_person": "dmalcolm", "last_awarded_person": "keitellf",
+        "last_awarded": 1465418173.0, "percent_earned": 0.8972703388698402, "id":
+        "consolation-prize", "times_awarded": 403, "name": "Consolation Prize"}, {"description":
+        "You updated your GPG key with the Fedora Account System (FAS)", "tags": "account,community,",
+        "issued": 1408455462.0, "image": "https://badges.fedoraproject.org/pngs/fas-crypto-panda.png",
+        "first_awarded": 1375891835.0, "first_awarded_person": "itorres", "last_awarded_person":
+        "usuha", "last_awarded": 1491448085.0, "percent_earned": 6.430066349022577,
+        "id": "crypto-panda", "times_awarded": 2888, "name": "Crypto Panda"}, {"description":
+        "Successfully completed 20 copr builds", "tags": "development,copr,", "issued":
+        1408587109.0, "image": "https://badges.fedoraproject.org/pngs/copr-build-20.png",
+        "first_awarded": 1389199520.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "shosca", "last_awarded": 1491505320.0, "percent_earned": 2.3711982900654585,
+        "id": "building-the-outer-ring-copr-build-ii", "times_awarded": 1065, "name":
+        "Building the Outer Ring (Copr Build II)"}, {"description": "Pushed 1000 bodhi
+        updates to the testing repositories.", "tags": "development,", "issued": 1409321568.0,
+        "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-05.png",
+        "first_awarded": 1398006872.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "pghmcfc", "last_awarded": 1490811747.0, "percent_earned": 0.03562363628267355,
+        "id": "mic-check!-updates-testing-v", "times_awarded": 16, "name": "Mic Check!
+        (Updates-Testing V)"}, {"description": "You took ownership of a previously
+        orphaned package.", "tags": "development,ownership,", "issued": 1418228616.0,
+        "image": "https://badges.fedoraproject.org/pngs/pkgdb-the-cat-came-back.png",
+        "first_awarded": 1381330499.0, "first_awarded_person": "tmraz", "last_awarded_person":
+        "sailer", "last_awarded": 1490344262.0, "percent_earned": 0.7525493164714788,
+        "id": "the-cat-came-back...", "times_awarded": 338, "name": "The cat came
+        back..."}, {"description": "You designed a Fedora Badge! Welcome to Florence,
+        apprentice badge artist.", "tags": "content,design,badges", "issued": 1420755671.0,
+        "image": "https://badges.fedoraproject.org/pngs/artist-01-apprentice.png",
+        "first_awarded": 1420755606.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "michalrud", "last_awarded": 1470317213.0, "percent_earned": 0.04675602262100904,
+        "id": "apprentice-badge-artist-i", "times_awarded": 21, "name": "Apprentice
+        (Badge Artist I)"}, {"description": "You have received 100 or more good karma
+        on your bodhi updates", "tags": "development,karma,", "issued": 1421316923.0,
+        "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-100.png",
+        "first_awarded": 1396803342.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "kkeithle", "last_awarded": 1491364811.0, "percent_earned": 0.20706238589304005,
+        "id": "what-goes-around-comes-around-karma-iv", "times_awarded": 93, "name":
+        "What goes around comes around (Karma IV)"}, {"description": "You participated
+        in the Design Team FAD, 2015", "tags": "event,fad,design,", "issued": 1421420485.0,
+        "image": "https://badges.fedoraproject.org/pngs/fad_design.png", "first_awarded":
+        1421419806.0, "first_awarded_person": "chrisroberts", "last_awarded_person":
+        "rbergero", "last_awarded": 1421432734.0, "percent_earned": 0.04007659081800775,
+        "id": "design-ninja", "times_awarded": 18, "name": "Design Ninja"}, {"description":
+        "You gave a presentation at Flock 2015, the Fedora Contributor Conference",
+        "tags": "event,flock,speaker,", "issued": 1439502241.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-speaker.png",
+        "first_awarded": 1439502092.0, "first_awarded_person": "spot", "last_awarded_person":
+        "jzeleny", "last_awarded": 1439503832.0, "percent_earned": 0.13804159059536003,
+        "id": "flock-2015-speaker", "times_awarded": 62, "name": "Flock 2015 Speaker"},
+        {"description": "Pushed 1000 bodhi updates to the stable repositories.", "tags":
+        "development,", "issued": 1423235515.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-05.png",
+        "first_awarded": 1413266082.0, "first_awarded_person": "remi", "last_awarded_person":
+        "orion", "last_awarded": 1490756246.0, "percent_earned": 0.024491249944338066,
+        "id": "tectonic!-updates-stable-v", "times_awarded": 11, "name": "Tectonic!
+        (Updates-Stable V)"}, {"description": "You got 25 or more Fedora Badges",
+        "tags": "community,badges,", "issued": 1423235523.0, "image": "https://badges.fedoraproject.org/pngs/badger-01.5.png",
+        "first_awarded": 1421694162.0, "first_awarded_person": "rhughes", "last_awarded_person":
+        "pcreech17", "last_awarded": 1491432940.0, "percent_earned": 2.3756512446007925,
+        "id": "associate-badger-badger-1.5", "times_awarded": 1067, "name": "Associate
+        Badger (Badger 1.5)"}, {"description": "You attended Flock 2015, the Fedora
+        Contributor Conference", "tags": "conference,rochester,events,flock,flock
+        2015,event,", "issued": 1439318156.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-attendee.png",
+        "first_awarded": 1438119680.0, "first_awarded_person": "decause", "last_awarded_person":
+        "kellin", "last_awarded": 1461547496.0, "percent_earned": 0.18479761321636906,
+        "id": "flock-2015-attendee", "times_awarded": 83, "name": "Flock 2015 Attendee"},
+        {"description": "You''ve been given a cookie for helping someone out.  Thanks!",
+        "tags": "cookie,community,", "issued": 1426702546.0, "image": "https://badges.fedoraproject.org/pngs/macaroncookie.png",
+        "first_awarded": 1426702242.0, "first_awarded_person": "riecatnor", "last_awarded_person":
+        "gtierney", "last_awarded": 1491405719.0, "percent_earned": 1.852429086699025,
+        "id": "macaron-cookie-i", "times_awarded": 832, "name": "Macaron (Cookie I)"},
+        {"description": "Ten people think you''re great, because they''ve given you
+        cookies. Yummy!", "tags": "cookie,community,", "issued": 1426796321.0, "image":
+        "https://badges.fedoraproject.org/pngs/chocolatechipcookie.png", "first_awarded":
+        1426796321.0, "first_awarded_person": "ralph", "last_awarded_person": "mizdebsk",
+        "last_awarded": 1491500198.0, "percent_earned": 0.3606893173620697, "id":
+        "chocolate-chip-cookie-ii", "times_awarded": 162, "name": "Chocolate Chip
+        (Cookie II)"}, {"description": "Voted in the supplemental wallpapers election
+        for the Fedora 22 release", "tags": "election,community,nuancier,", "issued":
+        1426883250.0, "image": "https://badges.fedoraproject.org/pngs/nuancier-f22-voted.png",
+        "first_awarded": 1426835617.0, "first_awarded_person": "gnokii", "last_awarded_person":
+        "sarupbanskota", "last_awarded": 1427224635.0, "percent_earned": 0.18257113594870197,
+        "id": "nuancier-f22", "times_awarded": 82, "name": "Nuancier (F22)"}, {"description":
+        "You pinned your country in FAS!", "tags": "account,community,", "issued":
+        1427991763.0, "image": "https://badges.fedoraproject.org/pngs/fas-country.png",
+        "first_awarded": 1427832337.0, "first_awarded_person": "aeperezt", "last_awarded_person":
+        "schwankek", "last_awarded": 1491486520.0, "percent_earned": 17.635926437191078,
+        "id": "origin", "times_awarded": 7921, "name": "Origin"}, {"description":
+        "You added a new package to the distribution.", "tags": "development,", "issued":
+        1377267893.0, "image": "https://badges.fedoraproject.org/pngs/delivery.png",
+        "first_awarded": 1376569056.0, "first_awarded_person": "besser82", "last_awarded_person":
+        "nmilosev", "last_awarded": 1491213323.0, "percent_earned": 0.919535111546511,
+        "id": "delivery", "times_awarded": 413, "name": "Delivery"}, {"description":
+        "You mapped an upstream project to a Fedora package on release-monitoring.org",
+        "tags": "quality,anitya,", "issued": 1430855052.0, "image": "https://badges.fedoraproject.org/pngs/telegraph.png",
+        "first_awarded": 1430855052.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dustymabe", "last_awarded": 1475240820.0, "percent_earned": 0.5655252259874427,
+        "id": "telegraphist-upstream-release-monitoring-i", "times_awarded": 254,
+        "name": "Telegraphist (Upstream Release Monitoring I)"}, {"description": "Mmmmm....
+        cookies. You''ve helped out 25 Fedorans! Go you!", "tags": "cookies,cookie,community,",
+        "issued": 1430923797.0, "image": "https://badges.fedoraproject.org/pngs/blackandwhitecookie.png",
+        "first_awarded": 1430923797.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dmsimard", "last_awarded": 1491321510.0, "percent_earned": 0.14472102239836132,
+        "id": "black-and-white-cookie-iii", "times_awarded": 65, "name": "Black and
+        White (Cookie III)"}, {"description": "Submitted 250 or more koji builds that
+        failed to build.", "tags": "development,oops,koji,", "issued": 1431713399.0,
+        "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-05.png",
+        "first_awarded": 1378300711.0, "first_awarded_person": "ppisar", "last_awarded_person":
+        "smani", "last_awarded": 1485976444.0, "percent_earned": 0.08460613617134968,
+        "id": "hindenburg-koji-failure-v", "times_awarded": 38, "name": "Hindenburg
+        (Koji Failure V)"}, {"description": "Twenty Fedora Badges owe their existence
+        to you.  Prolific!", "tags": "community,badges,", "issued": 1433267989.0,
+        "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_20.png",
+        "first_awarded": 1433267989.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jflory7", "last_awarded": 1475509860.0, "percent_earned": 0.006679431803001291,
+        "id": "badge-muse-badges-released-iv", "times_awarded": 3, "name": "Badge
+        Muse (Badges Released IV)"}, {"description": "Five of your new Fedora Badge
+        ideas were approved and released.  Superb!", "tags": "community,badges,",
+        "issued": 1433268002.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_5.png",
+        "first_awarded": 1423674135.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "pravins", "last_awarded": 1472553692.0, "percent_earned": 0.03562363628267355,
+        "id": "badge-muse-badges-released-ii", "times_awarded": 16, "name": "Badge
+        Muse (Badges Released II)"}, {"description": "Your idea for a new Fedora Badge
+        was approved and released.  Awesome!", "tags": "community,badges,", "issued":
+        1433268014.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved.png",
+        "first_awarded": 1423674153.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "frantisekz", "last_awarded": 1485462295.0, "percent_earned": 0.10019147704501936,
+        "id": "badge-muse-badges-released-i", "times_awarded": 45, "name": "Badge
+        Muse (Badges Released I)"}, {"description": "Ten of your new Fedora Badge
+        ideas were approved and released.  Impressive!", "tags": "community,badges,",
+        "issued": 1433267996.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_10.png",
+        "first_awarded": 1424466072.0, "first_awarded_person": "gnokii", "last_awarded_person":
+        "jflory7", "last_awarded": 1475509873.0, "percent_earned": 0.013358863606002582,
+        "id": "badge-muse-badges-released-iii", "times_awarded": 6, "name": "Badge
+        Muse (Badges Released III)"}, {"description": "Each unique like a snowflake,
+        fifty of your Fedora Badge ideas were implemented.", "tags": "community,badges,",
+        "issued": 1433268008.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_50.png",
+        "first_awarded": 1433268008.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ralph", "last_awarded": 1433268008.0, "percent_earned": 0.002226477267667097,
+        "id": "badge-muse-badges-released-v", "times_awarded": 1, "name": "Badge Muse
+        (Badges Released V)"}, {"description": "Beep boop!  You attended the Museum
+        of Play party at the Flock 2015 conference in Rochester!", "tags": "event,flock,",
+        "issued": 1440122051.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-party.png",
+        "first_awarded": 1439510783.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "kellin", "last_awarded": 1461547520.0, "percent_earned": 0.16921227234269937,
+        "id": "nom-nom-nom", "times_awarded": 76, "name": "Nom Nom Nom"}, {"description":
+        "Successfully completed 60 copr builds", "tags": "development,copr,", "issued":
+        1441220012.0, "image": "https://badges.fedoraproject.org/pngs/copr-build-60.png",
+        "first_awarded": 1389199513.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "rluzynski", "last_awarded": 1491442095.0, "percent_earned": 1.5763459055083047,
+        "id": "building-the-outer-ring-copr-build-iii", "times_awarded": 708, "name":
+        "Building the Outer Ring (Copr Build III)"}, {"description": "You maintain
+        the Fedora websites.  The Web is yours!", "tags": "web,community,", "issued":
+        1442348476.0, "image": "https://badges.fedoraproject.org/pngs/rock-the-web.png",
+        "first_awarded": 1393598200.0, "first_awarded_person": "sundaram", "last_awarded_person":
+        "dustymabe", "last_awarded": 1491326903.0, "percent_earned": 0.12468272698935744,
+        "id": "rock-the-web!", "times_awarded": 56, "name": "Rock the Web!"}, {"description":
+        "You can speak Python and and you''ve ported at least one package to Python
+        3", "tags": "python,miscellaneous,", "issued": 1447943105.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth.png",
+        "first_awarded": 1438695566.0, "first_awarded_person": "rkuska", "last_awarded_person":
+        "crobinso", "last_awarded": 1491217608.0, "percent_earned": 0.33842454468539873,
+        "id": "parselmouth", "times_awarded": 152, "name": "Parselmouth I"}, {"description":
+        "Edited at least 250 wiki pages.", "tags": "content,wiki,", "issued": 1444762324.0,
+        "image": "https://badges.fedoraproject.org/pngs/wikiedit-250.png", "first_awarded":
+        1409692963.0, "first_awarded_person": "hhorak", "last_awarded_person": "jonatoni",
+        "last_awarded": 1490211890.0, "percent_earned": 0.22710068130204392, "id":
+        "chief-editor", "times_awarded": 102, "name": "Chief Editor"}, {"description":
+        "You''ve had a FAS account for over seven years", "tags": "lifecycle,community,",
+        "issued": 1446001652.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-froglet.png",
+        "first_awarded": 1389060935.0, "first_awarded_person": "jwboyer", "last_awarded_person":
+        "apahim", "last_awarded": 1491360865.0, "percent_earned": 4.628846239479895,
+        "id": "froglet", "times_awarded": 2079, "name": "Froglet"}, {"description":
+        "50 Fedorans have recognized your achievements by giving you cookies.", "tags":
+        "cookie,community,", "issued": 1447693727.0, "image": "https://badges.fedoraproject.org/pngs/pizzellecookie.png",
+        "first_awarded": 1447693727.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ryanlerch", "last_awarded": 1490719235.0, "percent_earned": 0.03117068174733936,
+        "id": "pizzelle-cookie-iv", "times_awarded": 14, "name": "Pizzelle (Cookie
+        IV)"}, {"description": "You held second place on the Fedora Badges leaderboard",
+        "tags": "meta,community,", "issued": 1452873723.0, "image": "https://badges.fedoraproject.org/pngs/2_badge_340.png",
+        "first_awarded": 1452873723.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "churchyard", "last_awarded": 1475564266.0, "percent_earned": 0.011132386338335486,
+        "id": "silver", "times_awarded": 5, "name": "Silver"}, {"description": "You
+        ranked among the top 10 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875167.0, "image": "https://badges.fedoraproject.org/pngs/10_badge_340.png",
+        "first_awarded": 1452875035.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "fale", "last_awarded": 1489622707.0, "percent_earned": 0.03117068174733936,
+        "id": "top-10", "times_awarded": 14, "name": "Top 10"}, {"description": "You
+        ranked among the top 100 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875191.0, "image": "https://badges.fedoraproject.org/pngs/100_badge_340.png",
+        "first_awarded": 1452875133.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "tflink", "last_awarded": 1480461776.0, "percent_earned": 0.25159193124638196,
+        "id": "top-100", "times_awarded": 113, "name": "Top 100"}, {"description":
+        "You ranked among the top 25 contributors on the Fedora Badges leaderboard",
+        "tags": "meta,community,", "issued": 1452875198.0, "image": "https://badges.fedoraproject.org/pngs/25_badge_340.png",
+        "first_awarded": 1452875140.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "mrunge", "last_awarded": 1490730635.0, "percent_earned": 0.06679431803001291,
+        "id": "top-25", "times_awarded": 30, "name": "Top 25"}, {"description": "You
+        ranked among the top 500 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875206.0, "image": "https://badges.fedoraproject.org/pngs/500_badge_340.png",
+        "first_awarded": 1452875147.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "zsun", "last_awarded": 1490677027.0, "percent_earned": 1.2000712472725654,
+        "id": "top-500", "times_awarded": 539, "name": "Top 500"}, {"description":
+        "You visited the Fedora table at BrickHack 2016!", "tags": "brickhack,event,hackathon,events,",
+        "issued": 1457234130.0, "image": "http://infrastructure.fedoraproject.org/infra/badges/pngs/events-brickhack-2016.png",
+        "first_awarded": 1455845197.0, "first_awarded_person": "jflory7", "last_awarded_person":
+        "brandondgr8", "last_awarded": 1461299769.0, "percent_earned": 0.05566193169167742,
+        "id": "brickhack-2016-attendee", "times_awarded": 25, "name": "BrickHack 2016
+        Attendee"}, {"description": "You gave a presentation at DevConf.cz, 2016",
+        "tags": "devconf,speaker,event,", "issued": 1455008224.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter-2016.png",
+        "first_awarded": 1454667336.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "thozza", "last_awarded": 1459500320.0, "percent_earned": 0.2137418176960413,
+        "id": "devconf-2016-speaker", "times_awarded": 96, "name": "DevConf 2016 Speaker"},
+        {"description": "You attended the 2016 iteration of DevConf.cz, a yearly Linux
+        and JBoss conference in the Czech Republic", "tags": "event, devconf,", "issued":
+        1455120228.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-attendee-2016.png",
+        "first_awarded": 1454600601.0, "first_awarded_person": "eischmann", "last_awarded_person":
+        "dkaspar", "last_awarded": 1461940547.0, "percent_earned": 0.18925056775170326,
+        "id": "devconf-2016-attendee", "times_awarded": 85, "name": "DevConf 2016
+        Attendee"}, {"description": "You got a \"Change\" accepted into the Fedora
+        24 Change list", "tags": "development,FESCo,process,", "issued": 1458652673.0,
+        "image": "https://badges.fedoraproject.org/pngs/changes-f24.png", "first_awarded":
+        1458651465.0, "first_awarded_person": "eischmann", "last_awarded_person":
+        "pnemade", "last_awarded": 1459240914.0, "percent_earned": 0.10019147704501936,
+        "id": "fedora-24-change-accepted", "times_awarded": 45, "name": "Fedora 24
+        Change Accepted"}, {"description": "You held third place on the Fedora Badges
+        leaderboard", "tags": "meta,community,", "issued": 1462904993.0, "image":
+        "https://badges.fedoraproject.org/pngs/3_badge_340.png", "first_awarded":
+        1452875229.0, "first_awarded_person": "pnemade", "last_awarded_person": "kevin",
+        "last_awarded": 1489692901.0, "percent_earned": 0.011132386338335486, "id":
+        "bronze", "times_awarded": 5, "name": "Bronze"}, {"description": "You burned
+        a yubikey and linked it with your Fedora Account", "tags": "account,community,",
+        "issued": 1462904755.0, "image": "https://badges.fedoraproject.org/pngs/yubi-badge.png",
+        "first_awarded": 1422906928.0, "first_awarded_person": "plautrba", "last_awarded_person":
+        "findarato", "last_awarded": 1491408410.0, "percent_earned": 0.4052188627154117,
+        "id": "horcrux", "times_awarded": 182, "name": "Horcrux"}, {"description":
+        "You hacked with the Fedora crew at PyCon US 2016", "tags": "event, python,pycon,",
+        "issued": 1464988862.0, "image": "https://badges.fedoraproject.org/pngs/pycon_2016_attendee.png",
+        "first_awarded": 1464988862.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "maxamillion", "last_awarded": 1464991242.0, "percent_earned": 0.028944204479672265,
+        "id": "pycon-2016-attendee", "times_awarded": 13, "name": "PyCon 2016 Attendee"},
+        {"description": "You got 150 Fedora Badges!  Wow!", "tags": "badges,community,",
+        "issued": 1468058613.0, "image": "https://badges.fedoraproject.org/pngs/badger-04.png",
+        "first_awarded": 1457979017.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "pbrobinson", "last_awarded": 1490919191.0, "percent_earned": 0.020038295409003874,
+        "id": "badge-junkie-badger-iv", "times_awarded": 9, "name": "Badge Junkie
+        (Badger IV)"}, {"description": "You can speak Python and and you''ve ported
+        at least five packages to Python 3", "tags": "python,miscellaneous,", "issued":
+        1468058067.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth-II.png",
+        "first_awarded": 1467988651.0, "first_awarded_person": "cstratak", "last_awarded_person":
+        "ishcherb", "last_awarded": 1488193805.0, "percent_earned": 0.028944204479672265,
+        "id": "parselmouth-ii", "times_awarded": 13, "name": "Parselmouth II"}, {"description":
+        "You can speak Python and and you''ve ported at least ten packages to Python
+        3", "tags": "python,miscellaneous,", "issued": 1468058072.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth-III.png",
+        "first_awarded": 1467997850.0, "first_awarded_person": "cstratak", "last_awarded_person":
+        "ishcherb", "last_awarded": 1489753361.0, "percent_earned": 0.02226477267667097,
+        "id": "parselmouth-iii", "times_awarded": 10, "name": "Parselmouth III"},
+        {"description": "You attended Flock 2016, the Fedora Contributor Conference",
+        "tags": "flock,event,events,", "issued": 1470404480.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/flock-2016-attendee.png",
+        "first_awarded": 1468984748.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "adrian", "last_awarded": 1486483875.0, "percent_earned": 0.2360065903727123,
+        "id": "flock-2016-attendee", "times_awarded": 106, "name": "Flock 2016 Attendee"},
+        {"description": "You attended the party at Flock 2016 in Krakow!", "tags":
+        "flock,krakow,event,events,", "issued": 1470404592.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/krakow-brews.png",
+        "first_awarded": 1468963417.0, "first_awarded_person": "duffy", "last_awarded_person":
+        "kittykat", "last_awarded": 1486217490.0, "percent_earned": 0.18257113594870197,
+        "id": "krakow-brews!", "times_awarded": 82, "name": "Krakow Brews!"}, {"description":
+        "You gave a presentation at Flock 2016, the Fedora Contributor Conference",
+        "tags": "flock,speaker,event,", "issued": 1471354751.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/flock-2016-speaker.png",
+        "first_awarded": 1470941077.0, "first_awarded_person": "mattdm", "last_awarded_person":
+        "kittykat", "last_awarded": 1486217546.0, "percent_earned": 0.19592999955470453,
+        "id": "flock-2016-speaker", "times_awarded": 88, "name": "Flock 2016 Speaker"},
+        {"description": "You''re a member of the Modularity Working Group!", "tags":
+        "modularity,community,", "issued": 1475082397.0, "image": "https://badges.fedoraproject.org/pngs/fas-modularity-wg.png",
+        "first_awarded": 1475082098.0, "first_awarded_person": "asamalik", "last_awarded_person":
+        "jscotka", "last_awarded": 1491213584.0, "percent_earned": 0.0801531816360155,
+        "id": "modularity-wg-member", "times_awarded": 36, "name": "Modularity WG
+        Member"}, {"description": "You got a \"Change\" accepted into the Fedora 26
+        Change list", "tags": "development, FESCo, process,", "issued": 1479749896.0,
+        "image": "https://badges.fedoraproject.org/pngs/changes-f26.png", "first_awarded":
+        1473331018.0, "first_awarded_person": "cstratak", "last_awarded_person": "ncoghlan",
+        "last_awarded": 1489656918.0, "percent_earned": 0.11577681791868906, "id":
+        "fedora-26-change-accepted", "times_awarded": 52, "name": "Fedora 26 Change
+        Accepted"}, {"description": "You gave a presentation at DevConf.cz, 2017",
+        "tags": "speaker,devconf,event,", "issued": 1486037155.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter-2017.png",
+        "first_awarded": 1486033923.0, "first_awarded_person": "jflory7", "last_awarded_person":
+        "siddharths", "last_awarded": 1486077040.0, "percent_earned": 0.24713897671104776,
+        "id": "devconf-2017-speaker", "times_awarded": 111, "name": "DevConf 2017
+        Speaker"}, {"description": "You have helped so many Fedorans you''ve got over
+        100 cookies. Don''t eat them all at once!", "tags": "cookie,community,", "issued":
+        1486153659.0, "image": "https://badges.fedoraproject.org/pngs/rainbowcookie.png",
+        "first_awarded": 1468278719.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "ralph", "last_awarded": 1486153659.0, "percent_earned": 0.011132386338335486,
+        "id": "rainbow-cookie-v", "times_awarded": 5, "name": "Rainbow (Cookie V)"}],
+        "percent_earned": 41.81818181818181, "user": "ralph"}'}
+    headers:
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=45356299]
+      connection: [Keep-Alive]
+      content-length: ['74203']
+      content-type: [application/json; charset=UTF-8]
+      date: ['Thu, 06 Apr 2017 22:32:50 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://badges.fedoraproject.org/user/ralph/json
+  response:
+    body: {string: !!python/unicode '{"user_count": 44887, "rank": 5, "avatar": "https://seccdn.libravatar.org/avatar/9c9f7784935381befc302fe3c814f9136e7a33953d0318761669b8643f4df55c?s=100&d=https%3A%2F%2Fbadges.fedoraproject.org%2Fstatic%2Fimg%2Fbadger_avatar.png",
+        "percentile": "0.01", "assertions": [{"description": "You attended Flock 2013,
+        the Fedora Contributor Conference", "tags": "flock,event,", "issued": 1375373082.0,
+        "image": "https://badges.fedoraproject.org/pngs/flock-2013-attendee.png",
+        "first_awarded": 1375373082.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "rbergero", "last_awarded": 1473972160.0, "percent_earned": 0.18034465868103486,
+        "id": "flock-2013-attendee", "times_awarded": 81, "name": "Flock 2013 Attendee"},
+        {"description": "Voted on 50 tags for packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1375450118.0, "image": "https://badges.fedoraproject.org/pngs/tagger-02.png",
+        "first_awarded": 1375450118.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "imabug", "last_awarded": 1490198996.0, "percent_earned": 0.663490225764795,
+        "id": "tagger-tagger-ii", "times_awarded": 298, "name": "Tagger (Tagger II)"},
+        {"description": "Voted on 10 tags for packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1375450119.0, "image": "https://badges.fedoraproject.org/pngs/tagger-01.png",
+        "first_awarded": 1375450119.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "bbroad", "last_awarded": 1491002037.0, "percent_earned": 1.0508972703388697,
+        "id": "junior-tagger-tagger-i", "times_awarded": 472, "name": "Junior Tagger
+        (Tagger I)"}, {"description": "You signed the Fedora Project Contributor Agreement
+        (FPCA)", "tags": "account,community,", "issued": 1375572218.0, "image": "https://badges.fedoraproject.org/pngs/involvement.png",
+        "first_awarded": 1375572176.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "ylarguex", "last_awarded": 1491516838.0, "percent_earned": 53.357527719641986,
+        "id": "involvement", "times_awarded": 23965, "name": "Involvement"}, {"description":
+        "Participated in an IRC meeting.", "tags": "irc,community,", "issued": 1375734057.0,
+        "image": "https://badges.fedoraproject.org/pngs/irc-speak-up.png", "first_awarded":
+        1375734057.0, "first_awarded_person": "ralph", "last_awarded_person": "icole",
+        "last_awarded": 1491505117.0, "percent_earned": 3.9564501046444316, "id":
+        "speak-up!", "times_awarded": 1777, "name": "Speak Up!"}, {"description":
+        "Edited at least 10 wiki pages.", "tags": "content,wiki,", "issued": 1375888314.0,
+        "image": "https://badges.fedoraproject.org/pngs/junior-editor.png", "first_awarded":
+        1375888314.0, "first_awarded_person": "ralph", "last_awarded_person": "kdudka",
+        "last_awarded": 1491484751.0, "percent_earned": 3.3129981742886407, "id":
+        "junior-editor", "times_awarded": 1488, "name": "Junior Editor"}, {"description":
+        "Edited at least 20 wiki pages.", "tags": "content,wiki,", "issued": 1375888315.0,
+        "image": "https://badges.fedoraproject.org/pngs/associate-editor.png", "first_awarded":
+        1375888315.0, "first_awarded_person": "ralph", "last_awarded_person": "ankit1997",
+        "last_awarded": 1491242577.0, "percent_earned": 2.108473972480741, "id": "associate-editor",
+        "times_awarded": 947, "name": "Associate Editor"}, {"description": "Chaired
+        an IRC meeting.", "tags": "irc,community,", "issued": 1375889493.0, "image":
+        "https://badges.fedoraproject.org/pngs/irc-secretary-general.png", "first_awarded":
+        1375889493.0, "first_awarded_person": "ralph", "last_awarded_person": "yzhang",
+        "last_awarded": 1491431124.0, "percent_earned": 1.5451752237609653, "id":
+        "secretary-general", "times_awarded": 694, "name": "Secretary General"}, {"description":
+        "Voted on 250 tags for packages in Fedora Tagger.", "tags": "quality,tagger,",
+        "issued": 1375898650.0, "image": "https://badges.fedoraproject.org/pngs/tagger-03.png",
+        "first_awarded": 1375898650.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "imabug", "last_awarded": 1490198974.0, "percent_earned": 0.3139332947410607,
+        "id": "senior-tagger-tagger-iii", "times_awarded": 141, "name": "Senior Tagger
+        (Tagger III)"}, {"description": "Succesfully completed 250 koji builds.",
+        "tags": "development,koji,", "issued": 1377742978.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-04.png",
+        "first_awarded": 1377711940.0, "first_awarded_person": "remi", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491425650.0, "percent_earned": 0.5454869305784388,
+        "id": "if-you-build-it...-koji-success-iv", "times_awarded": 245, "name":
+        "If you build it... (Koji Success IV)"}, {"description": "Succesfully completed
+        a koji build.", "tags": "development,koji,", "issued": 1377742982.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-01.png", "first_awarded":
+        1377708898.0, "first_awarded_person": "mcpierce", "last_awarded_person": "nickboldt",
+        "last_awarded": 1490915696.0, "percent_earned": 2.858796811684553, "id": "if-you-build-it...-koji-success-i",
+        "times_awarded": 1284, "name": "If you build it... (Koji Success I)"}, {"description":
+        "Succesfully completed 10 koji builds.", "tags": "development,koji,", "issued":
+        1377742977.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-02.png",
+        "first_awarded": 1377708893.0, "first_awarded_person": "mcpierce", "last_awarded_person":
+        "sjenning", "last_awarded": 1491017600.0, "percent_earned": 2.3288952219797836,
+        "id": "if-you-build-it...-koji-success-ii", "times_awarded": 1046, "name":
+        "If you build it... (Koji Success II)"}, {"description": "Succesfully completed
+        50 koji builds.", "tags": "development,koji,", "issued": 1377742981.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-03.png", "first_awarded":
+        1377708896.0, "first_awarded_person": "mcpierce", "last_awarded_person": "tartare",
+        "last_awarded": 1491481451.0, "percent_earned": 1.4138130649686065, "id":
+        "if-you-build-it...-koji-success-iii", "times_awarded": 635, "name": "If you
+        build it... (Koji Success III)"}, {"description": "Ten or more questions and
+        answers on the Ask Fedora system.", "tags": "ask fedora,community,", "issued":
+        1377794097.0, "image": "https://badges.fedoraproject.org/pngs/askbot-curious-penguin-02.png",
+        "first_awarded": 1375889645.0, "first_awarded_person": "hmaarrfk", "last_awarded_person":
+        "fedo25", "last_awarded": 1491440137.0, "percent_earned": 1.4182660195039407,
+        "id": "curious-penguin-ask-fedora-ii", "times_awarded": 637, "name": "Curious
+        Penguin (Ask Fedora II)"}, {"description": "You had your first interaction
+        with the Ask Fedora forum system.", "tags": "ask fedora,community,", "issued":
+        1377794096.0, "image": "https://badges.fedoraproject.org/pngs/askbot-curious-penguin-01.png",
+        "first_awarded": 1375889644.0, "first_awarded_person": "hmaarrfk", "last_awarded_person":
+        "andre", "last_awarded": 1491511008.0, "percent_earned": 9.738611568775884,
+        "id": "curious-penguin-ask-fedora-i", "times_awarded": 4374, "name": "Curious
+        Penguin (Ask Fedora I)"}, {"description": "You updated your Security Question
+        with the Fedora Account System (FAS)", "tags": "account,community,", "issued":
+        1375988994.0, "image": "https://badges.fedoraproject.org/pngs/fas-riddle-me-this.png",
+        "first_awarded": 1375988994.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dtemirgaziyev", "last_awarded": 1491482061.0, "percent_earned": 1.903638063855368,
+        "id": "riddle-me-this", "times_awarded": 855, "name": "Riddle Me This"}, {"description":
+        "Pushed 30 commits to a Fedora package repository.", "tags": "development,",
+        "issued": 1376076329.0, "image": "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-02.png",
+        "first_awarded": 1375887918.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "mhonek", "last_awarded": 1490899614.0, "percent_earned": 2.0127354499710557,
+        "id": "you-can-call-me-patches-scm-ii", "times_awarded": 904, "name": "You
+        can call me \"Patches\" (SCM II)"}, {"description": "Submitted a koji build
+        that failed to build.", "tags": "development,oops,koji,", "issued": 1377887821.0,
+        "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-01.png",
+        "first_awarded": 1377711403.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "nickboldt", "last_awarded": 1491244448.0, "percent_earned": 1.94816760920871,
+        "id": "what-goes-up...-koji-failure-i", "times_awarded": 875, "name": "What
+        goes up... (Koji Failure I)"}, {"description": "Pushed 5 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076328.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-01.png", "first_awarded":
+        1375887918.0, "first_awarded_person": "jforbes", "last_awarded_person": "jscotka",
+        "last_awarded": 1491289110.0, "percent_earned": 2.6695462439328494, "id":
+        "you-can-call-me-patches-scm-i", "times_awarded": 1199, "name": "You can call
+        me \"Patches\" (SCM I)"}, {"description": "Pushed 400 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076329.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-04.png", "first_awarded":
+        1375892509.0, "first_awarded_person": "rdieter", "last_awarded_person": "berrange",
+        "last_awarded": 1491242273.0, "percent_earned": 0.5588457941844414, "id":
+        "you-can-call-me-patches-scm-iv", "times_awarded": 251, "name": "You can call
+        me \"Patches\" (SCM IV)"}, {"description": "Pushed 90 commits to a Fedora
+        package repository.", "tags": "development,", "issued": 1376076329.0, "image":
+        "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-03.png", "first_awarded":
+        1375889053.0, "first_awarded_person": "mhlavink", "last_awarded_person": "cydrobolt",
+        "last_awarded": 1490744785.0, "percent_earned": 1.3603776105445964, "id":
+        "you-can-call-me-patches-scm-iii", "times_awarded": 611, "name": "You can
+        call me \"Patches\" (SCM III)"}, {"description": "Submitted 20 or more koji
+        builds that failed to build.", "tags": "development,oops,koji,", "issued":
+        1377887820.0, "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-03.png",
+        "first_awarded": 1377711401.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491441515.0, "percent_earned": 0.6523578394264594,
+        "id": "what-goes-up...-koji-failure-iii", "times_awarded": 293, "name": "What
+        goes up... (Koji Failure III)"}, {"description": "Submitted 5 or more koji
+        builds that failed to build.", "tags": "development,oops,koji,", "issued":
+        1377887822.0, "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-02.png",
+        "first_awarded": 1377711406.0, "first_awarded_person": "bpepple", "last_awarded_person":
+        "tkrizek", "last_awarded": 1490376511.0, "percent_earned": 1.329206928797257,
+        "id": "what-goes-up...-koji-failure-ii", "times_awarded": 597, "name": "What
+        goes up... (Koji Failure II)"}, {"description": "Pushed 100 bodhi updates
+        to the testing repositories.", "tags": "development", "issued": 1376077757.0,
+        "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-03.png",
+        "first_awarded": 1375892382.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "jhogarth", "last_awarded": 1491441872.0, "percent_earned": 0.5299015897047691,
+        "id": "is-this-thing-on-updates-testing-iii", "times_awarded": 238, "name":
+        "Is this thing on? (Updates-Testing III)"}, {"description": "*(a path to follow)*\nTested
+        and added karma to 5 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1376171387.0, "image": "https://badges.fedoraproject.org/pngs/tester-02.png",
+        "first_awarded": 1375889658.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "toshio", "last_awarded": 1491516269.0, "percent_earned": 2.70739635748319,
+        "id": "discovery-of-the-footprints-tester-ii", "times_awarded": 1216, "name":
+        "Discovery of the Footprints (Tester II)"}, {"description": "You got your
+        first 10 Fedora Badges", "tags": "community,badges,", "issued": 1376171392.0,
+        "image": "https://badges.fedoraproject.org/pngs/badger-01.png", "first_awarded":
+        1376118882.0, "first_awarded_person": "ignatenkobrain", "last_awarded_person":
+        "apollo7", "last_awarded": 1491362331.0, "percent_earned": 7.547757937391459,
+        "id": "junior-badger-badger-i", "times_awarded": 3390, "name": "Junior Badger
+        (Badger I)"}, {"description": "You pushed your first bodhi update to the stable
+        repositories.", "tags": "development,", "issued": 1376171906.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-01.png",
+        "first_awarded": 1375891204.0, "first_awarded_person": "blakegardner", "last_awarded_person":
+        "scox", "last_awarded": 1491419635.0, "percent_earned": 2.562675335084829,
+        "id": "like-a-rock-updates-stable-i", "times_awarded": 1151, "name": "Like
+        a Rock (Updates-Stable I)"}, {"description": "*(but only its rear, not its
+        head)*\nTested and added karma to 10 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1376171388.0, "image": "https://badges.fedoraproject.org/pngs/tester-03.png",
+        "first_awarded": 1375889660.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "alciregi", "last_awarded": 1491294835.0, "percent_earned": 1.6631785189473214,
+        "id": "perceiving-the-bull-tester-iii", "times_awarded": 747, "name": "Perceiving
+        the Bull (Tester III)"}, {"description": "Pushed 100 bodhi updates to the
+        stable repositories.", "tags": "development,", "issued": 1376171907.0, "image":
+        "https://badges.fedoraproject.org/pngs/like-a-rock-03.png", "first_awarded":
+        1375930621.0, "first_awarded_person": "cicku", "last_awarded_person": "mystro256",
+        "last_awarded": 1491340270.0, "percent_earned": 0.39408647637707617, "id":
+        "like-a-rock-updates-stable-iii", "times_awarded": 177, "name": "Like a Rock
+        (Updates-Stable III)"}, {"description": "*(aimless searching, only the sound
+        of cicadas)*\nTested and added karma to 1 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1376171388.0, "image": "https://badges.fedoraproject.org/pngs/tester-01.png",
+        "first_awarded": 1375889660.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "landgraf", "last_awarded": 1491498009.0, "percent_earned": 6.412254530881239,
+        "id": "in-search-of-the-bull-tester-i", "times_awarded": 2880, "name": "In
+        Search of the Bull (Tester I)"}, {"description": "Pushed 20 bodhi updates
+        to the stable repositories.", "tags": "development,", "issued": 1376171906.0,
+        "image": "https://badges.fedoraproject.org/pngs/like-a-rock-02.png", "first_awarded":
+        1375893154.0, "first_awarded_person": "pingou", "last_awarded_person": "cqi",
+        "last_awarded": 1491420777.0, "percent_earned": 1.2379213608229058, "id":
+        "like-a-rock-updates-stable-ii", "times_awarded": 556, "name": "Like a Rock
+        (Updates-Stable II)"}, {"description": "Partied with Flock 2013 (at the South
+        Carolina Aquarium)", "tags": "party,flock,event,", "issued": 1376252202.0,
+        "image": "https://badges.fedoraproject.org/pngs/tanked.png", "first_awarded":
+        1376252202.0, "first_awarded_person": "ralph", "last_awarded_person": "gnokii",
+        "last_awarded": 1411656869.0, "percent_earned": 0.14026806786302712, "id":
+        "tanked!", "times_awarded": 63, "name": "Tanked!"}, {"description": "You proposed
+        a new idea for a Fedora Badge.  Thanks!", "tags": "community,badges,", "issued":
+        1376261121.0, "image": "https://badges.fedoraproject.org/pngs/badge-muse-01.png",
+        "first_awarded": 1376261121.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "frantisekz", "last_awarded": 1484316413.0, "percent_earned": 0.3161597720087278,
+        "id": "badge-muse-badge-ideas-i", "times_awarded": 142, "name": "Badge Muse
+        (Badge Ideas I)"}, {"description": "You got a new koji certificate!", "tags":
+        "account,community,", "issued": 1376347603.0, "image": "https://badges.fedoraproject.org/pngs/fas-bona-fide.png",
+        "first_awarded": 1375891140.0, "first_awarded_person": "ralphlange", "last_awarded_person":
+        "dtemirgaziyev", "last_awarded": 1491477255.0, "percent_earned": 5.508304760208398,
+        "id": "bona-fide", "times_awarded": 2474, "name": "Bona Fide"}, {"description":
+        " Submitted a Supplemental Wallpaper Idea for Fedora 20.", "tags": "content,design,nuancier,",
+        "issued": 1378218417.0, "image": "https://badges.fedoraproject.org/pngs/design-keepin-fedora-beautiful-f20.png",
+        "first_awarded": 1377269416.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "iwan14", "last_awarded": 1378218427.0, "percent_earned": 0.05788840895934453,
+        "id": "keepin-fedora-beautiful-f20", "times_awarded": 26, "name": "Keepin
+        Fedora Beautiful (F20)"}, {"description": "You got (about) 50 Fedora Badges.  Way
+        to go!", "tags": "community,badges,", "issued": 1378504718.0, "image": "https://badges.fedoraproject.org/pngs/badger-02.png",
+        "first_awarded": 1377470469.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "mailga", "last_awarded": 1491037049.0, "percent_earned": 0.823796589036826,
+        "id": "senior-badger-badger-ii", "times_awarded": 370, "name": "Senior Badger
+        (Badger II)"}, {"description": "You pushed your first bodhi update to the
+        testing repositories.", "tags": "development", "issued": 1376077757.0, "image":
+        "https://badges.fedoraproject.org/pngs/is-this-thing-on-01.png", "first_awarded":
+        1375889661.0, "first_awarded_person": "fantom", "last_awarded_person": "olem",
+        "last_awarded": 1491070078.0, "percent_earned": 2.658413857594514, "id": "is-this-thing-on-updates-testing-i",
+        "times_awarded": 1194, "name": "Is this thing on? (Updates-Testing I)"}, {"description":
+        "Pushed 20 bodhi updates to the testing repositories.", "tags": "development",
+        "issued": 1376077757.0, "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-02.png",
+        "first_awarded": 1375889878.0, "first_awarded_person": "stefw", "last_awarded_person":
+        "dustymabe", "last_awarded": 1491432891.0, "percent_earned": 1.467248519392617,
+        "id": "is-this-thing-on-updates-testing-ii", "times_awarded": 659, "name":
+        "Is this thing on? (Updates-Testing II)"}, {"description": "You added a co-maintainer
+        to a package. BFF!", "tags": "development,", "issued": 1376531564.0, "image":
+        "https://badges.fedoraproject.org/pngs/pkgdb-partners-in-crime.png", "first_awarded":
+        1376531564.0, "first_awarded_person": "ralph", "last_awarded_person": "dustymabe",
+        "last_awarded": 1491406327.0, "percent_earned": 1.714387496103665, "id": "partners-in-crime",
+        "times_awarded": 770, "name": "Partners in Crime"}, {"description": "*(a great
+        struggle, repeated escapes, discipline required)*\nTested and added karma
+        to 20 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1378251927.0, "image": "https://badges.fedoraproject.org/pngs/tester-04.png",
+        "first_awarded": 1375889657.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "mcepl", "last_awarded": 1491490795.0, "percent_earned": 0.961838179632186,
+        "id": "catching-the-bull-tester-iv", "times_awarded": 432, "name": "Catching
+        the Bull (Tester IV)"}, {"description": "Voted on 1000 tags for packages in
+        Fedora Tagger.", "tags": "quality,tagger,", "issued": 1378613794.0, "image":
+        "https://badges.fedoraproject.org/pngs/tagger-04.png", "first_awarded": 1376814408.0,
+        "first_awarded_person": "jamielinux", "last_awarded_person": "bt0dotninja",
+        "last_awarded": 1489638271.0, "percent_earned": 0.16253284053969808, "id":
+        "master-tagger-tagger-iv", "times_awarded": 73, "name": "Master Tagger (Tagger
+        IV)"}, {"description": "A member of the sysadmin-badges group.  Nothing to
+        see here..  move along.", "tags": "infrastructure,badges,community,", "issued":
+        1378319150.0, "image": "https://badges.fedoraproject.org/pngs/fas-inside-job.png",
+        "first_awarded": 1378319146.0, "first_awarded_person": "oddshocks", "last_awarded_person":
+        "jflory7", "last_awarded": 1472154166.0, "percent_earned": 0.02226477267667097,
+        "id": "inside-job", "times_awarded": 10, "name": "Inside Job"}, {"description":
+        "Indicated your timezone in FAS so everyone can sync with you", "tags": "account,community,",
+        "issued": 1378320160.0, "image": "https://badges.fedoraproject.org/pngs/fas-white-rabbit.png",
+        "first_awarded": 1378313171.0, "first_awarded_person": "churchyard", "last_awarded_person":
+        "salvadorhm", "last_awarded": 1491509400.0, "percent_earned": 8.144453845126241,
+        "id": "white-rabbit", "times_awarded": 3658, "name": "White Rabbit"}, {"description":
+        "You have a Libravatar associated with your Fedora OpenID.", "tags": "libravatar,community,",
+        "issued": 1376946138.0, "image": "https://badges.fedoraproject.org/pngs/mugshot.png",
+        "first_awarded": 1376946138.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jcholast", "last_awarded": 1491423845.0, "percent_earned": 3.061406243042258,
+        "id": "mugshot", "times_awarded": 1375, "name": "Mugshot"}, {"description":
+        "A member of sysadmin-main.", "tags": "development,infrastructure,", "issued":
+        1378320106.0, "image": "https://badges.fedoraproject.org/pngs/fas-trust-me-i-know-what-i-am-doing.png",
+        "first_awarded": 1378320079.0, "first_awarded_person": "lmacken", "last_awarded_person":
+        "pbrobinson", "last_awarded": 1430409877.0, "percent_earned": 0.03785011355034065,
+        "id": "trust-me,-i-know-what-i-am-doing", "times_awarded": 17, "name": "Trust
+        me, I know what I am doing"}, {"description": "Has used an ansible playbook
+        to deploy to Fedora Infrastructure", "tags": "development,infrastructure,",
+        "issued": 1378321037.0, "image": "https://badges.fedoraproject.org/pngs/ansible-maestro.png",
+        "first_awarded": 1378321037.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jkaluza", "last_awarded": 1490254934.0, "percent_earned": 0.10241795431268647,
+        "id": "maestro", "times_awarded": 46, "name": "Maestro"}, {"description":
+        "Created your own user profile page on the Fedora Wiki", "tags": "content,",
+        "issued": 1378321704.0, "image": "https://badges.fedoraproject.org/pngs/wiki-let-me-introduce-myself.png",
+        "first_awarded": 1378321704.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "cra", "last_awarded": 1491437628.0, "percent_earned": 4.250345103976488,
+        "id": "let-me-introduce-myself", "times_awarded": 1909, "name": "Let Me Introduce
+        Myself"}, {"description": "You submitted a bodhi update including multiple
+        packages.", "tags": "development,", "issued": 1377206257.0, "image": "https://badges.fedoraproject.org/pngs/bodhi-binary-star.png",
+        "first_awarded": 1376049444.0, "first_awarded_person": "jwrdegoede", "last_awarded_person":
+        "ngompa", "last_awarded": 1490659963.0, "percent_earned": 0.7614552255421472,
+        "id": "binary-star", "times_awarded": 342, "name": "Binary Star"}, {"description":
+        "Became a sponsor of new packagers.", "tags": "development,account,", "issued":
+        1377283223.0, "image": "https://badges.fedoraproject.org/pngs/fas-packager-sponsor.png",
+        "first_awarded": 1377283156.0, "first_awarded_person": "abompard", "last_awarded_person":
+        "xavierb", "last_awarded": 1484573828.0, "percent_earned": 0.32506568107939615,
+        "id": "packager-sponsor", "times_awarded": 146, "name": "Packager Sponsor"},
+        {"description": "You posted 5 or more things to the Fedora Planet!", "tags":
+        "blog,community,", "issued": 1377697248.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-02.png",
+        "first_awarded": 1375902038.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468491.0, "percent_earned": 1.3470187469385937,
+        "id": "bloggin-it!-planet-ii", "times_awarded": 605, "name": "Bloggin'' it!
+        (Planet II)"}, {"description": "You posted 10 or more things to the Fedora
+        Planet!", "tags": "blog,community,", "issued": 1377697247.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-03.png",
+        "first_awarded": 1375902037.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468509.0, "percent_earned": 1.1399563610455536,
+        "id": "bloggin-it!-planet-iii", "times_awarded": 512, "name": "Bloggin'' it!
+        (Planet III)"}, {"description": "You posted something to the Fedora Planet.",
+        "tags": "blog,community,", "issued": 1377697248.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-01.png",
+        "first_awarded": 1375902037.0, "first_awarded_person": "gomix", "last_awarded_person":
+        "stefw", "last_awarded": 1491468543.0, "percent_earned": 1.6231019281293138,
+        "id": "bloggin-it!-planet-i", "times_awarded": 729, "name": "Bloggin'' it!
+        (Planet I)"}, {"description": "You submitted a bodhi update with type \"security\"",
+        "tags": "development,security,", "issued": 1380316519.0, "image": "https://badges.fedoraproject.org/pngs/white-hat.png",
+        "first_awarded": 1379357860.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "dtimms", "last_awarded": 1491484910.0, "percent_earned": 0.939573406955515,
+        "id": "white-hat", "times_awarded": 422, "name": "White Hat"}, {"description":
+        "Voted in a supplemental wallpapers election for a Fedora release", "tags":
+        "election,community,nuancier,", "issued": 1380637092.0, "image": "https://badges.fedoraproject.org/pngs/nauncier.png",
+        "first_awarded": 1380617342.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "duffy", "last_awarded": 1381233461.0, "percent_earned": 0.17589170414570066,
+        "id": "nuancier", "times_awarded": 79, "name": "Nuancier"}, {"description":
+        "You''ve had a FAS account for over two years", "tags": "lifecycle,community,",
+        "issued": 1389056166.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-embryo.png",
+        "first_awarded": 1389043685.0, "first_awarded_person": "joev", "last_awarded_person":
+        "andrespol", "last_awarded": 1491360716.0, "percent_earned": 20.256490181235247,
+        "id": "embryo", "times_awarded": 9098, "name": "Embryo"}, {"description":
+        "*(less straying/discipline, bull becomes gentle/obedient)*\nTested and added
+        karma to 40 updates-testing updates in Bodhi.\n", "tags": "ten bulls,quality,",
+        "issued": 1381160713.0, "image": "https://badges.fedoraproject.org/pngs/tester-05.png",
+        "first_awarded": 1375896222.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "piotrdrag", "last_awarded": 1491478479.0, "percent_earned": 0.549939885113773,
+        "id": "taming-the-bull-tester-v", "times_awarded": 247, "name": "Taming the
+        Bull (Tester V)"}, {"description": "You contributed to a Common Bugs page.
+        Bugs? What bugs?", "tags": "content,", "issued": 1381324210.0, "image": "https://badges.fedoraproject.org/pngs/wiki-common-bugs.png",
+        "first_awarded": 1381324210.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ferdnyc", "last_awarded": 1484744055.0, "percent_earned": 0.1469474996660284,
+        "id": "common-bugs-contributor", "times_awarded": 66, "name": "Common Bugs
+        Contributor"}, {"description": "You have logged in to the Fedora Badges app",
+        "tags": "community,badges,", "issued": 1382755079.0, "image": "https://badges.fedoraproject.org/pngs/baby-badger.png",
+        "first_awarded": 1382755079.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "metonymy", "last_awarded": 1491502807.0, "percent_earned": 6.249721690341541,
+        "id": "baby-badger", "times_awarded": 2807, "name": "Baby Badger"}, {"description":
+        "Pushed 500 bodhi updates to the testing repositories.", "tags": "development",
+        "issued": 1384804767.0, "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-04.png",
+        "first_awarded": 1376840497.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "martinkg", "last_awarded": 1489655875.0, "percent_earned": 0.09351204524201807,
+        "id": "is-this-thing-on-updates-testing-iv", "times_awarded": 42, "name":
+        "Is this thing on? (Updates-Testing IV)"}, {"description": "You retired a
+        package from the distribution. It hurt, but it had to be done.", "tags": "development,ownership,",
+        "issued": 1389043285.0, "image": "https://badges.fedoraproject.org/pngs/pkgdb-old-yeller.png",
+        "first_awarded": 1382533838.0, "first_awarded_person": "notting", "last_awarded_person":
+        "pwalter", "last_awarded": 1491445231.0, "percent_earned": 0.6167342031437859,
+        "id": "old-yeller", "times_awarded": 277, "name": "Old Yeller"}, {"description":
+        "  You gave a presentation at Flock 2013, the Fedora Contributor Conference",
+        "tags": "event,speaker,", "issued": 1386089550.0, "image": "https://badges.fedoraproject.org/pngs/flockpresenter.png",
+        "first_awarded": 1386089406.0, "first_awarded_person": "sundaram", "last_awarded_person":
+        "pjones", "last_awarded": 1407317187.0, "percent_earned": 0.1580798860043639,
+        "id": "flock-2013-speaker", "times_awarded": 71, "name": "Flock 2013 Speaker"},
+        {"description": "You posted 20 or more things to the Fedora Planet!", "tags":
+        "blog,community,", "issued": 1386909731.0, "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-04.png",
+        "first_awarded": 1375904463.0, "first_awarded_person": "paulmellors", "last_awarded_person":
+        "stefw", "last_awarded": 1491468562.0, "percent_earned": 0.7547757937391459,
+        "id": "bloggin-it!-planet-iv", "times_awarded": 339, "name": "Bloggin'' it!
+        (Planet IV)"}, {"description": "Edited at least 50 wiki pages.", "tags": "content,wiki,",
+        "issued": 1389045512.0, "image": "https://badges.fedoraproject.org/pngs/senior-editor.png",
+        "first_awarded": 1375891555.0, "first_awarded_person": "ppisar", "last_awarded_person":
+        "augenauf", "last_awarded": 1491316933.0, "percent_earned": 1.013047156788529,
+        "id": "senior-editor", "times_awarded": 455, "name": "Senior Editor"}, {"description":
+        "You''ve had a FAS account for over three years", "tags": "lifecycle,community,",
+        "issued": 1389064377.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-tadpole.png",
+        "first_awarded": 1389061835.0, "first_awarded_person": "jwboyer", "last_awarded_person":
+        "avichi", "last_awarded": 1491361425.0, "percent_earned": 14.18933962684241,
+        "id": "tadpole", "times_awarded": 6373, "name": "Tadpole"}, {"description":
+        "You''ve had a FAS account for over one year", "tags": "lifecycle,community,",
+        "issued": 1389070973.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-egg.png",
+        "first_awarded": 1389068846.0, "first_awarded_person": "darthvader", "last_awarded_person":
+        "amogh23k", "last_awarded": 1491362307.0, "percent_earned": 28.41430288996749,
+        "id": "egg", "times_awarded": 12762, "name": "Egg"}, {"description": "You''ve
+        had a FAS account for over 5 years", "tags": "lifecycle,community,", "issued":
+        1389077372.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-tadpole-with-legs.png",
+        "first_awarded": 1389076692.0, "first_awarded_person": "ftpfree", "last_awarded_person":
+        "akopacsi", "last_awarded": 1490757692.0, "percent_earned": 7.30952486975108,
+        "id": "tadpole-with-legs", "times_awarded": 3283, "name": "Tadpole with Legs"},
+        {"description": "You''re a developer contributing to the Fedora Badges stack",
+        "tags": "development,badges,", "issued": 1389131503.0, "image": "https://badges.fedoraproject.org/pngs/badge-off.png",
+        "first_awarded": 1389131479.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "lsedlar", "last_awarded": 1476326553.0, "percent_earned": 0.05120897715634323,
+        "id": "badge-off!", "times_awarded": 23, "name": "Badge Off!"}, {"description":
+        "*(great joy)*\nTested and added karma to 80 updates-testing updates in Bodhi.\n",
+        "tags": "ten bulls,quality,", "issued": 1389368853.0, "image": "https://badges.fedoraproject.org/pngs/tester-06.png",
+        "first_awarded": 1375896221.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "sassam", "last_awarded": 1491148875.0, "percent_earned": 0.3072538629380594,
+        "id": "riding-the-bull-home-tester-vi", "times_awarded": 138, "name": "Riding
+        the Bull Home (Tester VI)"}, {"description": "You were sponsored into the
+        Fedora Infrastructure Apprentice group", "tags": "development,infrastructure,",
+        "issued": 1389889699.0, "image": "https://badges.fedoraproject.org/pngs/no-ronin.png",
+        "first_awarded": 1389889653.0, "first_awarded_person": "jam3s", "last_awarded_person":
+        "metonymy", "last_awarded": 1491476646.0, "percent_earned": 0.4208042035890813,
+        "id": "no-longer-a-ronin", "times_awarded": 189, "name": "No Longer a Ronin"},
+        {"description": "Added 250 new tags to packages in Fedora Tagger.", "tags":
+        "quality,tagger,", "issued": 1390419184.0, "image": "https://badges.fedoraproject.org/pngs/master-package-tagger.png",
+        "first_awarded": 1390419184.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "bt0dotninja", "last_awarded": 1489643141.0, "percent_earned": 0.13358863606002583,
+        "id": "master-package-tagger-package-tagger-iv", "times_awarded": 60, "name":
+        "Master Package Tagger (Package Tagger IV)"}, {"description": "Added a new
+        tag for a package in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419187.0, "image": "https://badges.fedoraproject.org/pngs/junior-package-tagger.png",
+        "first_awarded": 1390412894.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "bbroad", "last_awarded": 1491002111.0, "percent_earned": 0.8750055661931692,
+        "id": "junior-package-tagger-package-tagger-i", "times_awarded": 393, "name":
+        "Junior Package Tagger (Package Tagger I)"}, {"description": "Added 50 new
+        tags to packages in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419190.0, "image": "https://badges.fedoraproject.org/pngs/senior-package-tagger.png",
+        "first_awarded": 1390412897.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "ignacio", "last_awarded": 1485888954.0, "percent_earned": 0.28944204479672264,
+        "id": "senior-package-tagger-package-tagger-iii", "times_awarded": 130, "name":
+        "Senior Package Tagger (Package Tagger III)"}, {"description": "Added 10 new
+        tags for packages in Fedora Tagger.", "tags": "quality,tagger,", "issued":
+        1390419193.0, "image": "https://badges.fedoraproject.org/pngs/package-tagger.png",
+        "first_awarded": 1390412900.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "imabug", "last_awarded": 1490199130.0, "percent_earned": 0.5120897715634324,
+        "id": "package-tagger-package-tagger-ii", "times_awarded": 230, "name": "Package
+        Tagger (Package Tagger II)"}, {"description": "You gave a presentation at
+        DevConf.cz, 2014", "tags": "devconf,speaker,event,", "issued": 1390422108.0,
+        "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter.png", "first_awarded":
+        1390422108.0, "first_awarded_person": "ralph", "last_awarded_person": "siddharths",
+        "last_awarded": 1420480194.0, "percent_earned": 0.10241795431268647, "id":
+        "devconf-2014-speaker", "times_awarded": 46, "name": "DevConf 2014 Speaker"},
+        {"description": "Pushed 1000 commits to a Fedora package repository.", "tags":
+        "development,", "issued": 1390944539.0, "image": "https://badges.fedoraproject.org/pngs/you-can-call-me-patches-05.png",
+        "first_awarded": 1375892508.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "terjeros", "last_awarded": 1491331717.0, "percent_earned": 0.26717727212005166,
+        "id": "patches-scm-v", "times_awarded": 120, "name": "Patches (SCM V)"}, {"description":
+        "Created a buildroot override in the koji build system", "tags": "development,",
+        "issued": 1391016371.0, "image": "https://badges.fedoraproject.org/pngs/override-you-say.png",
+        "first_awarded": 1390489685.0, "first_awarded_person": "notting", "last_awarded_person":
+        "decathorpe", "last_awarded": 1491331634.0, "percent_earned": 0.7480963619361446,
+        "id": "override,-you-say", "times_awarded": 336, "name": "Override, you say?"},
+        {"description": "You attended the 2014 iteration of devconf.cz, a yearly Linux
+        and JBoss conference in the Czech Republic", "tags": "event,devconf,", "issued":
+        1391763090.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-attendee.png",
+        "first_awarded": 1391760446.0, "first_awarded_person": "churchyard", "last_awarded_person":
+        "siddharths", "last_awarded": 1419207296.0, "percent_earned": 0.13804159059536003,
+        "id": "devconf-attendee", "times_awarded": 62, "name": "DevConf 2014 Attendee"},
+        {"description": "You are a member of the videos team", "tags": "video,community,",
+        "issued": 1392217171.0, "image": "https://badges.fedoraproject.org/pngs/videographer.png",
+        "first_awarded": 1392217167.0, "first_awarded_person": "williamjmorenor",
+        "last_awarded_person": "swilson", "last_awarded": 1458943142.0, "percent_earned":
+        0.03562363628267355, "id": "videographer", "times_awarded": 16, "name": "Videographer"},
+        {"description": "Succesfully completed 1000 koji builds.", "tags": "development,koji,",
+        "issued": 1392827319.0, "image": "https://badges.fedoraproject.org/pngs/koji-if-you-build-it-05.png",
+        "first_awarded": 1377712281.0, "first_awarded_person": "rdieter", "last_awarded_person":
+        "zbyszek", "last_awarded": 1490388487.0, "percent_earned": 0.15585340873669679,
+        "id": "all-your-$arch-are-belong-to-us-koji-success-v", "times_awarded": 70,
+        "name": "All your $arch are belong to us (Koji Success V)"}, {"description":
+        "Successfully completed a copr build", "tags": "development,copr,", "issued":
+        1392676733.0, "image": "https://badges.fedoraproject.org/pngs/copr-build.png",
+        "first_awarded": 1389199510.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "shosca", "last_awarded": 1491441908.0, "percent_earned": 3.958676581912099,
+        "id": "building-the-outer-ring-copr-build-i", "times_awarded": 1778, "name":
+        "Building the Outer Ring (Copr Build I)"}, {"description": "Someone gave one
+        of your updates good karma", "tags": "development,karma,", "issued": 1392712116.0,
+        "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around.png",
+        "first_awarded": 1392392577.0, "first_awarded_person": "spot", "last_awarded_person":
+        "cydrobolt", "last_awarded": 1491503467.0, "percent_earned": 2.046132608986062,
+        "id": "what-goes-around-comes-around-karma-i", "times_awarded": 919, "name":
+        "What goes around comes around (Karma I)"}, {"description": "Tested an update
+        for an older stable release of Fedora", "tags": "quality,", "issued": 1392752446.0,
+        "image": "https://badges.fedoraproject.org/pngs/it-still-works.png", "first_awarded":
+        1386126231.0, "first_awarded_person": "kevin", "last_awarded_person": "caiqian",
+        "last_awarded": 1481903131.0, "percent_earned": 1.6765373825533243, "id":
+        "it-still-works!", "times_awarded": 753, "name": "It still works!"}, {"description":
+        "You submitted an update to bodhi with one or more bugzilla bug numbers attached",
+        "tags": "development,", "issued": 1394463577.0, "image": "https://badges.fedoraproject.org/pngs/take-this.png",
+        "first_awarded": 1393128956.0, "first_awarded_person": "mooninite", "last_awarded_person":
+        "cydrobolt", "last_awarded": 1491442200.0, "percent_earned": 2.1641359041724186,
+        "id": "take-this-and-call-me-in-the-morning", "times_awarded": 972, "name":
+        "Take this and call me in the morning"}, {"description": "You posted 40 or
+        more things to the Fedora Planet!", "tags": "blog,community,", "issued": 1398960087.0,
+        "image": "https://badges.fedoraproject.org/pngs/planet-bloggin-it-05.png",
+        "first_awarded": 1375963234.0, "first_awarded_person": "rjones", "last_awarded_person":
+        "stefw", "last_awarded": 1491468526.0, "percent_earned": 0.35623636282673554,
+        "id": "bob-loblaw-planet-v", "times_awarded": 160, "name": "Bob Loblaw (Planet
+        V)"}, {"description": "Pushed 500 bodhi updates to the stable repositories.",
+        "tags": "development,", "issued": 1396123360.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-04.png",
+        "first_awarded": 1376079014.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "dfateyev", "last_awarded": 1490465620.0, "percent_earned": 0.06902079529768002,
+        "id": "like-a-rock-updates-stable-iv", "times_awarded": 31, "name": "Like
+        a Rock (Updates-Stable IV)"}, {"description": "You set up a meeting in the
+        Fedora Calendar", "tags": "community,", "issued": 1397054728.0, "image": "https://badges.fedoraproject.org/pngs/lets-do-lunch.png",
+        "first_awarded": 1391191973.0, "first_awarded_person": "lbazan", "last_awarded_person":
+        "ignatenkobrain", "last_awarded": 1485808919.0, "percent_earned": 0.14472102239836132,
+        "id": "lets-do-lunch!", "times_awarded": 65, "name": "Let''s do lunch!"},
+        {"description": "Sip the lemonade away (You deserve it for remembering your
+        responsibility)!", "tags": "community,", "issued": 1397059347.0, "image":
+        "https://badges.fedoraproject.org/pngs/beach-bum.png", "first_awarded": 1394486757.0,
+        "first_awarded_person": "shaiton", "last_awarded_person": "mythcat", "last_awarded":
+        1484694397.0, "percent_earned": 0.4208042035890813, "id": "vacation", "times_awarded":
+        189, "name": "Vacation"}, {"description": "You visited the Fedora booth at
+        Pycon, 2014", "tags": "python,event,pycon,", "issued": 1397168025.0, "image":
+        "https://badges.fedoraproject.org/pngs/pycon_2014_attendee.png", "first_awarded":
+        1397168025.0, "first_awarded_person": "ralph", "last_awarded_person": "abompard",
+        "last_awarded": 1397573900.0, "percent_earned": 0.02226477267667097, "id":
+        "pycon-2014-attendee", "times_awarded": 10, "name": "Pycon 2014 Attendee"},
+        {"description": "You submitted feedback for an update in Bodhi after it was
+        pushed to stable", "tags": "quality,oops,", "issued": 1397684618.0, "image":
+        "https://badges.fedoraproject.org/pngs/missed-the-train.png", "first_awarded":
+        1392396477.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "thozza", "last_awarded": 1439904242.0, "percent_earned": 0.6345460212851227,
+        "id": "missed-the-train", "times_awarded": 285, "name": "Missed the Train"},
+        {"description": "You built a package owned by someone else.  It takes a village...",
+        "tags": "development,", "issued": 1400246660.0, "image": "https://badges.fedoraproject.org/pngs/helping_hand.png",
+        "first_awarded": 1394038498.0, "first_awarded_person": "sochotni", "last_awarded_person":
+        "nickboldt", "last_awarded": 1490915668.0, "percent_earned": 0.984102952308857,
+        "id": "helping-hand", "times_awarded": 442, "name": "Helping Hand"}, {"description":
+        "You''ve been granted the blessing of the Fedora Project Leader.", "tags":
+        "fpl,miscellaneous,", "issued": 1401833399.0, "image": "https://badges.fedoraproject.org/pngs/fpl-blessing.png",
+        "first_awarded": 1376269552.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "skottler", "last_awarded": 1401833697.0, "percent_earned": 0.20483590862537293,
+        "id": "the-blessing-of-the-fpl", "times_awarded": 92, "name": "The Blessing
+        of the FPL"}, {"description": "Submitted 100 or more koji builds that failed
+        to build.", "tags": "development,oops,koji,", "issued": 1403195400.0, "image":
+        "https://badges.fedoraproject.org/pngs/koji-what-goes-up-04.png", "first_awarded":
+        1377715853.0, "first_awarded_person": "vicodan", "last_awarded_person": "wtaymans",
+        "last_awarded": 1490906731.0, "percent_earned": 0.20038295409003873, "id":
+        "what-goes-up...-koji-failure-iv", "times_awarded": 90, "name": "What goes
+        up... (Koji Failure IV)"}, {"description": "You updated your password with
+        the Fedora Account System (FAS)", "tags": "account,community,", "issued":
+        1404948910.0, "image": "https://badges.fedoraproject.org/pngs/fas-paranoid-panda.png",
+        "first_awarded": 1376708721.0, "first_awarded_person": "rstrode", "last_awarded_person":
+        "payasr", "last_awarded": 1491512765.0, "percent_earned": 61.86712383666563,
+        "id": "paranoid-panda", "times_awarded": 27787, "name": "Paranoid Panda"},
+        {"description": "      You gave a presentation at Flock 2014, the Fedora Contributor
+        Conference", "tags": "event,flock,speaker,", "issued": 1405362797.0, "image":
+        "https://badges.fedoraproject.org/pngs/flock-2014-speaker.png", "first_awarded":
+        1405362496.0, "first_awarded_person": "abompard", "last_awarded_person": "amita",
+        "last_awarded": 1408553413.0, "percent_earned": 0.16253284053969808, "id":
+        "flock-2014-speaker", "times_awarded": 73, "name": "Flock 2014 Speaker"},
+        {"description": "You have received 10 or more good karma on your bodhi updates",
+        "tags": "development,karma,", "issued": 1406215355.0, "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-10.png",
+        "first_awarded": 1392397378.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "jvymazal", "last_awarded": 1491284602.0, "percent_earned": 1.099879770227546,
+        "id": "what-goes-around-comes-around-karma-ii", "times_awarded": 494, "name":
+        "What goes around comes around (Karma II)"}, {"description": "Edited at least
+        100 wiki pages.", "tags": "content,wiki,", "issued": 1406329746.0, "image":
+        "https://badges.fedoraproject.org/pngs/master-editor.png", "first_awarded":
+        1375891554.0, "first_awarded_person": "ppisar", "last_awarded_person": "athoscr",
+        "last_awarded": 1490991464.0, "percent_earned": 0.5900164759317807, "id":
+        "master-editor", "times_awarded": 265, "name": "Master Editor"}, {"description":
+        "You have received 50 or more good karma on your bodhi updates", "tags": "development,karma,",
+        "issued": 1406630310.0, "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-50.png",
+        "first_awarded": 1393287975.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "terjeros", "last_awarded": 1491384354.0, "percent_earned": 0.389633521841742,
+        "id": "what-goes-around-comes-around-karma-iii", "times_awarded": 175, "name":
+        "What goes around comes around (Karma III)"}, {"description": "  You attended
+        Flock 2014, the Fedora Contributor Conference", "tags": "event,flock,", "issued":
+        1407308400.0, "image": "https://badges.fedoraproject.org/pngs/flock-2014-attendee.png",
+        "first_awarded": 1405296000.0, "first_awarded_person": "marinaz", "last_awarded_person":
+        "rathann", "last_awarded": 1426267409.0, "percent_earned": 0.27163022665538583,
+        "id": "flock-2014-attendee", "times_awarded": 122, "name": "Flock 2014 Attendee"},
+        {"description": "You completed a run of the kernel regression test suite",
+        "tags": "kernel,quality,", "issued": 1407409472.0, "image": "https://badges.fedoraproject.org/pngs/kernel-tester.png",
+        "first_awarded": 1407405561.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "pperron", "last_awarded": 1490999828.0, "percent_earned": 0.49873090795742975,
+        "id": "science-kernel-tester-i", "times_awarded": 224, "name": "Science (Kernel
+        Tester I)"}, {"description": "Ahoy! You attended the boat party at the Flock
+        2014 conference in Prague!", "tags": "event,flock,", "issued": 1407430507.0,
+        "image": "https://badges.fedoraproject.org/pngs/party-pirate.png", "first_awarded":
+        1407430497.0, "first_awarded_person": "lmacken", "last_awarded_person": "amsharma",
+        "last_awarded": 1408665317.0, "percent_earned": 0.19815647682237164, "id":
+        "party-pirate", "times_awarded": 89, "name": "Party Pirate"}, {"description":
+        "You submitted 200 Bodhi updates for an EPEL branch.", "tags": "development,epel,",
+        "issued": 1407498349.0, "image": "https://badges.fedoraproject.org/pngs/corporate-overlord.png",
+        "first_awarded": 1407423571.0, "first_awarded_person": "remi", "last_awarded_person":
+        "pghmcfc", "last_awarded": 1485788654.0, "percent_earned": 0.03339715901500646,
+        "id": "corporate-overlord", "times_awarded": 15, "name": "Corporate Overlord"},
+        {"description": "You submitted 50 Bodhi updates for an EPEL branch.", "tags":
+        "development,epel,", "issued": 1407498359.0, "image": "https://badges.fedoraproject.org/pngs/corporate-shill.png",
+        "first_awarded": 1407423581.0, "first_awarded_person": "remi", "last_awarded_person":
+        "kdudka", "last_awarded": 1491214689.0, "percent_earned": 0.18925056775170326,
+        "id": "corporate-shill", "times_awarded": 85, "name": "Corporate Shill"},
+        {"description": "You submitted an Bodhi update for an EPEL branch.", "tags":
+        "development,epel,", "issued": 1407498367.0, "image": "https://badges.fedoraproject.org/pngs/corporate-drone.png",
+        "first_awarded": 1407423591.0, "first_awarded_person": "remi", "last_awarded_person":
+        "dsommers", "last_awarded": 1490298016.0, "percent_earned": 1.3024892015852518,
+        "id": "corporate-drone", "times_awarded": 585, "name": "Corporate Drone"},
+        {"description": "You got 100 Fedora Badges!  Wow!", "tags": "community,badges,",
+        "issued": 1407498391.0, "image": "https://badges.fedoraproject.org/pngs/badger-03.png",
+        "first_awarded": 1394558737.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "vondruch", "last_awarded": 1491296529.0, "percent_earned": 0.14026806786302712,
+        "id": "master-badger-badger-iii", "times_awarded": 63, "name": "Master Badger
+        (Badger III)"}, {"description": "*(the bull forgotten, discipline''s whip
+        is idle; stillness)*\nTested and added karma to 125 updates-testing updates
+        in Bodhi.\n", "tags": "ten bulls,quality,", "issued": 1408115651.0, "image":
+        "https://badges.fedoraproject.org/pngs/tester-07.png", "first_awarded": 1375896222.0,
+        "first_awarded_person": "kevin", "last_awarded_person": "fale", "last_awarded":
+        1490321832.0, "percent_earned": 0.2248742040343768, "id": "the-bull-transcended-tester-vii",
+        "times_awarded": 101, "name": "The Bull Transcended (Tester VII)"}, {"description":
+        "You danced with Toshio (abadger1999) at a Fedora event", "tags": "event,freshness,",
+        "issued": 1408122340.0, "image": "https://badges.fedoraproject.org/pngs/dancing_with_toshio.png",
+        "first_awarded": 1408121695.0, "first_awarded_person": "abompard", "last_awarded_person":
+        "rbergero", "last_awarded": 1455959914.0, "percent_earned": 0.0712472725653471,
+        "id": "dancing-with-toshio", "times_awarded": 32, "name": "Dancing with Toshio"},
+        {"description": "Voted in a supplemental wallpapers election for the Fedora
+        21 release", "tags": "election,community,nuancier,", "issued": 1408150877.0,
+        "image": "https://badges.fedoraproject.org/pngs/nuancier-f21-voted.png", "first_awarded":
+        1408147284.0, "first_awarded_person": "gnokii", "last_awarded_person": "pravins",
+        "last_awarded": 1409902273.0, "percent_earned": 0.2449124994433807, "id":
+        "nuancier-21", "times_awarded": 110, "name": "Nuancier 21"}, {"description":
+        "We''re sorry, but you had one of your badges revoked.  Something must have
+        gone wrong.", "tags": "sad trombone,badges,community,", "issued": 1408725704.0,
+        "image": "https://badges.fedoraproject.org/pngs/consolation-prize.png", "first_awarded":
+        1392255607.0, "first_awarded_person": "dmalcolm", "last_awarded_person": "keitellf",
+        "last_awarded": 1465418173.0, "percent_earned": 0.8972703388698402, "id":
+        "consolation-prize", "times_awarded": 403, "name": "Consolation Prize"}, {"description":
+        "You updated your GPG key with the Fedora Account System (FAS)", "tags": "account,community,",
+        "issued": 1408455462.0, "image": "https://badges.fedoraproject.org/pngs/fas-crypto-panda.png",
+        "first_awarded": 1375891835.0, "first_awarded_person": "itorres", "last_awarded_person":
+        "usuha", "last_awarded": 1491448085.0, "percent_earned": 6.430066349022577,
+        "id": "crypto-panda", "times_awarded": 2888, "name": "Crypto Panda"}, {"description":
+        "Successfully completed 20 copr builds", "tags": "development,copr,", "issued":
+        1408587109.0, "image": "https://badges.fedoraproject.org/pngs/copr-build-20.png",
+        "first_awarded": 1389199520.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "shosca", "last_awarded": 1491505320.0, "percent_earned": 2.3711982900654585,
+        "id": "building-the-outer-ring-copr-build-ii", "times_awarded": 1065, "name":
+        "Building the Outer Ring (Copr Build II)"}, {"description": "Pushed 1000 bodhi
+        updates to the testing repositories.", "tags": "development,", "issued": 1409321568.0,
+        "image": "https://badges.fedoraproject.org/pngs/is-this-thing-on-05.png",
+        "first_awarded": 1398006872.0, "first_awarded_person": "jamielinux", "last_awarded_person":
+        "pghmcfc", "last_awarded": 1490811747.0, "percent_earned": 0.03562363628267355,
+        "id": "mic-check!-updates-testing-v", "times_awarded": 16, "name": "Mic Check!
+        (Updates-Testing V)"}, {"description": "You took ownership of a previously
+        orphaned package.", "tags": "development,ownership,", "issued": 1418228616.0,
+        "image": "https://badges.fedoraproject.org/pngs/pkgdb-the-cat-came-back.png",
+        "first_awarded": 1381330499.0, "first_awarded_person": "tmraz", "last_awarded_person":
+        "sailer", "last_awarded": 1490344262.0, "percent_earned": 0.7525493164714788,
+        "id": "the-cat-came-back...", "times_awarded": 338, "name": "The cat came
+        back..."}, {"description": "You designed a Fedora Badge! Welcome to Florence,
+        apprentice badge artist.", "tags": "content,design,badges", "issued": 1420755671.0,
+        "image": "https://badges.fedoraproject.org/pngs/artist-01-apprentice.png",
+        "first_awarded": 1420755606.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "michalrud", "last_awarded": 1470317213.0, "percent_earned": 0.04675602262100904,
+        "id": "apprentice-badge-artist-i", "times_awarded": 21, "name": "Apprentice
+        (Badge Artist I)"}, {"description": "You have received 100 or more good karma
+        on your bodhi updates", "tags": "development,karma,", "issued": 1421316923.0,
+        "image": "https://badges.fedoraproject.org/pngs/what-goes-around-comes-around-100.png",
+        "first_awarded": 1396803342.0, "first_awarded_person": "jforbes", "last_awarded_person":
+        "kkeithle", "last_awarded": 1491364811.0, "percent_earned": 0.20706238589304005,
+        "id": "what-goes-around-comes-around-karma-iv", "times_awarded": 93, "name":
+        "What goes around comes around (Karma IV)"}, {"description": "You participated
+        in the Design Team FAD, 2015", "tags": "event,fad,design,", "issued": 1421420485.0,
+        "image": "https://badges.fedoraproject.org/pngs/fad_design.png", "first_awarded":
+        1421419806.0, "first_awarded_person": "chrisroberts", "last_awarded_person":
+        "rbergero", "last_awarded": 1421432734.0, "percent_earned": 0.04007659081800775,
+        "id": "design-ninja", "times_awarded": 18, "name": "Design Ninja"}, {"description":
+        "You gave a presentation at Flock 2015, the Fedora Contributor Conference",
+        "tags": "event,flock,speaker,", "issued": 1439502241.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-speaker.png",
+        "first_awarded": 1439502092.0, "first_awarded_person": "spot", "last_awarded_person":
+        "jzeleny", "last_awarded": 1439503832.0, "percent_earned": 0.13804159059536003,
+        "id": "flock-2015-speaker", "times_awarded": 62, "name": "Flock 2015 Speaker"},
+        {"description": "Pushed 1000 bodhi updates to the stable repositories.", "tags":
+        "development,", "issued": 1423235515.0, "image": "https://badges.fedoraproject.org/pngs/like-a-rock-05.png",
+        "first_awarded": 1413266082.0, "first_awarded_person": "remi", "last_awarded_person":
+        "orion", "last_awarded": 1490756246.0, "percent_earned": 0.024491249944338066,
+        "id": "tectonic!-updates-stable-v", "times_awarded": 11, "name": "Tectonic!
+        (Updates-Stable V)"}, {"description": "You got 25 or more Fedora Badges",
+        "tags": "community,badges,", "issued": 1423235523.0, "image": "https://badges.fedoraproject.org/pngs/badger-01.5.png",
+        "first_awarded": 1421694162.0, "first_awarded_person": "rhughes", "last_awarded_person":
+        "pcreech17", "last_awarded": 1491432940.0, "percent_earned": 2.3756512446007925,
+        "id": "associate-badger-badger-1.5", "times_awarded": 1067, "name": "Associate
+        Badger (Badger 1.5)"}, {"description": "You attended Flock 2015, the Fedora
+        Contributor Conference", "tags": "conference,rochester,events,flock,flock
+        2015,event,", "issued": 1439318156.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-attendee.png",
+        "first_awarded": 1438119680.0, "first_awarded_person": "decause", "last_awarded_person":
+        "kellin", "last_awarded": 1461547496.0, "percent_earned": 0.18479761321636906,
+        "id": "flock-2015-attendee", "times_awarded": 83, "name": "Flock 2015 Attendee"},
+        {"description": "You''ve been given a cookie for helping someone out.  Thanks!",
+        "tags": "cookie,community,", "issued": 1426702546.0, "image": "https://badges.fedoraproject.org/pngs/macaroncookie.png",
+        "first_awarded": 1426702242.0, "first_awarded_person": "riecatnor", "last_awarded_person":
+        "gtierney", "last_awarded": 1491405719.0, "percent_earned": 1.852429086699025,
+        "id": "macaron-cookie-i", "times_awarded": 832, "name": "Macaron (Cookie I)"},
+        {"description": "Ten people think you''re great, because they''ve given you
+        cookies. Yummy!", "tags": "cookie,community,", "issued": 1426796321.0, "image":
+        "https://badges.fedoraproject.org/pngs/chocolatechipcookie.png", "first_awarded":
+        1426796321.0, "first_awarded_person": "ralph", "last_awarded_person": "mizdebsk",
+        "last_awarded": 1491500198.0, "percent_earned": 0.3606893173620697, "id":
+        "chocolate-chip-cookie-ii", "times_awarded": 162, "name": "Chocolate Chip
+        (Cookie II)"}, {"description": "Voted in the supplemental wallpapers election
+        for the Fedora 22 release", "tags": "election,community,nuancier,", "issued":
+        1426883250.0, "image": "https://badges.fedoraproject.org/pngs/nuancier-f22-voted.png",
+        "first_awarded": 1426835617.0, "first_awarded_person": "gnokii", "last_awarded_person":
+        "sarupbanskota", "last_awarded": 1427224635.0, "percent_earned": 0.18257113594870197,
+        "id": "nuancier-f22", "times_awarded": 82, "name": "Nuancier (F22)"}, {"description":
+        "You pinned your country in FAS!", "tags": "account,community,", "issued":
+        1427991763.0, "image": "https://badges.fedoraproject.org/pngs/fas-country.png",
+        "first_awarded": 1427832337.0, "first_awarded_person": "aeperezt", "last_awarded_person":
+        "schwankek", "last_awarded": 1491486520.0, "percent_earned": 17.635926437191078,
+        "id": "origin", "times_awarded": 7921, "name": "Origin"}, {"description":
+        "You added a new package to the distribution.", "tags": "development,", "issued":
+        1377267893.0, "image": "https://badges.fedoraproject.org/pngs/delivery.png",
+        "first_awarded": 1376569056.0, "first_awarded_person": "besser82", "last_awarded_person":
+        "nmilosev", "last_awarded": 1491213323.0, "percent_earned": 0.919535111546511,
+        "id": "delivery", "times_awarded": 413, "name": "Delivery"}, {"description":
+        "You mapped an upstream project to a Fedora package on release-monitoring.org",
+        "tags": "quality,anitya,", "issued": 1430855052.0, "image": "https://badges.fedoraproject.org/pngs/telegraph.png",
+        "first_awarded": 1430855052.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dustymabe", "last_awarded": 1475240820.0, "percent_earned": 0.5655252259874427,
+        "id": "telegraphist-upstream-release-monitoring-i", "times_awarded": 254,
+        "name": "Telegraphist (Upstream Release Monitoring I)"}, {"description": "Mmmmm....
+        cookies. You''ve helped out 25 Fedorans! Go you!", "tags": "cookies,cookie,community,",
+        "issued": 1430923797.0, "image": "https://badges.fedoraproject.org/pngs/blackandwhitecookie.png",
+        "first_awarded": 1430923797.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "dmsimard", "last_awarded": 1491321510.0, "percent_earned": 0.14472102239836132,
+        "id": "black-and-white-cookie-iii", "times_awarded": 65, "name": "Black and
+        White (Cookie III)"}, {"description": "Submitted 250 or more koji builds that
+        failed to build.", "tags": "development,oops,koji,", "issued": 1431713399.0,
+        "image": "https://badges.fedoraproject.org/pngs/koji-what-goes-up-05.png",
+        "first_awarded": 1378300711.0, "first_awarded_person": "ppisar", "last_awarded_person":
+        "smani", "last_awarded": 1485976444.0, "percent_earned": 0.08460613617134968,
+        "id": "hindenburg-koji-failure-v", "times_awarded": 38, "name": "Hindenburg
+        (Koji Failure V)"}, {"description": "Twenty Fedora Badges owe their existence
+        to you.  Prolific!", "tags": "community,badges,", "issued": 1433267989.0,
+        "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_20.png",
+        "first_awarded": 1433267989.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "jflory7", "last_awarded": 1475509860.0, "percent_earned": 0.006679431803001291,
+        "id": "badge-muse-badges-released-iv", "times_awarded": 3, "name": "Badge
+        Muse (Badges Released IV)"}, {"description": "Five of your new Fedora Badge
+        ideas were approved and released.  Superb!", "tags": "community,badges,",
+        "issued": 1433268002.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_5.png",
+        "first_awarded": 1423674135.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "pravins", "last_awarded": 1472553692.0, "percent_earned": 0.03562363628267355,
+        "id": "badge-muse-badges-released-ii", "times_awarded": 16, "name": "Badge
+        Muse (Badges Released II)"}, {"description": "Your idea for a new Fedora Badge
+        was approved and released.  Awesome!", "tags": "community,badges,", "issued":
+        1433268014.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved.png",
+        "first_awarded": 1423674153.0, "first_awarded_person": "adamwill", "last_awarded_person":
+        "frantisekz", "last_awarded": 1485462295.0, "percent_earned": 0.10019147704501936,
+        "id": "badge-muse-badges-released-i", "times_awarded": 45, "name": "Badge
+        Muse (Badges Released I)"}, {"description": "Ten of your new Fedora Badge
+        ideas were approved and released.  Impressive!", "tags": "community,badges,",
+        "issued": 1433267996.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_10.png",
+        "first_awarded": 1424466072.0, "first_awarded_person": "gnokii", "last_awarded_person":
+        "jflory7", "last_awarded": 1475509873.0, "percent_earned": 0.013358863606002582,
+        "id": "badge-muse-badges-released-iii", "times_awarded": 6, "name": "Badge
+        Muse (Badges Released III)"}, {"description": "Each unique like a snowflake,
+        fifty of your Fedora Badge ideas were implemented.", "tags": "community,badges,",
+        "issued": 1433268008.0, "image": "https://badges.fedoraproject.org/pngs/badgemuse_approved_50.png",
+        "first_awarded": 1433268008.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ralph", "last_awarded": 1433268008.0, "percent_earned": 0.002226477267667097,
+        "id": "badge-muse-badges-released-v", "times_awarded": 1, "name": "Badge Muse
+        (Badges Released V)"}, {"description": "Beep boop!  You attended the Museum
+        of Play party at the Flock 2015 conference in Rochester!", "tags": "event,flock,",
+        "issued": 1440122051.0, "image": "https://badges.fedoraproject.org/pngs/flock-2015-party.png",
+        "first_awarded": 1439510783.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "kellin", "last_awarded": 1461547520.0, "percent_earned": 0.16921227234269937,
+        "id": "nom-nom-nom", "times_awarded": 76, "name": "Nom Nom Nom"}, {"description":
+        "Successfully completed 60 copr builds", "tags": "development,copr,", "issued":
+        1441220012.0, "image": "https://badges.fedoraproject.org/pngs/copr-build-60.png",
+        "first_awarded": 1389199513.0, "first_awarded_person": "dvratil", "last_awarded_person":
+        "rluzynski", "last_awarded": 1491442095.0, "percent_earned": 1.5763459055083047,
+        "id": "building-the-outer-ring-copr-build-iii", "times_awarded": 708, "name":
+        "Building the Outer Ring (Copr Build III)"}, {"description": "You maintain
+        the Fedora websites.  The Web is yours!", "tags": "web,community,", "issued":
+        1442348476.0, "image": "https://badges.fedoraproject.org/pngs/rock-the-web.png",
+        "first_awarded": 1393598200.0, "first_awarded_person": "sundaram", "last_awarded_person":
+        "dustymabe", "last_awarded": 1491326903.0, "percent_earned": 0.12468272698935744,
+        "id": "rock-the-web!", "times_awarded": 56, "name": "Rock the Web!"}, {"description":
+        "You can speak Python and and you''ve ported at least one package to Python
+        3", "tags": "python,miscellaneous,", "issued": 1447943105.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth.png",
+        "first_awarded": 1438695566.0, "first_awarded_person": "rkuska", "last_awarded_person":
+        "crobinso", "last_awarded": 1491217608.0, "percent_earned": 0.33842454468539873,
+        "id": "parselmouth", "times_awarded": 152, "name": "Parselmouth I"}, {"description":
+        "Edited at least 250 wiki pages.", "tags": "content,wiki,", "issued": 1444762324.0,
+        "image": "https://badges.fedoraproject.org/pngs/wikiedit-250.png", "first_awarded":
+        1409692963.0, "first_awarded_person": "hhorak", "last_awarded_person": "jonatoni",
+        "last_awarded": 1490211890.0, "percent_earned": 0.22710068130204392, "id":
+        "chief-editor", "times_awarded": 102, "name": "Chief Editor"}, {"description":
+        "You''ve had a FAS account for over seven years", "tags": "lifecycle,community,",
+        "issued": 1446001652.0, "image": "https://badges.fedoraproject.org/pngs/fas-account-froglet.png",
+        "first_awarded": 1389060935.0, "first_awarded_person": "jwboyer", "last_awarded_person":
+        "apahim", "last_awarded": 1491360865.0, "percent_earned": 4.628846239479895,
+        "id": "froglet", "times_awarded": 2079, "name": "Froglet"}, {"description":
+        "50 Fedorans have recognized your achievements by giving you cookies.", "tags":
+        "cookie,community,", "issued": 1447693727.0, "image": "https://badges.fedoraproject.org/pngs/pizzellecookie.png",
+        "first_awarded": 1447693727.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "ryanlerch", "last_awarded": 1490719235.0, "percent_earned": 0.03117068174733936,
+        "id": "pizzelle-cookie-iv", "times_awarded": 14, "name": "Pizzelle (Cookie
+        IV)"}, {"description": "You held second place on the Fedora Badges leaderboard",
+        "tags": "meta,community,", "issued": 1452873723.0, "image": "https://badges.fedoraproject.org/pngs/2_badge_340.png",
+        "first_awarded": 1452873723.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "churchyard", "last_awarded": 1475564266.0, "percent_earned": 0.011132386338335486,
+        "id": "silver", "times_awarded": 5, "name": "Silver"}, {"description": "You
+        ranked among the top 10 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875167.0, "image": "https://badges.fedoraproject.org/pngs/10_badge_340.png",
+        "first_awarded": 1452875035.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "fale", "last_awarded": 1489622707.0, "percent_earned": 0.03117068174733936,
+        "id": "top-10", "times_awarded": 14, "name": "Top 10"}, {"description": "You
+        ranked among the top 100 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875191.0, "image": "https://badges.fedoraproject.org/pngs/100_badge_340.png",
+        "first_awarded": 1452875133.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "tflink", "last_awarded": 1480461776.0, "percent_earned": 0.25159193124638196,
+        "id": "top-100", "times_awarded": 113, "name": "Top 100"}, {"description":
+        "You ranked among the top 25 contributors on the Fedora Badges leaderboard",
+        "tags": "meta,community,", "issued": 1452875198.0, "image": "https://badges.fedoraproject.org/pngs/25_badge_340.png",
+        "first_awarded": 1452875140.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "mrunge", "last_awarded": 1490730635.0, "percent_earned": 0.06679431803001291,
+        "id": "top-25", "times_awarded": 30, "name": "Top 25"}, {"description": "You
+        ranked among the top 500 contributors on the Fedora Badges leaderboard", "tags":
+        "meta,community,", "issued": 1452875206.0, "image": "https://badges.fedoraproject.org/pngs/500_badge_340.png",
+        "first_awarded": 1452875147.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "zsun", "last_awarded": 1490677027.0, "percent_earned": 1.2000712472725654,
+        "id": "top-500", "times_awarded": 539, "name": "Top 500"}, {"description":
+        "You visited the Fedora table at BrickHack 2016!", "tags": "brickhack,event,hackathon,events,",
+        "issued": 1457234130.0, "image": "http://infrastructure.fedoraproject.org/infra/badges/pngs/events-brickhack-2016.png",
+        "first_awarded": 1455845197.0, "first_awarded_person": "jflory7", "last_awarded_person":
+        "brandondgr8", "last_awarded": 1461299769.0, "percent_earned": 0.05566193169167742,
+        "id": "brickhack-2016-attendee", "times_awarded": 25, "name": "BrickHack 2016
+        Attendee"}, {"description": "You gave a presentation at DevConf.cz, 2016",
+        "tags": "devconf,speaker,event,", "issued": 1455008224.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter-2016.png",
+        "first_awarded": 1454667336.0, "first_awarded_person": "pbrobinson", "last_awarded_person":
+        "thozza", "last_awarded": 1459500320.0, "percent_earned": 0.2137418176960413,
+        "id": "devconf-2016-speaker", "times_awarded": 96, "name": "DevConf 2016 Speaker"},
+        {"description": "You attended the 2016 iteration of DevConf.cz, a yearly Linux
+        and JBoss conference in the Czech Republic", "tags": "event, devconf,", "issued":
+        1455120228.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-attendee-2016.png",
+        "first_awarded": 1454600601.0, "first_awarded_person": "eischmann", "last_awarded_person":
+        "dkaspar", "last_awarded": 1461940547.0, "percent_earned": 0.18925056775170326,
+        "id": "devconf-2016-attendee", "times_awarded": 85, "name": "DevConf 2016
+        Attendee"}, {"description": "You got a \"Change\" accepted into the Fedora
+        24 Change list", "tags": "development,FESCo,process,", "issued": 1458652673.0,
+        "image": "https://badges.fedoraproject.org/pngs/changes-f24.png", "first_awarded":
+        1458651465.0, "first_awarded_person": "eischmann", "last_awarded_person":
+        "pnemade", "last_awarded": 1459240914.0, "percent_earned": 0.10019147704501936,
+        "id": "fedora-24-change-accepted", "times_awarded": 45, "name": "Fedora 24
+        Change Accepted"}, {"description": "You held third place on the Fedora Badges
+        leaderboard", "tags": "meta,community,", "issued": 1462904993.0, "image":
+        "https://badges.fedoraproject.org/pngs/3_badge_340.png", "first_awarded":
+        1452875229.0, "first_awarded_person": "pnemade", "last_awarded_person": "kevin",
+        "last_awarded": 1489692901.0, "percent_earned": 0.011132386338335486, "id":
+        "bronze", "times_awarded": 5, "name": "Bronze"}, {"description": "You burned
+        a yubikey and linked it with your Fedora Account", "tags": "account,community,",
+        "issued": 1462904755.0, "image": "https://badges.fedoraproject.org/pngs/yubi-badge.png",
+        "first_awarded": 1422906928.0, "first_awarded_person": "plautrba", "last_awarded_person":
+        "findarato", "last_awarded": 1491408410.0, "percent_earned": 0.4052188627154117,
+        "id": "horcrux", "times_awarded": 182, "name": "Horcrux"}, {"description":
+        "You hacked with the Fedora crew at PyCon US 2016", "tags": "event, python,pycon,",
+        "issued": 1464988862.0, "image": "https://badges.fedoraproject.org/pngs/pycon_2016_attendee.png",
+        "first_awarded": 1464988862.0, "first_awarded_person": "ralph", "last_awarded_person":
+        "maxamillion", "last_awarded": 1464991242.0, "percent_earned": 0.028944204479672265,
+        "id": "pycon-2016-attendee", "times_awarded": 13, "name": "PyCon 2016 Attendee"},
+        {"description": "You got 150 Fedora Badges!  Wow!", "tags": "badges,community,",
+        "issued": 1468058613.0, "image": "https://badges.fedoraproject.org/pngs/badger-04.png",
+        "first_awarded": 1457979017.0, "first_awarded_person": "kevin", "last_awarded_person":
+        "pbrobinson", "last_awarded": 1490919191.0, "percent_earned": 0.020038295409003874,
+        "id": "badge-junkie-badger-iv", "times_awarded": 9, "name": "Badge Junkie
+        (Badger IV)"}, {"description": "You can speak Python and and you''ve ported
+        at least five packages to Python 3", "tags": "python,miscellaneous,", "issued":
+        1468058067.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth-II.png",
+        "first_awarded": 1467988651.0, "first_awarded_person": "cstratak", "last_awarded_person":
+        "ishcherb", "last_awarded": 1488193805.0, "percent_earned": 0.028944204479672265,
+        "id": "parselmouth-ii", "times_awarded": 13, "name": "Parselmouth II"}, {"description":
+        "You can speak Python and and you''ve ported at least ten packages to Python
+        3", "tags": "python,miscellaneous,", "issued": 1468058072.0, "image": "https://badges.fedoraproject.org/pngs/parselmouth-III.png",
+        "first_awarded": 1467997850.0, "first_awarded_person": "cstratak", "last_awarded_person":
+        "ishcherb", "last_awarded": 1489753361.0, "percent_earned": 0.02226477267667097,
+        "id": "parselmouth-iii", "times_awarded": 10, "name": "Parselmouth III"},
+        {"description": "You attended Flock 2016, the Fedora Contributor Conference",
+        "tags": "flock,event,events,", "issued": 1470404480.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/flock-2016-attendee.png",
+        "first_awarded": 1468984748.0, "first_awarded_person": "ryanlerch", "last_awarded_person":
+        "adrian", "last_awarded": 1486483875.0, "percent_earned": 0.2360065903727123,
+        "id": "flock-2016-attendee", "times_awarded": 106, "name": "Flock 2016 Attendee"},
+        {"description": "You attended the party at Flock 2016 in Krakow!", "tags":
+        "flock,krakow,event,events,", "issued": 1470404592.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/krakow-brews.png",
+        "first_awarded": 1468963417.0, "first_awarded_person": "duffy", "last_awarded_person":
+        "kittykat", "last_awarded": 1486217490.0, "percent_earned": 0.18257113594870197,
+        "id": "krakow-brews!", "times_awarded": 82, "name": "Krakow Brews!"}, {"description":
+        "You gave a presentation at Flock 2016, the Fedora Contributor Conference",
+        "tags": "flock,speaker,event,", "issued": 1471354751.0, "image": "https://infrastructure.fedoraproject.org/infra/badges/pngs/flock-2016-speaker.png",
+        "first_awarded": 1470941077.0, "first_awarded_person": "mattdm", "last_awarded_person":
+        "kittykat", "last_awarded": 1486217546.0, "percent_earned": 0.19592999955470453,
+        "id": "flock-2016-speaker", "times_awarded": 88, "name": "Flock 2016 Speaker"},
+        {"description": "You''re a member of the Modularity Working Group!", "tags":
+        "modularity,community,", "issued": 1475082397.0, "image": "https://badges.fedoraproject.org/pngs/fas-modularity-wg.png",
+        "first_awarded": 1475082098.0, "first_awarded_person": "asamalik", "last_awarded_person":
+        "jscotka", "last_awarded": 1491213584.0, "percent_earned": 0.0801531816360155,
+        "id": "modularity-wg-member", "times_awarded": 36, "name": "Modularity WG
+        Member"}, {"description": "You got a \"Change\" accepted into the Fedora 26
+        Change list", "tags": "development, FESCo, process,", "issued": 1479749896.0,
+        "image": "https://badges.fedoraproject.org/pngs/changes-f26.png", "first_awarded":
+        1473331018.0, "first_awarded_person": "cstratak", "last_awarded_person": "ncoghlan",
+        "last_awarded": 1489656918.0, "percent_earned": 0.11577681791868906, "id":
+        "fedora-26-change-accepted", "times_awarded": 52, "name": "Fedora 26 Change
+        Accepted"}, {"description": "You gave a presentation at DevConf.cz, 2017",
+        "tags": "speaker,devconf,event,", "issued": 1486037155.0, "image": "https://badges.fedoraproject.org/pngs/dev-conf-presenter-2017.png",
+        "first_awarded": 1486033923.0, "first_awarded_person": "jflory7", "last_awarded_person":
+        "siddharths", "last_awarded": 1486077040.0, "percent_earned": 0.24713897671104776,
+        "id": "devconf-2017-speaker", "times_awarded": 111, "name": "DevConf 2017
+        Speaker"}, {"description": "You have helped so many Fedorans you''ve got over
+        100 cookies. Don''t eat them all at once!", "tags": "cookie,community,", "issued":
+        1486153659.0, "image": "https://badges.fedoraproject.org/pngs/rainbowcookie.png",
+        "first_awarded": 1468278719.0, "first_awarded_person": "puiterwijk", "last_awarded_person":
+        "ralph", "last_awarded": 1486153659.0, "percent_earned": 0.011132386338335486,
+        "id": "rainbow-cookie-v", "times_awarded": 5, "name": "Rainbow (Cookie V)"}],
+        "percent_earned": 41.81818181818181, "user": "ralph"}'}
+    headers:
+      appserver: [proxy06.fedoraproject.org]
+      apptime: [D=44302106]
+      connection: [Keep-Alive]
+      content-length: ['74203']
+      content-type: [application/json; charset=UTF-8]
+      date: ['Thu, 06 Apr 2017 22:33:37 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+    status: {code: 200, message: OK}
+version: 1
diff --git a/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_fedmsgstats.TestFedmsgStats.test_view_authz b/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_fedmsgstats.TestFedmsgStats.test_view_authz
new file mode 100644
index 0000000..0681d72
--- /dev/null
+++ b/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_fedmsgstats.TestFedmsgStats.test_view_authz
@@ -0,0 +1,1051 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://apps.fedoraproject.org/datagrepper/raw?user=ralph
+  response:
+    body:
+      string: !!binary |
+        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
+    headers:
+      appserver: [proxy11.fedoraproject.org]
+      apptime: [D=260670723]
+      connection: [close]
+      content-encoding: [gzip]
+      content-type: [application/json]
+      date: ['Thu, 06 Apr 2017 22:43:06 GMT']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      vary: [Accept-Encoding]
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://apps.fedoraproject.org/datagrepper/raw?user=ralph
+  response:
+    body:
+      string: !!binary |
+        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
+    headers:
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=5811152]
+      connection: [Keep-Alive]
+      content-encoding: [gzip]
+      content-type: [application/json]
+      date: ['Thu, 06 Apr 2017 22:47:28 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      vary: [Accept-Encoding]
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://apps.fedoraproject.org/datagrepper/raw?user=ralph
+  response:
+    body:
+      string: !!binary |
+        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
+    headers:
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=2388762]
+      connection: [Keep-Alive]
+      content-encoding: [gzip]
+      content-type: [application/json]
+      date: ['Thu, 06 Apr 2017 22:47:34 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      vary: [Accept-Encoding]
+    status: {code: 200, message: OK}
+version: 1
diff --git a/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_meetings.TestMeetings.test_view_authz b/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_meetings.TestMeetings.test_view_authz
new file mode 100644
index 0000000..ec0fd82
--- /dev/null
+++ b/hubs/tests/vcr-request-data/hubs.tests.test_widgets.test_meetings.TestMeetings.test_view_authz
@@ -0,0 +1,304 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://apps.fedoraproject.org/calendar/api/meetings/?calendar=Fedora%20release
+  response:
+    body:
+      string: !!binary |
+        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
+    headers:
+      accept-ranges: [bytes]
+      age: ['0']
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=466769]
+      connection: [Keep-Alive]
+      content-encoding: [gzip]
+      content-type: [application/json]
+      date: ['Thu, 06 Apr 2017 22:47:43 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      set-cookie: ['fedocal=eyJfcGVybWFuZW50Ijp0cnVlfQ.C8hXEA.J-L3Q3D5WaWNhP3xXPbEcZs4hBU;
+          Expires=Thu, 06-Apr-2017 23:47:44 GMT; Secure; HttpOnly; Path=/calendar']
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      vary: [Accept-Encoding]
+      via: [1.1 varnish-v4]
+      x-varnish: ['14164337']
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://apps.fedoraproject.org/calendar/api/meetings/?calendar=Fedora%20release
+  response:
+    body:
+      string: !!binary |
+        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
+    headers:
+      accept-ranges: [bytes]
+      age: ['0']
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=422626]
+      connection: [Keep-Alive]
+      content-encoding: [gzip]
+      content-length: ['3975']
+      content-type: [application/json]
+      date: ['Thu, 06 Apr 2017 22:47:44 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      set-cookie: ['fedocal=eyJfcGVybWFuZW50Ijp0cnVlfQ.C8hXEQ.l-i-NQ-NFV7tfhfpF_Pjr_6LWaM;
+          Expires=Thu, 06-Apr-2017 23:47:45 GMT; Secure; HttpOnly; Path=/calendar']
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      vary: [Accept-Encoding]
+      via: [1.1 varnish-v4]
+      x-varnish: ['14488870']
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.13.0]
+    method: GET
+    uri: https://apps.fedoraproject.org/calendar/api/meetings/?calendar=Fedora%20release
+  response:
+    body:
+      string: !!binary |
+        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
+        AMTKs+Vs2AAA
+    headers:
+      accept-ranges: [bytes]
+      age: ['0']
+      appserver: [proxy04.fedoraproject.org]
+      apptime: [D=525518]
+      connection: [Keep-Alive]
+      content-encoding: [gzip]
+      content-type: [application/json]
+      date: ['Thu, 06 Apr 2017 22:47:45 GMT']
+      keep-alive: ['timeout=15, max=500']
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5]
+      set-cookie: ['fedocal=eyJfcGVybWFuZW50Ijp0cnVlfQ.C8hXEg.iAM2PfVJ078Ph-buEOa2ztll9hk;
+          Expires=Thu, 06-Apr-2017 23:47:46 GMT; Secure; HttpOnly; Path=/calendar']
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      vary: [Accept-Encoding]
+      via: [1.1 varnish-v4]
+      x-varnish: ['14164340']
+    status: {code: 200, message: OK}
+version: 1

From a4468aec867a7a43af53508994c692a0a41f289f Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 11 2017 12:51:54 +0000
Subject: [PATCH 6/9] Fix the height column issue in the config panel


---

diff --git a/hubs/static/client/app/components/TabSet.jsx b/hubs/static/client/app/components/TabSet.jsx
index df63568..c53bc31 100644
--- a/hubs/static/client/app/components/TabSet.jsx
+++ b/hubs/static/client/app/components/TabSet.jsx
@@ -39,14 +39,14 @@ export default class TabSet extends React.Component {
     }.bind(this));
 
     return (
-      <div>
+      <div {...this.props.tabWrapperProps}>
         <ul role="tablist"
-            className={"nav nav-tabs " + this.props.tabListClass || ""}
+            className={"nav nav-tabs " + this.props.tabListClass}
             >
           {tabLinks}
         </ul>
         <div
-          className={"tab-content " + this.props.tabContentClass || ""}
+          className={"tab-content " + this.props.tabContentClass}
           >
           {tabPanels}
         </div>
@@ -56,5 +56,16 @@ export default class TabSet extends React.Component {
 
 }
 
+TabSet.propTypes = {
+  tabWrapperProps: React.PropTypes.object,
+  tabListClass: React.PropTypes.string,
+  tabContentClass: React.PropTypes.string,
+};
+TabSet.defaultProps = {
+  tabWrapperProps: {},
+  tabListClass: '',
+  tabContentClass: '',
+};
+
 
 // vim: set ts=2 sw=2 et:
diff --git a/hubs/static/client/app/core/HubConfig.jsx b/hubs/static/client/app/core/HubConfig.jsx
index f4b75af..3732bd7 100644
--- a/hubs/static/client/app/core/HubConfig.jsx
+++ b/hubs/static/client/app/core/HubConfig.jsx
@@ -176,6 +176,10 @@ export default class HubConfig extends React.Component {
               <div className="modal-body">
                 <div className="row">
                   <TabSet
+                    tabWrapperProps={{
+                      className: "d-flex",
+                      style: {display: "flex"},
+                      }}
                     tabListClass="col-md-3"
                     tabContentClass="col-md-9"
                     >
diff --git a/hubs/static/css/style.css b/hubs/static/css/style.css
index 7ff066f..86e35f0 100644
--- a/hubs/static/css/style.css
+++ b/hubs/static/css/style.css
@@ -178,18 +178,6 @@
   padding-bottom: 2em;
 }
 
-/* Fixed vertical size to give the appearance of panels. Hackish. Find a CSS expert to do this properly. */
-#settings-modal .nav {
-  min-height: 400px;
-  max-height: 400px;
-}
-#settings-modal .tab-content {
-  min-height: 400px;
-  max-height: 400px;
-  overflow-y: auto;
-}
-
-
 
 /*
  * Widgets

From c8ff4f530bda14d7fc5bedb40c8afb1ac2d0fd9e Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 11 2017 12:51:54 +0000
Subject: [PATCH 7/9] Split the config panels into separate files


---

diff --git a/hubs/static/client/app/core/HubConfig.jsx b/hubs/static/client/app/core/HubConfig.jsx
index 3732bd7..fca9e33 100644
--- a/hubs/static/client/app/core/HubConfig.jsx
+++ b/hubs/static/client/app/core/HubConfig.jsx
@@ -4,12 +4,10 @@ import {
   defineMessages,
   FormattedMessage,
   } from 'react-intl';
-import {
-  GeneralPanel,
-  ChatPanel,
-  UserPanel,
-  NotImplementedPanel,
-  } from './HubConfigPanel.jsx';
+import GeneralPanel from './HubConfigPanelGeneral.jsx';
+import UserPanel from './HubConfigPanelUser.jsx';
+import ChatPanel from './HubConfigPanelChat.jsx';
+import NotImplementedPanel from './HubConfigPanelNotImpl.jsx';
 import TabSet from '../components/TabSet.jsx';
 
 
diff --git a/hubs/static/client/app/core/HubConfigPanel.jsx b/hubs/static/client/app/core/HubConfigPanel.jsx
deleted file mode 100644
index 865e340..0000000
--- a/hubs/static/client/app/core/HubConfigPanel.jsx
+++ /dev/null
@@ -1,388 +0,0 @@
-import React from 'react';
-import {
-  IntlProvider,
-  defineMessages,
-  FormattedMessage,
-  FormattedHTMLMessage,
-  } from 'react-intl';
-import TabPanel from '../components/TabPanel.jsx';
-
-
-const messages = defineMessages({
-  general_title: {
-    id: "hubs.core.config.general.title",
-    defaultMessage: "General Settings",
-  },
-  general_intro: {
-    id: "hubs.core.config.general.intro",
-    defaultMessage: "Change your global hub parameters here.",
-  },
-  general_summary: {
-    id: "hubs.core.config.general.summary",
-    defaultMessage: "Summary",
-  },
-  general_summary_help: {
-    id: "hubs.core.config.general.summary_help",
-    defaultMessage: "This text will be displayed at the top of the hub.",
-  },
-  general_left_width: {
-    id: "hubs.core.config.general.left_width",
-    defaultMessage: "Left width",
-  },
-  general_left_width_help: {
-    id: "hubs.core.config.general.left_width_help",
-    defaultMessage: (
-      "Change the proportions of you hub's columns by setting the width of "
-     +"the left column (the right column's width will be {right_width})."
-      ),
-  },
-  general_visibility: {
-    id: "hubs.core.config.general.visibility",
-    defaultMessage: "Visibility",
-  },
-  general_visibility_help: {
-    id: "hubs.core.config.general.visibility_help",
-    defaultMessage: (
-      "Public: the hub is visible to anyone.<br />"
-     +"Private: the hub is only visible to members.<br />"
-     +"Preview: some widgets can be made visible to logged-in users only."
-      ),
-  },
-  general_avatar: {
-    id: "hubs.core.config.general.avatar",
-    defaultMessage: "Avatar",
-  },
-  general_avatar_help: {
-    id: "hubs.core.config.general.avatar_help",
-    defaultMessage: "An URL to you hub's main image or logo."
-  },
-  chat_title: {
-    id: "hubs.core.config.chat.title",
-    defaultMessage: "Chat Settings",
-  },
-  chat_intro_1: {
-    id: "hubs.core.config.chat.intro1",
-    defaultMessage: (
-      "If your team or project has an IRC channel associated with it, you "
-     +"can connect this hub to it via the Hubs chat widget."
-     ),
-  },
-  chat_intro_2: {
-    id: "hubs.core.config.chat.intro2",
-    defaultMessage: "Please indicate your team's IRC channel below:",
-  },
-  chat_channel_name: {
-    id: "hubs.core.config.chat.channel_name",
-    defaultMessage: "Channel name",
-  },
-  chat_example: {
-    id: "hubs.core.config.chat.example",
-    defaultMessage: "Example:",
-  },
-  chat_network: {
-    id: "hubs.core.config.chat.network",
-    defaultMessage: "IRC network",
-  },
-  users_fullname: {
-    id: "hubs.core.config.users.fullname",
-    defaultMessage: "Full name",
-  },
-  users_username: {
-    id: "hubs.core.config.users.username",
-    defaultMessage: "Username",
-  },
-  users_change_role: {
-    id: "hubs.core.config.users.change_role",
-    defaultMessage: "Change role",
-  },
-  users_na: {
-    id: "hubs.core.config.users.na",
-    defaultMessage: "(N/A)",
-  },
-  users_remove_user: {
-    id: "hubs.core.config.users.remove_user",
-    defaultMessage: "(remove user)",
-  },
-  users_add_user: {
-    id: "hubs.core.config.users.add_user",
-    defaultMessage: "Add user...",
-  },
-  users_add: {
-    id: "hubs.core.config.users.add",
-    defaultMessage: "Add",
-  },
-});
-
-
-export class NotImplementedPanel extends React.Component {
-
-  render() {
-    return (
-      <div>
-        Not implemented yet.
-      </div>
-    );
-  }
-
-}
-
-
-export class GeneralPanel extends React.Component {
-
-  render() {
-    var stillLoading = (typeof this.props.hubConfig.summary === "undefined");
-    var visibilities = this.props.generalConfig.hub_visibility || [];
-    return (
-      <form onSubmit={(e) => {e.preventDefault();}}>
-        <FormattedMessage {...messages.general_title} tagName="h3" />
-        <FormattedMessage {...messages.general_intro} tagName="p" />
-        <div className="form-group">
-          <label htmlFor="hub-settings-general-summary">
-            <FormattedMessage {...messages.general_summary} />
-          </label>
-          <input
-            type="text" className="form-control" name="summary"
-            id="hub-settings-general-summary"
-            disabled={stillLoading}
-            onChange={this.props.handleChange}
-            value={this.props.hubConfig.summary || ""}
-            />
-          <p className="form-text text-muted">
-            <FormattedMessage {...messages.general_summary_help} />
-          </p>
-        </div>
-        <div className="form-group">
-          <label htmlFor="hub-settings-general-leftwidth">
-            <FormattedMessage {...messages.general_left_width} />
-          </label>
-          <input
-            type="number" className="form-control" name="left_width"
-            id="hub-settings-general-leftwidth"
-            disabled={stillLoading}
-            min="1" max="11"
-            onChange={this.props.handleChange}
-            value={this.props.hubConfig.left_width || ""}
-            />
-          <p className="form-text text-muted">
-            <FormattedMessage
-              {...messages.general_left_width_help}
-              values={{right_width: 12 - (this.props.hubConfig.left_width || 8)}}
-              />
-          </p>
-        </div>
-        <div className="form-group">
-          <label htmlFor="hub-settings-general-visibility">
-            <FormattedMessage {...messages.general_visibility} />
-          </label>
-          <select
-            className="form-control" name="visibility"
-            value={this.props.hubConfig.visibility || ""}
-            onChange={this.props.handleChange}
-            disabled={stillLoading}
-            >
-            {visibilities.map(function(value) {
-              return (
-                <option value={value} key={value}>{value}</option>
-                );
-            })}
-          </select>
-          <p className="form-text text-muted">
-            <FormattedHTMLMessage {...messages.general_visibility_help} />
-          </p>
-        </div>
-        <div className="form-group">
-          <label htmlFor="hub-settings-general-avatar">
-            <FormattedMessage {...messages.general_avatar} />
-          </label>
-          <input
-            type="text" className="form-control" name="avatar"
-            id="hub-settings-general-avatar"
-            disabled={stillLoading}
-            onChange={this.props.handleChange}
-            value={this.props.hubConfig.avatar || ""}
-            />
-          <p className="form-text text-muted">
-            <FormattedMessage {...messages.general_avatar_help} />
-          </p>
-        </div>
-      </form>
-    );
-  }
-
-}
-
-
-export class UserPanel extends React.Component {
-
-  constructor(props) {
-    super(props);
-    this.state = {
-      addUsername: "",
-    };
-    this.handleAdd = this.handleAdd.bind(this);
-    this.handleAddInputChange = this.handleAddInputChange.bind(this);
-    this.handleChange = this.handleChange.bind(this);
-  }
-
-  handleAdd(e) {
-    e.preventDefault();
-    this.props.handleChange(this.state.addUsername, this.props.role);
-    this.setState({addUsername: ""});
-  }
-
-  handleAddInputChange(e) {
-    this.setState({
-      addUsername: e.target.value,
-    });
-  }
-
-  handleChange(e) {
-    e.preventDefault();
-    this.props.handleChange(e.target.name, e.target.value);
-  }
-
-  render() {
-    var users = this.props.users.map(function(user) {
-      return (
-        <tr key={user.username}>
-          <td>{user.fullname}</td>
-          <td>{user.username}</td>
-          <td>
-            {user.locked ?
-              <FormattedMessage {...messages.users_na} />
-            :
-              <select
-                name={user.username}
-                value={user.role}
-                className="form-control"
-                onChange={this.handleChange}
-                disabled={this.props.loading}
-                >
-                {this.props.generalConfig.roles.map(function(role) {
-                  return (
-                    <option value={role} key={role}>{role}</option>
-                    );
-                })}
-                <FormattedMessage {...messages.users_remove_user}>
-                  {(value) => ( <option value="">{value}</option> )}
-                </FormattedMessage>
-              </select>
-            }
-          </td>
-        </tr>
-        );
-    }.bind(this));
-
-    return (
-      <div>
-        <h3>{this.props.tabTitle}</h3>
-        <p className="text-muted">[Membership requests will be shown here]</p>
-        <hr />
-        <form className="form-inline" onSubmit={this.handleAdd}
-              style={{marginBottom: "1em"}}>
-          <div className="form-group">
-            <FormattedMessage {...messages.users_add_user}>
-              {(value) => (
-                <input
-                  type="text" className="form-control"
-                  name="username" placeholder={value}
-                  onChange={this.handleAddInputChange}
-                  value={this.state.addUsername}
-                  style={{marginRight: "1em"}}
-                  />
-                )}
-            </FormattedMessage>
-          </div>
-          <button className="btn btn-primary" disabled={this.props.loading}>
-            <FormattedMessage {...messages.users_add} />
-          </button>
-        </form>
-        {users.length !== 0 &&
-        <table className="table table-hover table-sm">
-          <thead>
-            <tr>
-              <FormattedMessage {...messages.users_fullname} tagName="th" />
-              <FormattedMessage {...messages.users_username} tagName="th" />
-              <FormattedMessage {...messages.users_change_role} tagName="th" />
-            </tr>
-          </thead>
-          <tbody>
-            {users}
-          </tbody>
-        </table>
-        }
-      </div>
-    );
-  }
-
-}
-
-
-export class ChatPanel extends React.Component {
-
-  render() {
-    var networks = [],
-        channel = "#",
-        stillLoading = (
-          typeof this.props.hubConfig.chat_channel === "undefined"
-        );
-
-    if (this.props.hubConfig.chat_channel) {
-      channel = "#" + this.props.hubConfig.chat_channel.replace(/^#*/, "");
-    }
-
-    if (this.props.generalConfig.chat_networks) {
-      networks = this.props.generalConfig.chat_networks.map(
-        function(network, index) {
-          return (
-            <option value={network.domain} key={network.domain}>
-              {network.name} - {network.domain}
-            </option>
-          );
-        }.bind(this)
-      );
-    }
-
-    return (
-      <form onSubmit={(e) => {e.preventDefault();}}>
-        <FormattedMessage {...messages.chat_title} tagName="h3" />
-        <FormattedMessage {...messages.chat_intro_1} tagName="p" />
-        <FormattedMessage {...messages.chat_intro_2} tagName="p" />
-        <div className="form-group">
-          <label htmlFor="hub-settings-chat-channel">
-            <FormattedMessage {...messages.chat_channel_name} />
-          </label>
-          <input
-            type="text" className="form-control" name="chat_channel"
-            id="hub-settings-chat-channel"
-            disabled={stillLoading}
-            onChange={this.props.handleChange}
-            value={channel}
-            />
-          <p className="form-text text-muted">
-            <FormattedMessage {...messages.chat_example} tagName="strong" />
-            <span style={{marginLeft: "0.5em"}}>#fedora-devel</span>
-          </p>
-        </div>
-        <div className="form-group">
-          <label htmlFor="hub-settings-chat-domain">
-            <FormattedMessage {...messages.chat_network} />
-          </label>
-          <select
-            name="chat_domain" className="form-control"
-            id="hub-settings-chat-domain"
-            disabled={stillLoading}
-            onChange={this.props.handleChange}
-            value={this.props.hubConfig.chat_domain || ""}
-            >
-            {networks}
-          </select>
-        </div>
-      </form>
-    );
-  }
-
-}
-
-
-// vim: set ts=2 sw=2 et:
diff --git a/hubs/static/client/app/core/HubConfigPanelChat.jsx b/hubs/static/client/app/core/HubConfigPanelChat.jsx
new file mode 100644
index 0000000..458fd36
--- /dev/null
+++ b/hubs/static/client/app/core/HubConfigPanelChat.jsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import {
+  defineMessages,
+  FormattedMessage,
+  } from 'react-intl';
+
+
+const messages = defineMessages({
+  title: {
+    id: "hubs.core.config.chat.title",
+    defaultMessage: "Chat Settings",
+  },
+  intro_1: {
+    id: "hubs.core.config.chat.intro1",
+    defaultMessage: (
+      "If your team or project has an IRC channel associated with it, you "
+     +"can connect this hub to it via the Hubs chat widget."
+     ),
+  },
+  intro_2: {
+    id: "hubs.core.config.chat.intro2",
+    defaultMessage: "Please indicate your team's IRC channel below:",
+  },
+  channel_name: {
+    id: "hubs.core.config.chat.channel_name",
+    defaultMessage: "Channel name",
+  },
+  example: {
+    id: "hubs.core.config.chat.example",
+    defaultMessage: "Example:",
+  },
+  network: {
+    id: "hubs.core.config.chat.network",
+    defaultMessage: "IRC network",
+  },
+});
+
+
+export default class ChatPanel extends React.Component {
+
+  render() {
+    var networks = [],
+        channel = "#",
+        stillLoading = (
+          typeof this.props.hubConfig.chat_channel === "undefined"
+        );
+
+    if (this.props.hubConfig.chat_channel) {
+      channel = "#" + this.props.hubConfig.chat_channel.replace(/^#*/, "");
+    }
+
+    if (this.props.generalConfig.chat_networks) {
+      networks = this.props.generalConfig.chat_networks.map(
+        function(network, index) {
+          return (
+            <option value={network.domain} key={network.domain}>
+              {network.name} - {network.domain}
+            </option>
+          );
+        }.bind(this)
+      );
+    }
+
+    return (
+      <form onSubmit={(e) => {e.preventDefault();}}>
+        <FormattedMessage {...messages.title} tagName="h3" />
+        <FormattedMessage {...messages.intro_1} tagName="p" />
+        <FormattedMessage {...messages.intro_2} tagName="p" />
+        <div className="form-group">
+          <label htmlFor="hub-settings-chat-channel">
+            <FormattedMessage {...messages.channel_name} />
+          </label>
+          <input
+            type="text" className="form-control" name="chat_channel"
+            id="hub-settings-chat-channel"
+            disabled={stillLoading}
+            onChange={this.props.handleChange}
+            value={channel}
+            />
+          <p className="form-text text-muted">
+            <FormattedMessage {...messages.example} tagName="strong" />
+            <span style={{marginLeft: "0.5em"}}>#fedora-devel</span>
+          </p>
+        </div>
+        <div className="form-group">
+          <label htmlFor="hub-settings-chat-domain">
+            <FormattedMessage {...messages.network} />
+          </label>
+          <select
+            name="chat_domain" className="form-control"
+            id="hub-settings-chat-domain"
+            disabled={stillLoading}
+            onChange={this.props.handleChange}
+            value={this.props.hubConfig.chat_domain || ""}
+            >
+            {networks}
+          </select>
+        </div>
+      </form>
+    );
+  }
+
+}
+
+
+// vim: set ts=2 sw=2 et:
diff --git a/hubs/static/client/app/core/HubConfigPanelGeneral.jsx b/hubs/static/client/app/core/HubConfigPanelGeneral.jsx
new file mode 100644
index 0000000..15d53ab
--- /dev/null
+++ b/hubs/static/client/app/core/HubConfigPanelGeneral.jsx
@@ -0,0 +1,145 @@
+import React from 'react';
+import {
+  defineMessages,
+  FormattedMessage,
+  FormattedHTMLMessage,
+  } from 'react-intl';
+
+
+const messages = defineMessages({
+  title: {
+    id: "hubs.core.config.general.title",
+    defaultMessage: "General Settings",
+  },
+  intro: {
+    id: "hubs.core.config.general.intro",
+    defaultMessage: "Change your global hub parameters here.",
+  },
+  summary: {
+    id: "hubs.core.config.general.summary",
+    defaultMessage: "Summary",
+  },
+  summary_help: {
+    id: "hubs.core.config.general.summary_help",
+    defaultMessage: "This text will be displayed at the top of the hub.",
+  },
+  left_width: {
+    id: "hubs.core.config.general.left_width",
+    defaultMessage: "Left width",
+  },
+  left_width_help: {
+    id: "hubs.core.config.general.left_width_help",
+    defaultMessage: (
+      "Change the proportions of you hub's columns by setting the width of "
+     +"the left column (the right column's width will be {right_width})."
+      ),
+  },
+  visibility: {
+    id: "hubs.core.config.general.visibility",
+    defaultMessage: "Visibility",
+  },
+  visibility_help: {
+    id: "hubs.core.config.general.visibility_help",
+    defaultMessage: (
+      "Public: the hub is visible to anyone.<br />"
+     +"Private: the hub is only visible to members.<br />"
+     +"Preview: some widgets can be made visible to logged-in users only."
+      ),
+  },
+  avatar: {
+    id: "hubs.core.config.general.avatar",
+    defaultMessage: "Avatar",
+  },
+  avatar_help: {
+    id: "hubs.core.config.general.avatar_help",
+    defaultMessage: "An URL to you hub's main image or logo."
+  },
+});
+
+
+export default class GeneralPanel extends React.Component {
+
+  render() {
+    var stillLoading = (typeof this.props.hubConfig.summary === "undefined");
+    var visibilities = this.props.generalConfig.hub_visibility || [];
+    return (
+      <form onSubmit={(e) => {e.preventDefault();}}>
+        <FormattedMessage {...messages.title} tagName="h3" />
+        <FormattedMessage {...messages.intro} tagName="p" />
+        <div className="form-group">
+          <label htmlFor="hub-settings-general-summary">
+            <FormattedMessage {...messages.summary} />
+          </label>
+          <input
+            type="text" className="form-control" name="summary"
+            id="hub-settings-general-summary"
+            disabled={stillLoading}
+            onChange={this.props.handleChange}
+            value={this.props.hubConfig.summary || ""}
+            />
+          <p className="form-text text-muted">
+            <FormattedMessage {...messages.summary_help} />
+          </p>
+        </div>
+        <div className="form-group">
+          <label htmlFor="hub-settings-general-leftwidth">
+            <FormattedMessage {...messages.left_width} />
+          </label>
+          <input
+            type="number" className="form-control" name="left_width"
+            id="hub-settings-general-leftwidth"
+            disabled={stillLoading}
+            min="1" max="11"
+            onChange={this.props.handleChange}
+            value={this.props.hubConfig.left_width || ""}
+            />
+          <p className="form-text text-muted">
+            <FormattedMessage
+              {...messages.left_width_help}
+              values={{right_width: 12 - (this.props.hubConfig.left_width || 8)}}
+              />
+          </p>
+        </div>
+        <div className="form-group">
+          <label htmlFor="hub-settings-general-visibility">
+            <FormattedMessage {...messages.visibility} />
+          </label>
+          <select
+            className="form-control" name="visibility"
+            value={this.props.hubConfig.visibility || ""}
+            onChange={this.props.handleChange}
+            disabled={stillLoading}
+            >
+            {visibilities.map(function(value) {
+              return (
+                <option value={value} key={value}>{value}</option>
+                );
+            })}
+          </select>
+          <p className="form-text text-muted">
+            <FormattedHTMLMessage {...messages.visibility_help} />
+          </p>
+        </div>
+        <div className="form-group">
+          <label htmlFor="hub-settings-general-avatar">
+            <FormattedMessage {...messages.avatar} />
+          </label>
+          <input
+            type="text" className="form-control" name="avatar"
+            id="hub-settings-general-avatar"
+            disabled={stillLoading}
+            onChange={this.props.handleChange}
+            value={this.props.hubConfig.avatar || ""}
+            />
+          <p className="form-text text-muted">
+            <FormattedMessage {...messages.avatar_help} />
+          </p>
+        </div>
+      </form>
+    );
+  }
+
+}
+
+
+// vim: set ts=2 sw=2 et:
diff --git a/hubs/static/client/app/core/HubConfigPanelNotImpl.jsx b/hubs/static/client/app/core/HubConfigPanelNotImpl.jsx
new file mode 100644
index 0000000..31803be
--- /dev/null
+++ b/hubs/static/client/app/core/HubConfigPanelNotImpl.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+
+export default class NotImplementedPanel extends React.Component {
+
+  render() {
+    return (
+      <div>
+        Not implemented yet.
+      </div>
+    );
+  }
+
+}
+
+
+// vim: set ts=2 sw=2 et:
diff --git a/hubs/static/client/app/core/HubConfigPanelUser.jsx b/hubs/static/client/app/core/HubConfigPanelUser.jsx
new file mode 100644
index 0000000..d913c79
--- /dev/null
+++ b/hubs/static/client/app/core/HubConfigPanelUser.jsx
@@ -0,0 +1,147 @@
+import React from 'react';
+import {
+  defineMessages,
+  FormattedMessage,
+  } from 'react-intl';
+import CompletionInput from '../components/CompletionInput.jsx';
+
+
+const messages = defineMessages({
+  fullname: {
+    id: "hubs.core.config.users.fullname",
+    defaultMessage: "Full name",
+  },
+  username: {
+    id: "hubs.core.config.users.username",
+    defaultMessage: "Username",
+  },
+  change_role: {
+    id: "hubs.core.config.users.change_role",
+    defaultMessage: "Change role",
+  },
+  na: {
+    id: "hubs.core.config.users.na",
+    defaultMessage: "(N/A)",
+  },
+  remove_user: {
+    id: "hubs.core.config.users.remove_user",
+    defaultMessage: "(remove user)",
+  },
+  add_user: {
+    id: "hubs.core.config.users.add_user",
+    defaultMessage: "Add user...",
+  },
+  add: {
+    id: "hubs.core.config.users.add",
+    defaultMessage: "Add",
+  },
+});
+
+
+export default class UserPanel extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      addUsername: "",
+    };
+    this.handleAdd = this.handleAdd.bind(this);
+    this.handleAddInputChange = this.handleAddInputChange.bind(this);
+    this.handleChange = this.handleChange.bind(this);
+  }
+
+  handleAdd(e) {
+    e.preventDefault();
+    this.props.handleChange(this.state.addUsername, this.props.role);
+    this.setState({addUsername: ""});
+  }
+
+  handleAddInputChange(e) {
+    this.setState({
+      addUsername: e.target.value,
+    });
+  }
+
+  handleChange(e) {
+    e.preventDefault();
+    this.props.handleChange(e.target.name, e.target.value);
+  }
+
+  render() {
+    var users = this.props.users.map(function(user) {
+      return (
+        <tr key={user.username}>
+          <td>{user.fullname}</td>
+          <td>{user.username}</td>
+          <td>
+            {user.locked ?
+              <FormattedMessage {...messages.na} />
+            :
+              <select
+                name={user.username}
+                value={user.role}
+                className="form-control"
+                onChange={this.handleChange}
+                disabled={this.props.loading}
+                >
+                {this.props.generalConfig.roles.map(function(role) {
+                  return (
+                    <option value={role} key={role}>{role}</option>
+                    );
+                })}
+                <FormattedMessage {...messages.remove_user}>
+                  {(value) => ( <option value="">{value}</option> )}
+                </FormattedMessage>
+              </select>
+            }
+          </td>
+        </tr>
+        );
+    }.bind(this));
+
+    return (
+      <div>
+        <h3>{this.props.tabTitle}</h3>
+        <p className="text-muted">[Membership requests will be shown here]</p>
+        <hr />
+        <form className="form-inline" onSubmit={this.handleAdd}
+              style={{marginBottom: "1em"}}>
+          <div className="form-group">
+            <FormattedMessage {...messages.add_user}>
+              {(value) => (
+                <input
+                  type="text" className="form-control"
+                  name="username" placeholder={value}
+                  onChange={this.handleAddInputChange}
+                  value={this.state.addUsername}
+                  style={{marginRight: "1em"}}
+                  />
+                )}
+            </FormattedMessage>
+          </div>
+          <button className="btn btn-primary" disabled={this.props.loading}>
+            <FormattedMessage {...messages.add} />
+          </button>
+        </form>
+        {users.length !== 0 &&
+        <table className="table table-hover table-sm">
+          <thead>
+            <tr>
+              <FormattedMessage {...messages.fullname} tagName="th" />
+              <FormattedMessage {...messages.username} tagName="th" />
+              <FormattedMessage {...messages.change_role} tagName="th" />
+            </tr>
+          </thead>
+          <tbody>
+            {users}
+          </tbody>
+        </table>
+        }
+      </div>
+    );
+  }
+
+}
+
+
+// vim: set ts=2 sw=2 et:

From 2cb5e8bcd7d8f8bab9e40d36e3ae411989d09b6c Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 11 2017 12:51:54 +0000
Subject: [PATCH 8/9] Suggest users in the config panel


---

diff --git a/hubs/static/client/app/components/CompletionInput.jsx b/hubs/static/client/app/components/CompletionInput.jsx
index 9b8b581..a97fc7f 100644
--- a/hubs/static/client/app/components/CompletionInput.jsx
+++ b/hubs/static/client/app/components/CompletionInput.jsx
@@ -20,7 +20,6 @@ export default class CompletionInput extends React.Component {
     this.onChange = this.onChange.bind(this);
     this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this);
     this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this);
-    this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
     this.loadFromServer = this.loadFromServer.bind(this);
   }
 
@@ -39,20 +38,18 @@ export default class CompletionInput extends React.Component {
     this.setState({
       value: newValue
     });
-  }
-
-  onSuggestionSelected(e, {suggestionValue}) {
     // Transmit a "fake" changed event to the parent, if asked to.
-    if (!this.props.onChange) { return; }
-    const mockedEvent = {
-      type: 'change',
-      target: {
-        type: 'input',
-        name: this.props.name,
-        value: suggestionValue,
-      },
+    if (this.props.onChange) {
+      const mockedEvent = {
+        type: 'change',
+        target: {
+          type: 'input',
+          name: this.props.name,
+          value: newValue,
+        },
+      }
+      this.props.onChange(mockedEvent);
     }
-    this.props.onChange(mockedEvent);
   }
 
   // Autosuggest will call this function every time you need to update
@@ -83,13 +80,16 @@ export default class CompletionInput extends React.Component {
       dataType: 'json',
       cache: false,
       success: function(data) {
-        this.setState({
-          suggestions: data.results,
-          loading: false
-        });
+        var suggestions = data.results;
+        if (this.props.filterResults) {
+          suggestions = suggestions.filter(this.props.filterResults);
+        }
+        this.setState({suggestions: suggestions});
       }.bind(this),
       error: function(xhr, status, err) {
         console.error(this.props.url, status, err.toString());
+      }.bind(this),
+      complete: function(xhr, status) {
         this.setState({loading: false});
       }.bind(this)
     });
@@ -111,13 +111,11 @@ export default class CompletionInput extends React.Component {
     const renderSuggestion = suggestion => suggestion;
     // Autosuggest will pass through all these props to the input element.
     const inputProps = {
-      //placeholder: 'Type a programming language',
-      name: this.props.name,
       className: "form-control",
       value: this.state.value,
-      placeholder: this.props.placeholder,
       onChange: this.onChange,
     };
+    Object.assign(inputProps, this.props.inputProps);
 
     // Render the widget.
     return (
@@ -126,23 +124,21 @@ export default class CompletionInput extends React.Component {
           suggestions={this.state.suggestions}
           onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
           onSuggestionsClearRequested={this.onSuggestionsClearRequested}
-          onSuggestionSelected={this.onSuggestionSelected}
           getSuggestionValue={getSuggestionValue}
           renderSuggestion={renderSuggestion}
           inputProps={inputProps}
         />
-        { this.state.loading ?
-          <span className="form-control-feedback">
-            <img src={this.props.spinner} />
-          </span>
-        : null }
+        <div
+          className="loading-circle"
+          style={this.state.loading ? {display: "block"} : {}}
+          ></div>
       </div>
     );
   }
 
 }
 CompletionInput.defaultProps = {
-    placeholder: "",
+    inputProps: {},
     queryDelay: 800,
 };
 
diff --git a/hubs/static/client/app/core/HubConfig.jsx b/hubs/static/client/app/core/HubConfig.jsx
index fca9e33..f3993d7 100644
--- a/hubs/static/client/app/core/HubConfig.jsx
+++ b/hubs/static/client/app/core/HubConfig.jsx
@@ -70,7 +70,7 @@ export default class HubConfig extends React.Component {
 
   getRequestParams() {
     return {
-      url: this.props.url,
+      url: this.props.urls.config,
       dataType: 'json',
       cache: false,
       success: function(data) {
@@ -93,7 +93,7 @@ export default class HubConfig extends React.Component {
       error: function(xhr, status, err) {
         var msg = err.toString();
         if (!msg) { msg = "Error communicating with the server"; }
-        console.error(this.props.url, status, msg);
+        console.error(this.props.urls.config, status, msg);
         this.setState({error: msg});
       }.bind(this),
       complete: function(xhr, status) {
@@ -133,7 +133,6 @@ export default class HubConfig extends React.Component {
   }
 
   handleUserChange(username, role) {
-    console.log("new role", username, role);
     var data = {username: username, role: role};
     this.pushState(data, "role_change");
   }
@@ -195,6 +194,7 @@ export default class HubConfig extends React.Component {
                       handleChange={this.handleUserChange}
                       tabTitle={<FormattedMessage {...messages.owners} />}
                       loading={this.state.loading}
+                      urls={this.props.urls}
                       />
                     <UserPanel
                       users={this.state.users.member}
@@ -203,6 +203,7 @@ export default class HubConfig extends React.Component {
                       handleChange={this.handleUserChange}
                       tabTitle={<FormattedMessage {...messages.members} />}
                       loading={this.state.loading}
+                      urls={this.props.urls}
                       />
                     <ChatPanel
                       hubConfig={this.state.hubConfig}
diff --git a/hubs/static/client/app/core/HubConfigPanelUser.jsx b/hubs/static/client/app/core/HubConfigPanelUser.jsx
index d913c79..3565760 100644
--- a/hubs/static/client/app/core/HubConfigPanelUser.jsx
+++ b/hubs/static/client/app/core/HubConfigPanelUser.jsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import {
+  injectIntl,
   defineMessages,
   FormattedMessage,
   } from 'react-intl';
@@ -48,6 +49,7 @@ export default class UserPanel extends React.Component {
     this.handleAdd = this.handleAdd.bind(this);
     this.handleAddInputChange = this.handleAddInputChange.bind(this);
     this.handleChange = this.handleChange.bind(this);
+    this.filterSuggestedUsers = this.filterSuggestedUsers.bind(this);
   }
 
   handleAdd(e) {
@@ -67,6 +69,11 @@ export default class UserPanel extends React.Component {
     this.props.handleChange(e.target.name, e.target.value);
   }
 
+  filterSuggestedUsers(user) {
+    // Only suggest non-members.
+    return (this.props.users.indexOf(user) === -1);
+  }
+
   render() {
     var users = this.props.users.map(function(user) {
       return (
@@ -99,27 +106,58 @@ export default class UserPanel extends React.Component {
         );
     }.bind(this));
 
+    const IntlCompletionInput = injectIntl(function(props) {
+        return (
+          <CompletionInput
+            inputProps={{
+              name: "username",
+              placeholder: props.intl.formatMessage(messages.add_user),
+            }}
+            url={(
+              props.urls.suggestUsers
+              + "?exclude-role=" + props.role
+            )}
+            onChange={this.handleAddInputChange}
+            />
+            );
+    }.bind(this));
+
     return (
       <div>
         <h3>{this.props.tabTitle}</h3>
-        <p className="text-muted">[Membership requests will be shown here]</p>
-        <hr />
+        {(this.props.role == "member") &&
+          <div>
+            <p className="text-muted">
+              [Membership requests will be shown here]
+            </p>
+            <hr />
+          </div>
+        }
         <form className="form-inline" onSubmit={this.handleAdd}
               style={{marginBottom: "1em"}}>
           <div className="form-group">
             <FormattedMessage {...messages.add_user}>
-              {(value) => (
-                <input
-                  type="text" className="form-control"
-                  name="username" placeholder={value}
+              {(message) => (
+                <CompletionInput
+                  inputProps={{
+                    name: "username",
+                    placeholder: message,
+                    value: this.state.addUsername,
+                  }}
+                  url={(
+                    this.props.urls.suggestUsers
+                    + "?exclude-role=" + this.props.role
+                  )}
                   onChange={this.handleAddInputChange}
-                  value={this.state.addUsername}
-                  style={{marginRight: "1em"}}
                   />
-                )}
+              )}
             </FormattedMessage>
-          </div>
-          <button className="btn btn-primary" disabled={this.props.loading}>
+            </div>
+          <button
+            className="btn btn-primary"
+            disabled={this.props.loading}
+            style={{marginLeft: "1em"}}
+            >
             <FormattedMessage {...messages.add} />
           </button>
         </form>
diff --git a/hubs/static/client/app/widgets/halp/Config.jsx b/hubs/static/client/app/widgets/halp/Config.jsx
index 9e81477..4a65107 100644
--- a/hubs/static/client/app/widgets/halp/Config.jsx
+++ b/hubs/static/client/app/widgets/halp/Config.jsx
@@ -125,10 +125,11 @@ export default class Config extends React.Component {
 
     const IntlCompletionInput = injectIntl((props) => (
         <CompletionInput
-          name="hub-add"
+          inputProps={{
+            name: "hub-add",
+            placeholder: props.intl.formatMessage(messages.placeholder),
+          }}
           url={props.urls.hubs}
-          spinner={props.urls.spinnerCircle}
-          placeholder={props.intl.formatMessage(messages.placeholder)}
           />
     ));
 
diff --git a/hubs/static/client/app/widgets/halp/ModalAllRequests.jsx b/hubs/static/client/app/widgets/halp/ModalAllRequests.jsx
index c3d13cd..cbb24b3 100644
--- a/hubs/static/client/app/widgets/halp/ModalAllRequests.jsx
+++ b/hubs/static/client/app/widgets/halp/ModalAllRequests.jsx
@@ -176,18 +176,25 @@ export default class ModalAllRequests extends Modal {
             </label>
             <div className="col-sm-8">
               <CompletionInput
-                name="people"
+                inputProps={{name: "people"}}
                 url={this.props.urls.requesters}
-                spinner={this.props.urls.spinnerCircle}
-                onChange={this.handleChange} />
+                onChange={this.handleChange}
+                />
             </div>
           </div>
           <div className="form-group">
             <div className="col-sm-offset-4 col-sm-8">
-              <button type="submit" className="btn btn-primary">
+              <button
+                type="submit"
+                className="btn btn-primary"
+                style={{marginRight: "1em"}}
+                >
                 <FormattedMessage {...messages.search_button} />
               </button>
-              { this.state.loading && <img style={{marginLeft: "2em"}} src={this.props.urls.spinner} /> }
+              <div
+                className="loading-circle"
+                style={this.state.loading ? {display: "inline-block"} : {}}
+                ></div>
             </div>
           </div>
         </form>
diff --git a/hubs/static/client/app/widgets/halp/Widget.jsx b/hubs/static/client/app/widgets/halp/Widget.jsx
index 71234a4..91a2fe8 100644
--- a/hubs/static/client/app/widgets/halp/Widget.jsx
+++ b/hubs/static/client/app/widgets/halp/Widget.jsx
@@ -147,7 +147,10 @@ export default class Widget extends React.Component {
             onHubClick={this.onHubClick}
             />
           { this.state.loading ?
-              <img className="center-block" src={this.props.urls.spinner} />
+              <div
+                className="loading-circle center-block"
+                style={{display: "block"}}
+                ></div>
             :
               <div className="requests">
                 {requestNodes}
diff --git a/hubs/static/css/style.css b/hubs/static/css/style.css
index 86e35f0..ec6f499 100644
--- a/hubs/static/css/style.css
+++ b/hubs/static/css/style.css
@@ -141,6 +141,17 @@
       display: none;  /* hidden by default */
   }
 
+.loading-circle {
+  width: 32px;
+  height: 32px;
+  line-height: 32px;
+  background: url("../img/spinner-circle.gif") no-repeat center center;
+  vertical-align: bottom;
+  display: none;  /* hidden by default */
+}
+
+
+
 /*
  * Settings
  */
@@ -442,47 +453,36 @@ font-size: 32pt;
 /*
  * Autosuggest
  */
+.completion-input {
+  position: relative;
+}
 .react-autosuggest__suggestions-container {
-    position: absolute;
-    z-index: 10;
-    width: 100%;
-    padding-right: 2em;
+  position: absolute;
+  width: 100%;
+  z-index: 10;
+}
+.react-autosuggest__suggestions-list {
+  list-style: none;
+  padding: 0.3em 0.8em;
+  background-color: #fafafa;
+  border-bottom-left-radius: .25rem;
+  border-bottom-right-radius: .25rem;
+  border: 1px solid #ccc;
+  border-top: none;
+}
+.react-autosuggest__suggestion {
+  cursor: pointer;
 }
-.react-autosuggest__suggestions-container ul {
-    list-style: none;
-    padding: 0.3em 0.8em;
-    background-color: #eee;
-    border-bottom-left-radius: 4px;
-    border-bottom-right-radius: 4px;
+.react-autosuggest__container--open input {
+  border-bottom-left-radius: 0px;
+  border-bottom-right-radius: 0px;
 }
 
-.form-control-feedback {
+.completion-input .loading-circle {
   position: absolute;
   top: 0;
   right: 0;
   z-index: 2;
-  display: block;
-  width: 34px;
-  height: 34px;
-  line-height: 34px;
-  text-align: center;
-  pointer-events: none;
-}
-.input-lg + .form-control-feedback {
-  width: 46px;
-  height: 46px;
-  line-height: 46px;
-}
-.input-sm + .form-control-feedback {
-  width: 30px;
-  height: 30px;
-  line-height: 30px;
-}
-.form-horizontal .form-control-feedback {
-  right: 15px;
-}
-.form-inline .form-control-feedback {
-  top: 0;
 }
 
 
diff --git a/hubs/templates/hubs.html b/hubs/templates/hubs.html
index 35ab48a..2851568 100644
--- a/hubs/templates/hubs.html
+++ b/hubs/templates/hubs.html
@@ -390,7 +390,10 @@ $(function() {
   setup_widgets();
   setup_edit_btns();
   setup_settings({
-    url: {{ url_for("hub_config", name=hub.name)|tojson }},
+    urls: {
+        config: {{ url_for("hub_config", name=hub.name)|tojson }},
+        suggestUsers: {{ url_for("hub_config_suggest_users", name=hub.name)|tojson }},
+    }
   });
 
   {% if edit -%}
diff --git a/hubs/tests/test_fedora_hubs_flask_api.py b/hubs/tests/test_fedora_hubs_flask_api.py
index a0a0d62..ed58d25 100644
--- a/hubs/tests/test_fedora_hubs_flask_api.py
+++ b/hubs/tests/test_fedora_hubs_flask_api.py
@@ -527,3 +527,61 @@ class HubsAPITest(hubs.tests.APPTest):
             url = '/decause/config?category=config'
             result = self.app.post(url, data={"summary": "Defaced!"})
         self.assertEqual(result.status_code, 403)
+
+    def test_hub_config_suggest_users_no_filter(self):
+        user = tests.FakeAuthorization('ralph')
+        expected = [
+            u.username for u in
+            hubs.models.User.query.order_by(
+                hubs.models.User.username
+            ).all()]
+        # Check without filter
+        with tests.auth_set(app, user):
+            url = '/ralph/config/suggest-users'
+            result = self.app.get(url)
+        self.assertEqual(result.status_code, 200)
+        result_data = json.loads(result.get_data(as_text=True))
+        self.assertEqual(result_data["status"], "OK")
+        self.assertListEqual(result_data["results"], expected)
+
+    def test_hub_config_suggest_users_filter_owners(self):
+        # Filters on owners
+        user = tests.FakeAuthorization('ralph')
+        expected = [
+            u.username for u in
+            hubs.models.User.query.order_by(
+                hubs.models.User.username
+            ).filter(
+                hubs.models.User.username != "ralph"
+            ).all()]
+        with tests.auth_set(app, user):
+            url = '/ralph/config/suggest-users?exclude-role=owner'
+            result = self.app.get(url)
+        self.assertEqual(result.status_code, 200)
+        result_data = json.loads(result.get_data(as_text=True))
+        self.assertEqual(result_data["status"], "OK")
+        self.assertListEqual(result_data["results"], expected)
+
+    def test_hub_config_suggest_users_filter_members(self):
+        # Filters on members
+        user = tests.FakeAuthorization('ralph')
+        hub = hubs.models.Hub.get('ralph')
+        decause = hubs.models.User.query.get("decause")
+        devyani7 = hubs.models.User.query.get("devyani7")
+        hub.subscribe(decause, "member")
+        hub.subscribe(devyani7, "member")
+        expected = [
+            u.username for u in
+            hubs.models.User.query.order_by(
+                hubs.models.User.username
+            ).filter(
+                hubs.models.User.username != "decause",
+                hubs.models.User.username != "devyani7"
+            ).all()]
+        with tests.auth_set(app, user):
+            url = '/ralph/config/suggest-users?exclude-role=member'
+            result = self.app.get(url)
+        self.assertEqual(result.status_code, 200)
+        result_data = json.loads(result.get_data(as_text=True))
+        self.assertEqual(result_data["status"], "OK")
+        self.assertListEqual(result_data["results"], expected)
diff --git a/hubs/views/hub.py b/hubs/views/hub.py
index c3ea5f4..d6d05f7 100644
--- a/hubs/views/hub.py
+++ b/hubs/views/hub.py
@@ -246,3 +246,37 @@ def hub_config_post_role_change(hub):
         hub.associations.append(hubs.models.Association(
             hub=hub, user=user, role=role))
     flask.g.db.commit()
+
+
+@app.route('/<name>/config/suggest-users')
+@login_required
+@require_hub_access("config", json=True)
+def hub_config_suggest_users(name):
+    MAX_SUGGESTS = 10
+    hub = get_hub(name)
+    results = flask.g.db.query(hubs.models.User.username)
+    query = flask.request.args.get("q")
+    if query:
+        results = results.filter(
+            hubs.models.User.username.ilike("%{}%".format(query))
+        )
+    exclude = flask.request.args.get("exclude-role")
+    if exclude:
+        exclude_query = flask.g.db.query(
+                hubs.models.User.username
+            ).join(
+                hubs.models.Association
+            ).join(
+                hubs.models.Hub
+            ).filter(
+                hubs.models.Hub.name == hub.name,
+                hubs.models.Association.role == exclude,
+            )
+        results = results.filter(
+            ~hubs.models.User.username.in_(exclude_query)
+            )
+    results = results.order_by(hubs.models.User.username).limit(MAX_SUGGESTS)
+    return flask.jsonify({
+        "status": "OK",
+        "results": [r[0] for r in results],
+        })

From 122c7bb78d71e55053e00c9a093fe3624c884c1d Mon Sep 17 00:00:00 2001
From: Aurélien Bompard <aurelien@bompard.org>
Date: Apr 11 2017 12:51:55 +0000
Subject: [PATCH 9/9] Implement review suggestions


---

diff --git a/docs/api/auth.rst b/docs/api/auth.rst
index 36b5cf3..c5e8382 100644
--- a/docs/api/auth.rst
+++ b/docs/api/auth.rst
@@ -1,4 +1,4 @@
-Authentication and authorization
+Authentication and Authorization
 ================================
 
 Authentication is the fact of checking that the user is who they say they are.

