jqGrid in TurboGears2 Admin Screens

I wanted to use the jqGrid for my admin pages as I liked that look and it matches some of the other screens outside the admin controller.  The admin controller, or rather the CrudRestController out of tgext.crud, has a way of exporting results in json format. So surely its a matter of changing the TableBase to use jqGrid in the admin controller and we’re done?

Well, no.

First you need to adjust the jsonReader options so that it lines up to the field names that the controller sends and this was one of the first (or last snags). The json output looks like:

{
  "value_list": {
    "total": 20,
    "items_per_page": 7,
    "page": 1,
    "entries": [(lots of entries)...]
  }
}

Now this is a little odd because of the top-level dictionary that is being used here. Most of the examples have everything that is inside the value_list being returned. In fact adjusting the controller to return only those items in the value_list values works.

To look inside this structure we need to adjust the jsonReader options. jqGrid documentation uses the format “toptier>subtier” for the XML reader so that was the intial attempt. It was also an intial fail, it doesn’t work and you get your very familiar empty grid.

The trick is close to this format, but slightly different for json. You change the options to “toptier.subtier”. In other words change the greater than symbol to a full stop for json access.

The jqGridWidget now has the following options (amongst others):

options = {
  'datatype': 'json',
  'jsonReader': {
    'repeatitems': False,
    'root': 'value_list.entries',
    'total': 'value_list.total',
    'page': 'value_list.page',
  }
}

There might be a way of saying all entries sit under value_list inside jqGrid, but I couldn’t find it. Those options given above do give a working jqGrid on the admin screens.

jqplot in Turbogears

 

I’ve been working on the Rosenberg jqPlot GraphsNMS graphs slowly migrating them from using rrdtool graph and using jqplot.  While there have been many false-starts and re-works, I now have a working set of graphs, two of which are shown on the page.

The graphs look a lot slicker and I have also simplified the admin screens.  I found I kept having to type the same thing in over and over for the rrdgraphs and have narrowed down the type of graphs to approximately 5.  They’re certainly not bulletproof and need more testing but they’re a good start.

The graphs are based upon the ToscaWidgets2 series of widgets that then provide a nice “handle” for the jqPlot code.  My graphs even have some hand-coded Javascript to give nice units on the Y axis.

 

Enhanced by Zemanta

Pre-selecting ToscaWidgets jqgrid values in TurboGears

My Turbogears project has recently reached an important milestone; one of the back-end processes now runs pretty much continuously and the plugins it use (or at least the ones I can see) are also working.  That means I can turn to the front-end which displays the data the back-end collected.

For some of the data I am using a ToscaWidgets (or TW2) widget called a jqGridWidget which is a very nice jquery device that separates the presentation and data using a json query.  I’ve mentioned previously about how to get a jqGridWidget working but left the pre-filtering out until now.  This meant that my grid showed all the items in the database, not just the ones I wanted to see, but it was a start.

Now this widget displays things called Attributes which are basically children of another model called Hosts. Basically, Attributes are things you want to check or track about a Host.  My widget used to show all Attributes but often on a Host screen, I want to see its hosts only. So, this is how I got my widget to behave; I’m not sure this is THE CORRECT way of doing it, but it does work.

First, in the Hosts controller you need to create the widget and pass along the host_id that the controller has obtained.  I was not able to use the sub-classing trick you see in some TW2 documentation but actually make a widget instance.

class HostsController(BaseController):
    # other stuff here
    class por2(portlets.Portlet):
        title = 'Host Attributes'
        widget = AttributeGrid()
        widget.host_id = host_id

Next, the prepare() method in the Widget needs to get hold of the host_id and put it into the postData list.  I needed to do it before calling super() because the options become one long string in the sub-class.

class AttributeGrid(jqgrid.jqGridWidget):
    def prepare(self, **kw):
        if self.host_id is not None:
            self.options['postData'] = {'hostid': self.host_id}
        super(AttributeGrid, self).prepare()

This means the jqgrid when it asks for its JSON data will include the hostid parameter as well.  We need that method to “see” the host ID so we can filter the database access.

Finally in the JSON method for the Attribute we pick up and filter on the hostid.

    @expose('json')
    @validate(validators={'hostid':validators.Int()})
    def jqsumdata(self, hostid=0, page=1, rows=1, *args, **kw):
        conditions = []
        if hostid > 0:
            conditions.append(model.Attribute.host_id == hostid)
        attributes =model.DBSession.query(model.Attribute).filter(and_(*conditions))

From there you run through the attributes variable and build your JSON reply.

 

Enhanced by Zemanta

jqGridWidget in Turbogears

 

 

Turbogears 2 uses Toscawidgets 2 for a series of very clever widgets and base objects that you can use in your projects.  One I have been playing with is the jqGridWidget which uses jquery to display a grid. The idea with jquery is creating a grid or other object and then using javascript to call back to the webserver to obtain the data which usually comes from a database using the very excellent SQLAlchemy framework.

I found it a bit fiddly to get going and it is unforgiving, you get one thing wrong and you end up with an empty grid but when it works, it works very well.

from tw2.jqplugins.jqgrid import jqGridWidget
from tw2.jqplugins.jqgrid.base import word_wrap_css

class ItemsWidget(jqGridWidget):
    def prepare(self):
        self.resources.append(word_wrap_css)
        super(ItemsWidget, self).prepare()
    options = {
            'pager': 'item-list-pager2',
            'url': '/items/jqgrid',
            'datatype': 'json',
            'colNames': ['Date', 'Type', 'Area', 'Description'],

The code above is a snippet of the widget I use to make the graph. This is the visual side of the graph and shows just the  graph with nothing in it.  You need the datatype and url lines in the options which tell the code the url to get the data.  I have an items controller and within it there is a jqgrid method.  Note the number of columns I have, the data sent by the jqgrid method has to match this.

Deep inside the items controller is a jqgrid method. The idea is to return some rows of data back to the grid in the exact way the grid expects it otherwise you get, yep more empty grids.

   @expose('json')
    def jqgrid(self, page=1, rows=30, sidx=1, soid='asc', _search='false',
            searchOper=u'', searchField=u'', searchString=u'', **kw):

        qry = DBSession.query(Item)
        qry = qry.filter()
        qry = qry.order_by()
        result_count = qry.count()
        rows = int(rows)

        offset = (int(page)-1) * rows
        qry = qry.offset(offset).limit(rows)

        records = [{'id': rw.id,
           'cell': [ rw.created.strftime('%d %b %H:%M:%S'), rw.item_type.display_name), rw.display_name, rw.text()]} for rw in qry]
        total = int(math.ceil(result_count / float(rows)))
        return dict(page=int(page), total=total, records=result_count, rows=records)

The four items in the value for the cell in the dictionary must equal the number defined for the colNames.  I found it was useful to have firebug looking at my gets and another widget based upon SQLAjqGridWidget which is a lot simpler to setup but much less flexible too.

 

Enhanced by Zemanta