First of all there needs to be support for a pre-commit handler if you're using the 0.9x versions of django. The patch for this is: {{{ Index: django/core/meta/__init__.py =================================================================== --- django/core/meta/__init__.py (revision 2013) +++ django/core/meta/__init__.py (working copy) @@ -1026,6 +1026,11 @@ ','.join(placeholders)), db_values) if opts.has_auto_field and not pk_set: setattr(self, opts.pk.attname, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column)) + + # Run any pre-commit hooks. + if hasattr(self, '_pre_commit'): + self._pre_commit(opts, cursor) + db.db.commit() # Run any post-save hooks. if hasattr(self, '_post_save'): @@ -1696,6 +1701,7 @@ man.save = curry(manipulator_save, opts, klass, add, change) man.get_related_objects = curry(manipulator_get_related_objects, opts, klass, add, change) man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change) + man.Klass = klass for field_name_list in opts.unique_together: setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts)) for f in opts.fields: }}} Create helper methods for iterating through the parents (like in [wiki:CookBookCategoryDataModel A "category" data model] but I made it more general to avoid code duplication): {{{ def foo_recurse_list(obj, parent_field, value_field): parent_list = [] try: if getattr(obj, 'parent_'+parent_field+'_id'): p = getattr(obj, 'get_parent_'+parent_field)() if value_field: f = getattr(p, value_field) parent_list.append(f) else: parent_list.append(p) more = recurse_list(p, parent_field, value_field) parent_list.extend(more) finally: return parent_list def recurse_list(obj, parent_field, value_field): parent_list = foo_recurse_list(obj, parent_field, value_field) parent_list.reverse() return parent_list }}} In your model you will have a foreign key pointing to the parent element of each element and a character field that will be used to store the structure (called "cache_hierarchy" in my code). Then add a _pre_commit handler like this: {{{ def _pre_commit(self, opts, cursor): # TODO: import above and remove this import line when django gets the no-magic patches from where_ever_you_put_the_code_above import recurse_list parent_list = recurse_list(self, 'your_model_name_here', 'id') if self.id in parent_list: raise 'Cannot be a child of itself' parent_list.append(self.id) self.cache_hierarchy = '/'.join([str(i) for i in parent_list]) cursor.execute("UPDATE %s SET cache_hierarchy = %%s WHERE id=%%s" % (db.quote_name(opts.db_table)), [self.cache_hierarchy, self.id]) }}}