I’ve been doing a lot of work on datagrids lately. My datagrid work is originally based off of Djblets datagrids. One of the cool things about these datagrids is the ability to sort by any column in the grid. This is a problem if the sorting occurs at the query level and you are dealing with Google App Engine data models. You could mitigate this by putting an index on every property on the model, but that isn’t a realistic solution. I’ve come up with a different solution. I pass an array of objects into the datagrid instead of a query and I have a helper function that sorts the objects in memory. This helper has some cool features.
- It allows you to sort by returned values of object methods.
- It allows you to sort the objects in ascending or descending value order.
- It allows sorting by multiple attributes.
- It does some basic value clean up before sorting for sort consistency.
The code is commented fairly extensively so you should be able to figure out what I’m doing.
def sort_helper(object_list, sort_list, db_field_map=None): """ Helper method for sorting object_lists. @param object_list An array of objects to sort. @param sort_list An array of names to sort by. @param db_field_map An optional dictionary that maps a sort name to an object's property or method. @return The sorted list """ def clean(x): """ Function that cleans the value to sort by. @param x The attribute on the object. @return The cleaned up value for sorting. """ # attribute is a method so get the returned value v = x() if callable(x) else x # attribute is a string so convert it to lower case if type(v) == str or type(v) == unicode: v = v.lower() # attribute is a datetime so convert it to an epoch value elif type(v) == datetime.datetime: v = time.mktime(v.timetuple()) # return the value return v # reverse the sort order so we get the correct sub-sorting sort_order = reversed(sort_list) # Generate the actual list of attributes we'll be sorting by for sort_item in sort_order: # -name so sort this attribute descending if sort_item == '-': base_sort_item = sort_item[1:] reverse = True # Otherwise sort in ascending order else: base_sort_item = sort_item reverse = False # Check if the name is in the db field map if db_field_map: if not base_sort_item in db_field_map: continue db_field = db_field_map[base_sort_item] else: db_field = base_sort_item # Do the actual sort now try: object_list = sorted(object_list, key=lambda x: clean(getattr(x, db_field)), reverse=reverse) except Exception, e: logging.exception('sort_helper - %s' % unicode(e)) return object_list
To use the helper you would do something like:
object_list = Users.all().fetch(1000) sorted = sort_helper(object_list, ['-created', 'display_name'])
This would sort a list of users in decending created order then ascending display name.
You can download the full source here.