#302 Document Fedora Hubs Widgets
Closed 7 years ago by abompard. Opened 7 years ago by jcline.
jcline/fedora-hubs document-widgets  into  develop

file added
+13
@@ -0,0 +1,13 @@ 

+ Developer Interfaces

+ ====================

+ 

+ This documents ways developers can interface with Fedora Hubs.

+ 

+ .. _widgets-api:

+ 

+ Widgets

+ -------

+ 

+ .. automodule:: hubs.widgets

+    :members:

+    :show-inheritance:

file modified
+10 -5
@@ -16,20 +16,25 @@ 

  # add these directories to sys.path here. If the directory is relative to the

  # documentation root, use os.path.abspath to make it absolute, like shown here.

  #

- # import os

- # import sys

- # sys.path.insert(0, os.path.abspath('.'))

+ import os

+ import sys

+ sys.path.insert(0, os.path.abspath('../'))

  

  # -- General configuration ------------------------------------------------

  

  # If your documentation needs a minimal Sphinx version, state it here.

  #

- # needs_sphinx = '1.0'

+ needs_sphinx = '1.3'

  

  # Add any Sphinx extension module names here, as strings. They can be

  # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom

  # ones.

- extensions = []

+ extensions = [

+     'sphinx.ext.autodoc',

+     'sphinx.ext.doctest',

+     'sphinx.ext.napoleon',

+     'sphinx.ext.viewcode',

+ ]

  

  # Add any paths that contain templates here, relative to this directory.

  templates_path = ['_templates']

file modified
+4 -25
@@ -344,31 +344,10 @@ 

  Stubbing out a new Widget

  =========================

  

- You write a new widget in the ``hubs/widgets/`` directory and must declare it

- in the registry dict in ``hubs/widgets/__init__.py``.

- 

- In order to be valid, a widget must have:

- 

- - A ``data(session, widgets, **kwargs)`` function that returns a

-   jsonifiable dict of data.  This will get cached -- more on that later.

- - A ``template`` object that is a jinja2 template for that widget.

- - Optionally, a ``chrome`` decorator.

- - A ``should_invalidate(message, session, widget)`` function that will be used to

-   *potentially* invalidate the widget's cache. That function will get called by

-   a backend daemon listening for fedmsg messages so when you update your group

-   memberships in FAS, a fedmsg message hits the fedora-hubs backend and returns

-   True if the lookup value should be nuked/refreshed in memcached (or some

-   other store).

- 

- If you want to try making a new widget:

- 

- - Copy an existing one in ``hubs/widgets/``

- - Add it to the registry in ``hubs/widgets/__init__.py``

- - If you want it to show up on a **user** page, add it to ``hubs/defaults.py``

-   in the ``add_user_widgets`` function.

- - If you want it to show up on **group** pages, add it to ``populate.py``.

- 

- Destroy your database, rebuild it, and re-run the app.  Your widget should show up.

+ Widgets can be added and removed from a Hub to provide a customized experience

+ for each user. To learn how to implement a new widget, consult the

+ :ref:`widgets-api` documentation.

+ 

  

  Widget-specific views

  ---------------------

file modified
+1
@@ -13,6 +13,7 @@ 

  

     overview

     dev-guide

+    api

  

  

  Indices and tables

file modified
+120 -1
@@ -1,3 +1,42 @@ 

+ # -*- coding: utf-8 -*-

+ # This file is part of Fedora Hubs.

+ #

+ # Copyright (C) 2015 Dhriti Shikhar

+ # Copyright (C) 2015 Ralph Bean

+ # Copyright (C) 2015 Remy DeCausemaker

+ # Copyright (C) 2016 Pierre-Yves Chibon

+ # Copyright (C) 2017 Jeremy Cline

+ #

+ # 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/>.

+ """

+ Widgets are the reusable building blocks of Fedora Hubs.

+ 

+ To implement a new widget, add a new module in :mod:`hubs.widgets` package that

+ implements the interface defined in :class:`hubs.widgets.Widget`. Then, add

+ that module to the :data:`hubs.widgets.registry` dictionary.

+ 

+ If you would like it to show up in a user hub by default, add it to

+ :mod:`hubs.defaults`.

+ 

+ If you wish to add it to a group page in the development environment, add your

+ new widget to the ``populate.py`` script in the repository root. Next destroy

+ your database, rebuild it, and re-run the app.  Your widget should show up.

+ 

+ Attributes:

+     registry (dict): This dictionary is a registry of available widgets in

+         Fedora Hubs.

+ """

  from __future__ import unicode_literals

  

  from hubs.widgets import dummy
@@ -120,7 +159,14 @@ 

  def render(module, session, widget, *args, **kwargs):

      """ Main API entry point.

  

-     Call this to render a widget into HTML

+     Call this to render a widget into HTML.

+ 

+     Args:

+         module: An object that implements the Widget interface

+         session (sqlalchemy.orm.scoping.scoped_session): An SQLAlchemy session

+             to use when querying the database.

+         widget (hubs.models.Widget): The Widget instance to render

+ 

      """

      # The API returns exactly this data.  Shared cache

      data = module.data(session, widget, *args, **kwargs)
@@ -130,3 +176,76 @@ 

  

      # Use the API data to fill out a template, and potentially decorate it.

      return module.render(**data)

+ 

+ 

+ class Widget(object):

+     """

+     This is the widget interface all widgets must implement.

+ 

+     In order to implement a new widget in Fedora Hubs, you should subclass this

+     class, implement its methods, and set the required class attributes. You

+     must then register the :class:`Widget` in the Widget registry. The widget

+     registry is a simple dictionary found at :func:`hubs.widgets.registry`.

+ 

+     Attributes:

+         template (jinja2.Template): The `Jinja2 template`_ for this widget.

+         position (str): The position of the widget in the rendered page. It

+             should be one of the following values: 'left', 'right', or 'both'.

+         render (callable): This attribute is set to ``template.render``

+             automatically and should be a callable that returns some HTML for

+             this widget.

+ 

+     .. _Jinja2 template:

+        http://jinja.pocoo.org/docs/latest/api/#jinja2.Template

+     """

+ 

+     template = None

+     position = None

+     render = None if template is None else template.render

+ 

+     @staticmethod

+     def data(session, widget, *args, **kwargs):

+         """

+         This generates the data used to render a widget.

+ 

+         The result of this function is cached and this function will not be

+         called again until the :meth:`Widget.should_invalidate` returns

+         `True`.

+ 

+         Args:

+             module: An object that implements the Widget interface

+             session (sqlalchemy.orm.scoping.scoped_session): An SQLAlchemy

+                 session to use when querying the database.

+             widget (hubs.models.Widget): The Widget instance to render

+ 

+         Returns:

+             dict: A JSON-serializable dictionary of data.

+ 

+             This data will be cached.

+         """

+         raise NotImplementedError()

+ 

+     @staticmethod

+     def should_invalidate(message, session, widget):

+         """

+         A function that is used to determine when to invalidate the widget's

+         cache.

+ 

+         This function will be called when a fedmsg arrives. The widget should

+         use this to decide whether it needs to regenerate its data.

+ 

+         Args:

+             message (dict): The fedmsg that triggered this function call.

+             session (sqlalchemy.orm.scoping.scoped_session): An SQLAlchemy

+                 session to use when querying the database.

+             widget (hubs.models.Widget): The Widget instance to render

+ 

+         Returns:

+             bool: True if the cache needs to be rebuilt, False otherwise

+ 

+             A widget should return `False` if the message received would not

+             change the result of a call it the :meth:`Widget.data` function.

+             When it returns `True`, this widget's :meth:`Widget.data` function

+             will be called to refresh the cache.

+         """

+         raise NotImplementedError()

Widgets in Hubs have an interface they are expected to implement. This
formally defines and documents this interface as Sphinx docblocks in
code. The docblocks are then rendered to HTML using the Sphinx autodoc
plugin as part of a new API section of the documentation project. This
replaces the separate API documentation for widgets originally found in
the dev guide. This has the advantage of creating a single source of
truth for API documentation.

This commit does introduce a new Widget class to define the
interface. It should function in the same way as the widgets currently
defined at the module level and truthfully only serves to bundle the
interface into one neat object within the module.

I'm interested to hear what people think about implementing widgets as modules
or as classes. They're pretty much equivalent and both approaches should work,
but we should pick one way or the other and document it. If we want to use the
module approach, I can probably move the docblocks around and fiddle with sphinx
a bit to make the documentation still work and make sense.

To get a good idea of what this looks like, just build the docs with make html and then
navigate to the api.html page.

Signed-off-by: Jeremy Cline jeremy@jcline.org

rebased

7 years ago

rebased

7 years ago

I'd recommend filing this TODO as a ticket instead of putting it here, or at least in addition to putting it here.

LGTM, though it's conflicting.

I was hoping someone would enlighten me as part of the PR review process :(

rebased

7 years ago

Okay, I've rebased and fixed the conflict. I spent a while investigating the chrome function and I think it's best to remove it since I believe its functionality is equivalent to setting the render function directly.

Yeah, the chrome function isn't really useful, it can be replaced with template blocks.

I've started working on a class-based implementation of the widgets like the one you suggest (not exactly, but in this direction), and it's taking shape! Give me a couple days more and I'll have something to propose that will be more extendable, more modular, and with less magic :-)

My branch is merged now, I believe this pull request can be closed.

Pull-Request has been closed by abompard

7 years ago