Commits

pudo  committed 420631a

[templates,controllers] further fix up the current controllers, delete 'key' and 'enumeration_value'

  • Participants
  • Parent commits b5c02af
  • Branches mongo

Comments (0)

Files changed (23)

File wdmmg/config/routing.py

     map.connect('/dataset', controller='dataset', action='index')
     map.connect('/dataset/{name_or_id}', controller='dataset', action='view')
     map.connect('/dataset/{name_or_id}/{action}', controller='dataset')
-
+    
+    map.connect('/entity', controller='entity', action='index')
+    map.connect('/entity/{name_or_id}', controller='entity', action='view')
+    map.connect('/entity/{name_or_id}/{action}', controller='entity')
+    
+    map.connect('/classifier', controller='classifier', action='index')
+    map.connect('/classifier/{name_or_id}', controller='classifier', action='view')
+    map.connect('/classifier/{name_or_id}/{action}', controller='classifier')
+    
     map.connect('/entry', controller='entry', action='index')
     map.connect('/entry/{id_}', controller='entry', action='view')
     map.connect('/entry/{id_}/{action}', controller='entry')
 
-    map.connect('/key', controller='key', action='index')
-    map.connect('/key/{name_or_id}', controller='key', action='view')
-    map.connect('/key/{name_or_id}/{action}', controller='key')
-    map.connect('/key/{name_or_id}/value/{code}', controller='enumeration_value', action='view')
-    map.connect('/key/{name_or_id}/value/{code}/{action}', controller='enumeration_value')
-
-    # Deprecated.
-    map.connect('/enumeration_value/{id_}', controller='enumeration_value', action='view_id')
-
     map.connect('/api', controller='api', action='index')
     map.connect('/api/search', controller='api', action='search')
     map.connect('/api/aggregate', controller='api', action='aggregate')

File wdmmg/controllers/classifier.py

+import logging
+
+from pylons import request, response, session, tmpl_context as c, url
+from pylons.controllers.util import abort, redirect
+
+from wdmmg.lib.base import BaseController, render
+
+log = logging.getLogger(__name__)
+
+class ClassifierController(BaseController):
+
+    def index(self):
+        return 'Not implemented'
+
+    def view(self, name_or_id):
+        return 'Not implemented'

File wdmmg/controllers/dataset.py

 
     def index(self):
         c.limit = int(request.params.get('limit', '100')) # TODO: Nicer error message.
-        c.results = model.Session.query(model.Dataset)[:c.limit]
+        c.results = model.Dataset.find()[:c.limit]
         return render('dataset/index.html')
 
     def view(self, name_or_id=None):
-        c.row = self.get_by_name_or_id(model.Dataset, name_or_id)
-        c.num_entries = (model.Session.query(model.Entry)
-            .filter_by(dataset_=c.row)
-            ).count()
+        c.row = model.Dataset.by_name_or_id(name_or_id)
+        c.num_entries = model.Entry.find({'dataset.name': c.row.name}).count()
         return render('dataset/view.html')
 
     def entries(self, name_or_id=None):

File wdmmg/controllers/entity.py

+import logging
+
+from pylons import request, response, session, tmpl_context as c, url
+from pylons.controllers.util import abort, redirect
+
+from wdmmg.lib.base import BaseController, render
+
+log = logging.getLogger(__name__)
+
+class EntityController(BaseController):
+
+    def index(self):
+        return 'Not implemented'
+
+    def view(self, name_or_id):
+        return 'Not implemented'

File wdmmg/controllers/entry.py

 
 class EntryController(BaseController):
     def index(self):
-        query = model.Session.query(model.Entry)
+        query = model.Entry.find()
         c.page = Page(
             collection=query,
             page=int(request.params.get('page', 1)),
         return render('entry/index.html')
 
     def view(self, id_=None):
-        if not id_.isdigit():
-            abort(404, 'Sorry, entries must have a numeric code!')
-        c.row = self.get_by_id(model.Entry, id_)
+        c.row = model.Entry.by_name_or_id(id_)
         if not c.row:
-            abort(404, 'Sorry, there is no entry with code %r'%code)
-        if 'departments' in c.row.dataset_.name:
+            abort(404, 'Sorry, there is no entry with code %r'% id_)
+        if 'departments' in c.row.get('dataset', {}).get('name'):
             c.show_foi = True
         else:
             c.show_foi = False
-        c.classification = {} # Map from Key to EnumerationValue.
-        for ci in c.row.classificationitems:
-            if ci.value.key.name=='from':
-                c.entry_from = ci.value
-                c.wdtk_name = dept_name_to_wdtk_identifier(c.entry_from)
-            elif ci.value.key.name=='to':
-                c.entry_to = ci.value
-            elif ci.value.key.name=='time':
-                c.entry_time = ci.value
-            else:
-                c.classification[ci.value.key] = ci.value
+        #c.classification = {} # Map from Key to EnumerationValue.
+        #for ci in c.row.classificationitems:
+        #    if ci.value.key.name=='from':
+        #        c.entry_from = ci.value
+        #        c.wdtk_name = dept_name_to_wdtk_identifier(c.entry_from)
+        #    elif ci.value.key.name=='to':
+        #        c.entry_to = ci.value
+        #    elif ci.value.key.name=='time':
+        #        c.entry_time = ci.value
+        #    else:
+        #        c.classification[ci.value.key] = ci.value
         return render('entry/view.html')
 

File wdmmg/controllers/enumeration_value.py

-import logging
-
-from pylons import request, response, session, tmpl_context as c, url, app_globals
-from pylons.controllers.util import abort, redirect
-from sqlalchemy import desc
-
-from wdmmg.lib.base import BaseController, render
-from wdmmg.lib.helpers import Page
-from wdmmg import model
-
-log = logging.getLogger(__name__)
-
-class EnumerationValueController(BaseController):
-    '''
-    EnumerationValues are usually specified using a Key and a code.
-    The Key is usually specified by `name`, though an `id` is also accepted.
-    For backwards EnumerationValues can also be specified by `id`.
-    '''
-
-    def view_id(self, id_=None):
-        '''Deprecated.'''
-        c.row = self.get_by_id(model.EnumerationValue, id_)
-        c.key = c.row.key
-        return render('enumeration_value/view.html')
-
-    def view(self, name_or_id=None, code=None):
-        '''
-        name_or_id - a `Key.name` or `Key.id`.
-        code - an `EnumerationValue.code`.
-        '''
-        c.key = self.get_by_name_or_id(model.Key, name_or_id)
-        c.row = (model.Session.query(model.EnumerationValue)
-            .filter_by(key=c.key)
-            .filter_by(code=code)
-            ).first()
-        if not c.row:
-            abort(404, 'No record with code %r'%code)
-        query = (model.Session.query(model.Entry)
-            .join(model.ClassificationItem)
-            .join(model.EnumerationValue)
-            .filter_by(id=c.row.id)
-            .order_by(desc(model.Entry.amount))
-            )
-        c.amounts = self._get_totals()
-        c.page = Page(
-            collection=query,
-            page=int(request.params.get('page', 1)),
-            items_per_page=c.items_per_page,
-            item_count=query.count(),
-        )
-        return render('enumeration_value/view.html')
-
-    def _get_totals(self):
-        keyname = c.key.name
-        if c.key.name in ['to','from']:
-            keyname += '_code'
-        q = '%s:"%s"' % (keyname, c.row.code)
-        query = app_globals.solr.query(q, stats='true', stats_field='amount',
-                stats_facet='time_norm', rows=0)
-        try:
-            amountfield = query.stats['stats_fields']['amount']
-            c.total = amountfield['sum']
-            results = []
-            for timenorm, val in amountfield['facets']['time_norm'].items():
-                results.append([timenorm, val['sum']])
-            return sorted(results)
-        except:
-            return []
-

File wdmmg/controllers/key.py

-import logging
-
-from pylons import request, response, session, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
-
-from wdmmg.lib.base import BaseController, render
-from wdmmg.lib.helpers import Page
-from wdmmg import model
-
-log = logging.getLogger(__name__)
-
-class KeyController(BaseController):
-
-    def index(self):
-        all_keys = model.Session.query(model.Key).all()
-        # Make ourselves an index of keys by name.
-        c.by_name = {} # name -> Key
-        for key in all_keys:
-            c.by_name[key.name] = key
-        parent_key = self.get_by_name_or_id(model.Key, name_or_id=u'parent')
-        # Identify keys with/without parents.
-        c.parents = {} # name -> name
-        c.by_parent = {} # name -> list of names
-        for key in all_keys:
-            if parent_key in key.keyvalues:
-                c.parents[key.name] = key.keyvalues[parent_key]
-            else:
-                c.by_parent[key.name] = []
-        # For each key that has a parent, attach it to its ultimate ancestor.
-        for name in c.parents:
-            parent = c.parents[name]
-            while parent in c.parents:
-                parent = c.parents[parent]
-            c.by_parent[parent].append(name)
-        return render('key/index.html')
-
-    def view(self, name_or_id=None):
-        c.row = self.get_by_name_or_id(model.Key, name_or_id)
-        c.num_enumeration_values = (model.Session.query(model.KeyValue)
-            .filter_by(key=c.row)
-            .filter_by(ns=u'enumeration_value')
-            ).count()
-        query = model.Session.query(model.EnumerationValue).filter_by(key_id=c.row.id)
-        c.page = Page(
-            collection=query,
-            page=int(request.params.get('page', 1)),
-            items_per_page=c.items_per_page,
-            item_count=query.count()
-        )
-        return render('key/view.html')
-

File wdmmg/controllers/rest.py

 
     def _domain_object(self, domain_object):
         self._check_access(domain_object, READ)
-        return domain_object.as_big_dict()
+        return domain_object.to_flat_dict()
 
     def _check_access(self, domain_object, action):
         '''

File wdmmg/controllers/search.py

                 start=start, sort='score desc, amount desc', **kwargs)
         c.results = query.results
         c.amounts = []
+        c.total = 0
         if c.q and query.numFound > 0:
             c.amounts = self._get_totals()
         if c.facet_1:

File wdmmg/lib/helpers.py

 """
 from webhelpers.html import escape, HTML, literal, url_escape
 from webhelpers.html.tags import *
-from webhelpers import paginate
+from mongo_page import Page
+
 from webhelpers.markdown import markdown as _markdown
 def markdown(*args, **kwargs):
     return literal(_markdown(*args, **kwargs))
 
-class Page(paginate.Page):
-    '''Follow ckan setup.'''
-
-    # Curry the pager method of the webhelpers.paginate.Page class, so we have
-    # our custom layout set as default.
-    def pager(self, *args, **kwargs):
-        kwargs.update(
-            format="<div class='pager'>$link_previous ~2~ $link_next</div>",
-            symbol_previous=u'\xab Prev', symbol_next=u'Next \xbb'
-        )
-        return super(Page, self).pager(*args, **kwargs)
-
-
 import wdmmg.model as model
 from pylons import url
-def render_value(keyvalue):
-    enumval = keyvalue.enumeration_value
-    if enumval:
-        return link_to('%s (%s)' % (enumval.name, enumval.code),
-                url(controller='enumeration_value',
-                    name_or_id=keyvalue.key.name,
-                    action='view', code=enumval.code)
-                )
-    else:
-        return keyvalue.value
+def render_value(value):
+    return value
 
 def convert_search_result(result):
     entry_id = result.get('id', '')

File wdmmg/lib/loader.py

     data set into the store. The data set is assumed to be too big to fit in
     RAM, so it is streamed in and simultaneously written out to the database.
     
-    To avoid running out of RAM, the caller will probably call
-    `model.Session.remove()` one or more times during the lifetime of this 
-    Loader. Therefore, this class cannot retain domain objects between method 
-    calls. Instead, it records the database `id`s of the objects it needs.
-    
     The intended usage is something like this:
     
-        my_loader = Loader('my_dataset', [key_1, ..., key_n], commit_every=1000)
+        my_loader = Loader('my_dataset', **other_attributes)
         for row in fetch_my_data():
-            entry = my_loader.create_entry(row.amount, [
-                row.enumeration_value_1_id,
-                ...,
-                row.enumeration_value_n_id,
-            ])
+            entry = my_loader.create_entry(row.amount, **other_attributes))
             # Optionally do stuff to `entry` here.
         my_loader.compute_aggregates()
-    
-    The caller is responsible for setting up the Keys and EnumerationValues.
-    It is recommended that instances of ValueCache be used to retrieve the
-    `EnumerationValue.id`s passed to `create_entry()`, as this will
-    avoid database traffic.
     '''
     
     def __init__(self, dataset_name, description=u'', metadata=None, \
         
         axes - a list of Keys which will be used to classify spending.
         
-        notes (optional) - the `notes` to use when creating the Dataset.
+        description (optional) - the `description` to use when creating the Dataset.
         
-        commit_every (optional) - if not None, the frequency with which the
-            Loader will commit data to the store, expressed as a number of 
-            calls of `create_entry()`. The Loader will call
-            `model.Session.commit()` and also `model.Session.remove()`.
         '''
         assert isinstance(dataset_name, unicode)
         assert Dataset.find_one({'name': dataset_name}),\

File wdmmg/lib/mongo_page.py

+from pymongo.cursor import Cursor
+from webhelpers import paginate
+from webhelpers.paginate import get_wrapper as _get_wrapper
+
+
+class _MongoCursorWrapper(object):
+    def __init__(self, cur):
+        self.cur = cur
+        
+    def __getitem__(self, r):
+        cur = self.cur.clone()
+        return list(cur.__getitem__(r))
+        
+    def __len__(self):
+        return self.cur.clone().count()
+    
+
+def get_wrapper(obj, sqlalchemy_session=None):
+    if isinstance(obj, Cursor):
+        return _MongoCursorWrapper(obj)
+    return _get_wrapper(obj, sqlalchemy_session=sqlalchemy_session)
+    
+paginate.get_wrapper = get_wrapper
+
+class Page(paginate.Page):
+    # Curry the pager method of the webhelpers.paginate.Page class, so we have
+    # our custom layout set as default.
+    def pager(self, *args, **kwargs):
+        kwargs.update(
+            format="<div class='pager'>$link_previous ~2~ $link_next</div>",
+            symbol_previous=u'\xab Prev', symbol_next=u'Next \xbb'
+        )
+        return super(Page, self).pager(*args, **kwargs)

File wdmmg/lib/solrhelp.py

 SOLR_CORE_FIELDS = ['id', 'dataset', 'amount', 'time', 'location', 'from',
 'to', 'notes' ]
 
-def dict_to_indexable(orig, sep='.'):
-    from pymongo.dbref import DBRef
-    """ Flatten down a dictionary with some smartness. """
-    flat = {}
-    for k, v in orig.items():
-        if isinstance(v, DBRef):
-            v = v.as_doc().to_dict()
-        k = k.replace('$', '')
-        if k == '_id': 
-            k = 'id'
-        if isinstance(v, dict):
-            if 'name' in v.keys():
-                flat[k] = v['name']
-                del v['name']
-            for sk, sv in dict_to_indexable(v, sep=sep).items():
-                flat[k + sep + sk] = sv
-        #elif isinstance(v, dict):
-        else:
-            flat[k] = unicode(v)
-    return flat
-
 
 def build_index(dataset_name=None, solr=None):
     dataset_name = dataset_name or unicode(config.get('default_dataset', u'cra'))
     total = 0
     increment = 500
     for entry in cur:
-        ourdata = dict_to_indexable(entry)
-        from pprint import pprint
-        #pprint(ourdata)
+        ourdata = entry.to_flat_dict()
         buf.append(ourdata)
         if len(buf) == increment:
             print 'Writing %d records...' % len(buf)

File wdmmg/model/mongo.py

 from pymongo import Connection 
 from pymongo.database import Database
 from bson.dbref import DBRef
+from pymongo.objectid import ObjectId
 
 connection = ""
 db_name = ""
     def find_one(cls, *a, **kw):
         kw['as_class'] = cls
         return cls.c.find_one(*a, **kw)
-
+    
+    @classmethod
+    def by_name_or_id(cls, name_or_id):
+        return cls.find_one({'$or': [{'name': name_or_id},
+                                     {'_id': ObjectId(name_or_id)}]})
+    
     def __repr__(self):
         dr = super(Base, self).__repr__()
         return "<%s(%s)>" % (self.collection_name, dr)
                                          'context': self.context,
                                          'key': {'$in': self.keys()}})
         return dict([(e.key, e) for e in explanations])
+        
+    def to_flat_dict(self, sep='.'):
+        """ Flatten down a dictionary with some smartness. """
+        def _flatten(orig):
+            flat = {}
+            for k, v in orig.items():
+                if isinstance(v, DBRef):
+                    v = v.as_doc().to_dict()
+                k = k.replace('$', '')
+                if k == '_id': 
+                    k = 'id'
+                if isinstance(v, dict):
+                    if 'name' in v.keys():
+                        flat[k] = v['name']
+                        del v['name']
+                    for sk, sv in _flatten(v).items():
+                        flat[k + sep + sk] = sv
+                else:
+                    flat[k] = v
+            return flat
+        return _flatten(self)
     
 
 class Explanation(Base):

File wdmmg/templates/_util.html

 
   <div py:def="render_entries(entries)">
     <ul class="entry-list">
-      <li py:for="row in entries" py:with="from_=row.get('from', ''); time=row.get('time', ''); amount=row.get('amount', 0); to_=row.get('to', 'General Population');">
-        <a href="${url(controller='entry', action='view',
-          id_=row['id'])}">
-          <span class="time">${time}</span>: <span class="from">${from_}</span> spent
-          <span class="amount">${h.format_number(amount)}</span>
+      <li py:for="row in entries">
+        <a href="${url(controller='entry', action='view', id_=row.get('id'))}">
+          <span class="time">${row.get('time')}</span>: <span class="from">${row.get('from.label')}</span> spent
+          <span class="amount">${h.format_number(row.get('amount'))}</span>
           with
-          <span class="to">${to_}</span>
+          <span class="to">${row.get('to.label')}</span>
         </a>
         <div class="summary">
-          ${row.get('summary', '')}
+          ${row.get('description', '')}
         </div>
-        <a href="${url(controller='entry', action='view',
-          id_=row['id'])}">More info &raquo;</a>
+        <a href="${url(controller='entry', action='view', id_=row.get('id'))}">More info &raquo;</a>
       </li>
     </ul>
   </div>
 
   <div py:def="render_short_entries(entries)">
     <ul class="entry-list">
-      <li py:for="row in entries" py:with="from_=row.get('from', ''); from_code=row.get('from_code', ''); to_code=row.get('to_code', '');  time=row.get('time', ''); amount=row.get('amount', 0); to_=row.get('to', 'General Population');">
+      <li py:for="row in entries" py:with="from_=row.get('from.label', ''); from_code=row.get('from.id', ''); to_code=row.get('to.id', '');  time=row.get('time', ''); amount=row.get('amount', 0); to_=row.get('to.label', 'General Population');">
           <span class="time">${time}</span>: 
-      <a href="	${url(controller='enumeration_value', action='view',
-          name_or_id='from', code=from_code)}"><span class="from">${from_}</span></a> spent
+          <a href="	${url(controller='entity', action='view', name_or_id=from_code)}">
+            <span class="from">${from_}</span>
+          </a> spent
           <span class="amount">${h.format_number(amount)}</span>
           with
-          <a href="	${url(controller='enumeration_value', action='view',
-              name_or_id='to', code=to_code)}"><span class="to">${to_}</span></a>.
-          <br/><a href="${url(controller='entry', action='view',
-          id_=row['id'])}">See full entry &raquo;</a>
+          <a href="	${url(controller='entity', action='view', name_or_id=to_code)}">
+              <span class="to">${to_}</span></a>.
+          <br/>
+          <a href="${url(controller='entry', action='view', id_=row.get('id', 1))}">See full entry &raquo;</a>
       </li>
     </ul>
   </div>

File wdmmg/templates/dataset/index.html

     <p>The database contains the following datasets:</p>
     <ul>
       <li py:for="row in c.results">
-        <a href="${url(controller='dataset', action='view', name_or_id=row.name)}">${row.name}</a>: ${h.markdown(row.notes)}
+        <a href="${url(controller='dataset', action='view', name_or_id=row.name)}">${row.label if row.label else row.name}</a>: ${h.markdown(row.description)}
       </li>
     </ul>
-    <p py:if="len(c.results)>=c.limit">Only the first ${c.limit} results are shown.</p>
+    <p py:if="c.results.count()>=c.limit">Only the first ${c.limit} results are shown.</p>
   </div>
 
   <xi:include href="../layout.html" />

File wdmmg/templates/dataset/view.html

         <dt>Short name:</dt>
         <dd>${h.markdown(c.row.name)}</dd>
       </py:if>
-      <py:if test="c.row.long_name">
+      <py:if test="c.row.label">
         <dt>Full name:</dt>
-        <dd>${h.markdown(c.row.long_name)}</dd>
+        <dd>${h.markdown(c.row.label)}</dd>
       </py:if>
-      <py:if test="c.row.notes">
-        <dt>Notes:</dt>
-        <dd>${h.markdown(c.row.notes)}</dd>
+      <py:if test="c.row.description">
+        <dt>Description:</dt>
+        <dd>${h.markdown(c.row.description)}</dd>
       </py:if>
     </dl>
   </div>

File wdmmg/templates/entry/index.html

     data store. Browse them below or <a href="${url(controller='search', action='index')}">search them</a>.</p>
 
     ${c.page.pager(items_per_page=c.items_per_page)}
-    ${render_short_entries([ txn.as_big_dict() for txn in c.page.items ])}
+    ${render_short_entries([ txn.to_flat_dict() for txn in c.page.items ])}
     ${c.page.pager(items_per_page=c.items_per_page)}
 
   </div>

File wdmmg/templates/entry/view.html

 
    <div style="clear: both;"></div>
    <py:if test="c.show_foi">
-<!--
-   <li class="widget-container widget_text">
-     <div class="textwidget">
-
-		<form action='http://www.whatdotheyknow.com/new/bis' 
-		   id='foi_request' method='post' target='_blank'>  
-			<input type="hidden" id="tags" name="tags" 
-			  value="wheredoesmymoneygo entry:${c.row.id} ${request.url}" />
-<input id="default_letter" name="default_letter" type="hidden" 
-value="Under the Freedom of Information Act 2000 I would like to request the following information:
-All documents relating to the following payment, including (but not limited to) purchase orders,
-invoices, contracts, and tender documents.
-
-Supplier: ${c.entry_to.name}
-Date/period: ${c.entry_time.name}
-Amount: ${c.row.dataset_.currency.upper()} ${c.row.amount}
-
-This data was obtained from Where Does My Money Go website at ${request.url} - please see this page
-for original filename and row ID of this spending item.
-
-If this information is held by an outside contractor then it is your responsibility under 
-the FOIA to obtain that information.
-
-If the arrangements for any of the agreements with the Publisher have been delegated or passed 
-onto another public body, please can you inform me of this and if possible transfer the 
-request to that public body. My preferred format to receive this information is by 
-electronic means, specifically in a machine-readable form (e.g. CSV, Word or Excel 
-documents rather than scans of printouts).
-
-If you need any clarification of this request or if it is too broad in any way please feel 
-free to email me. If some parts of this request are more difficult to answer than others 
-please release the answerable material as it is available rather than hold up the entire 
-request for the contested data.
-
-If FOI requests of a similar nature have already been asked could you please include your 
-responses to those requests. I would be grateful if you could confirm in writing that 
-you have received this request, and I look forward to hearing from you within the 
-20-working-day statutory time period." /> 
-
-<input id="title" name="title" type="hidden" value="Information on transaction with ${c.entry_to.name} on ${c.entry_time.name}" /> 
-<input type="submit" value="Make an FoI Request" class='submit' />
-
-		</form>
-		<p>Clicking this will generate a new request on <a href="http://www.whatdotheyknow.com">
-		WhatDoTheyKnow</a>, with all details pre-populated. All you need to do is submit it.</p> 
-		<p>Remember, responding to FoI requests uses public resources. Please act responsibly.</p> 
-
-</div>
-</li> -->
+   
   </py:if>
    </py:def>
   
     <dl>
 
       <dt>In dataset:</dt>
-        <dd><a href="/dataset/${c.row.dataset_.id}">
+        <dd><a href="/dataset/${c.row.get('dataset').get('id')}">
 		<py:choose>
-		  <py:when test="c.row.dataset_.long_name">
-		    ${c.row.dataset_.long_name}
+		  <py:when test="c.row.get('dataset').get('label')">
+		    ${c.row.get('dataset').get('label')}
 		  </py:when>
 		  <py:otherwise>
-		   ${c.row.dataset_.name}
+		   ${c.row.get('dataset').get('name')}
 		  </py:otherwise>
 		</py:choose>
       </a></dd>
       <dt>To:</dt>
-      <dd><a href="${url(controller='enumeration_value', action='view', name_or_id=c.entry_to.key.name, code=c.entry_to.code)}">${c.entry_to.name}</a></dd> 
+      <dd><a href="${url(controller='entity', action='view', name_or_id=c.row.get('to').get('_id'))}">${c.row.get('to').get('label')}</a></dd> 
       <dt>From:</dt>
-      <dd><a href="${url(controller='enumeration_value', action='view', name_or_id=c.entry_from.key.name, code=c.entry_from.code)}">${c.entry_from.name}</a></dd>
+      <dd><a href="${url(controller='entity', action='view', name_or_id=c.row.get('from').get('_id'))}">${c.row.get('from').get('label')}</a></dd> 
+      
      <dt>Date made:</dt>
-      <dd>${c.entry_time.name}</dd>
+      <dd>${c.row.get('time')}</dd>
       <dt>Amount:</dt>
       <dd>
 		<py:choose>
 		  <py:when test="c.row.currency">
-		    ${c.row.currency.upper()}
+		    ${c.row.get('currency').upper()}
 		  </py:when>
 		  <py:otherwise>
-		   ${c.row.dataset_.currency.upper()}
+		   ${c.row.get('dataset').get('currency').upper()}
 		  </py:otherwise>
 		</py:choose>
        ${c.row.amount}
        </dd>
      
-      <py:if test="c.row.keyvalues">
-        <py:for each="key, keyvalue in c.row._keyvalues.items()">
-          <dt><a href="${url(controller='key', action='view', name_or_id=key.name)}">${key.name}</a></dt>
-          <dd>${h.render_value(keyvalue)}</dd>
+      <py:if test="c.row.items()">
+        <py:for each="key, value in c.row.items()">
+          <dt>${key}</dt>
+          <dd>${h.render_value(value)}</dd>
         </py:for>
       </py:if>
 
-      <py:if test="c.classification.items()">
-      <h3>Other classifiers:</h3>
-          <py:for each="key, value in sorted(c.classification.items(), key=lambda x: x[0].name)">
-            <dt id="${key.name}"><a href="${url(controller='key', action='view', name_or_id=key.name)}">${key.name}</a></dt>
-            <dd><a href="${url(controller='enumeration_value', action='view', name_or_id=key.name, code=value.code)}">${value.name}</a></dd>
-          </py:for>
-      </py:if>
-
-      <py:if test="c.row.notes">
+      <py:if test="c.row.get('description')">
         <h3>Notes:</h3>
-        <dd>${h.markdown(c.row.notes)}</dd>
+        <dd>${h.markdown(c.row.get('description'))}</dd>
       </py:if>
     </dl>
 
     </div>
 
 <script type="text/javascript">
-<py:for each="key, value in sorted(c.classification.items(), key=lambda x: x[0].name)">
+<py:for each="key, value in sorted(c.row.items(), key=lambda x: x[0])">
  $('dt#key').qtip({
     content: 'Some notes and a <a href="#">link</a>',
 	position: {

File wdmmg/templates/enumeration_value/view.html

   <xi:include href="../_timegraph.html" />
 
     ${c.page.pager(items_per_page=c.items_per_page)}
-        ${render_short_entries([ txn.as_big_dict() for txn in c.page.items])}
+        ${render_short_entries([ txn.to_flat_dict() for txn in c.page.items])}
     ${c.page.pager(items_per_page=c.items_per_page)}
 
   </div>

File wdmmg/templates/layout.html

         </a>
       </h1>
       <div id="site-description">Ever wondered what your taxes get spent on?</div>
-      <!--<div id="top-bar">
-        TODO: re-enable once user stuff in place
-        <py:choose>
-        <py:when test="c.user">
-        <p>
-          Logged in as <strong>${c.user.name}</strong> |
-          <a href="${url(controller='user',action='read',id=c.user.id)}">My Account</a>
-          | <a href="${url('/logout_openid')}">Logout</a>
-        </p>
-        </py:when>
-        <py:otherwise>
-          <p>
-          <a href="${url(controller='user',action='login', id=None)}">Login with <img width="16" height="16" alt="None" src="http://assets.okfn.org/images/icons/openid.png"/> OpenID</a>
-          </p>
-        </py:otherwise>
-        </py:choose>
-      </div> -->
     </div> <!-- /branding -->
 
     <div id="login-and-search">
 	              <span>Entries</span> 
 	            </a> 
 	          </li> 
-	          <li class="key"> 
-	            <a href="${url(controller='key', action='index')}" id="key" class="minibutton btn-key" title="The keys used to classify spending"> 
-	              <span>Keys</span> 
-	            </a> 
-	          </li> 
 	          <li class="api"> 
 	            <a href="${url(controller='api', action='index')}" id="api" class="minibutton btn-api" title="API"> 
 	              <span>API</span> 

File wdmmg/tests/functional/test_classifier.py

+from wdmmg.tests import *
+
+class TestClassifierController(TestController):
+
+    def test_index(self):
+        response = self.app.get(url(controller='classifier', action='index'))
+        # Test response...

File wdmmg/tests/functional/test_entity.py

+from wdmmg.tests import *
+
+class TestEntityController(TestController):
+
+    def test_index(self):
+        response = self.app.get(url(controller='entity', action='index'))
+        # Test response...