Skip to content

Instantly share code, notes, and snippets.

@beniwohli
Created September 13, 2012 09:11
Show Gist options
  • Save beniwohli/3713091 to your computer and use it in GitHub Desktop.
Save beniwohli/3713091 to your computer and use it in GitHub Desktop.
Combine hvad and mptt
from project.utils import classmaker
class Category(TranslatableModel, MPTTModel):
# https://github.com/ojii/django-nani/issues/39
__metaclass__ = classmaker()
import inspect, types, __builtin__
############## preliminary: two utility functions #####################
def skip_redundant(iterable, skipset=None):
"Redundant items are repeated items or items in the original skipset."
if skipset is None: skipset = set()
for item in iterable:
if item not in skipset:
skipset.add(item)
yield item
def remove_redundant(metaclasses):
skipset = set([types.ClassType])
for meta in metaclasses: # determines the metaclasses to be skipped
skipset.update(inspect.getmro(meta)[1:])
return tuple(skip_redundant(metaclasses, skipset))
##################################################################
## now the core of the module: two mutually recursive functions ##
##################################################################
memoized_metaclasses_map = {}
def get_noconflict_metaclass(bases, left_metas, right_metas):
"""Not intended to be used outside of this module, unless you know
what you are doing."""
# make tuple of needed metaclasses in specified priority order
metas = left_metas + tuple(map(type, bases)) + right_metas
needed_metas = remove_redundant(metas)
# return existing confict-solving meta, if any
if needed_metas in memoized_metaclasses_map:
return memoized_metaclasses_map[needed_metas]
# nope: compute, memoize and return needed conflict-solving meta
elif not needed_metas: # wee, a trivial case, happy us
meta = type
elif len(needed_metas) == 1: # another trivial case
meta = needed_metas[0]
# check for recursion, can happen i.e. for Zope ExtensionClasses
elif needed_metas == bases:
raise TypeError("Incompatible root metatypes", needed_metas)
else: # gotta work ...
metaname = '_' + ''.join([m.__name__ for m in needed_metas])
meta = classmaker()(metaname, needed_metas, {})
memoized_metaclasses_map[needed_metas] = meta
return meta
def classmaker(left_metas=(), right_metas=()):
def make_class(name, bases, adict):
metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
return metaclass(name, bases, adict)
return make_class
@kelvan
Copy link

kelvan commented Mar 24, 2015

great stuff, thanks

@urtzai
Copy link

urtzai commented Jul 18, 2016

This is awesome!! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment