diff --git a/apps/app_registry/models.py b/apps/app_registry/models.py index acf93ca8d7..02a46f9503 100644 --- a/apps/app_registry/models.py +++ b/apps/app_registry/models.py @@ -56,7 +56,7 @@ class App(object): for bootstrap_model in getattr(registration, 'bootstrap_models', []): logger.debug('bootstrap_model: %s' % bootstrap_model) - BootstrapModel(model_name=bootstrap_model.get('name'), app_name=app_name, sanitize=bootstrap_model.get('sanitize', True)) + BootstrapModel(model_name=bootstrap_model.get('name'), app_name=app_name, sanitize=bootstrap_model.get('sanitize', True), dependencies=bootstrap_model.get('dependencies')) def __unicode__(self): return unicode(self.label) diff --git a/apps/bootstrap/classes.py b/apps/bootstrap/classes.py index b65e7dd1ab..75ac995227 100644 --- a/apps/bootstrap/classes.py +++ b/apps/bootstrap/classes.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import logging +from itertools import chain from django.db import models from django.core import serializers @@ -8,6 +9,7 @@ from django.utils.datastructures import SortedDict from .exceptions import ExistingData from .literals import FIXTURE_TYPE_PK_NULLIFIER, FIXTURE_TYPE_MODEL_PROCESS +from .utils import toposort2 logger = logging.getLogger(__name__) @@ -43,16 +45,49 @@ class BootstrapModel(object): raise ExistingData @classmethod - def get_all(cls): - return cls._registry.values() + def get_all(cls, sort_by_dependencies=False): + """ + Return all boostrap models, sorted by dependencies optionally. + """ + if not sort_by_dependencies: + return cls._registry.values() + else: + return (cls.get_by_name(name) for name in list(chain.from_iterable(toposort2(cls.get_dependency_dict())))) + + @classmethod + def get_dependency_dict(cls): + """ + Return a dictionary where the key is the model name and it's value + is a list of models upon which it depends. + """ + result = {} + for instance in cls.get_all(): + result[instance.get_fullname()] = set(instance.dependencies) + + return result + + @classmethod + def get_by_name(cls, name): + """ + Return a BootstrapModel instance by the fullname of the model it + represents. + """ + return cls._registry[name] def get_fullname(self): + """ + Return a the full app name + model name of the model represented + by the instance. + """ return '.'.join([self.app_name, self.model_name]) def get_model_instance(self): + """ + Returns an actual Model class instance of the model. + """ return models.get_model(self.app_name, self.model_name) - def __init__(self, model_name, app_name=None, sanitize=True): + def __init__(self, model_name, app_name=None, sanitize=True, dependencies=None): app_name_splitted = None if '.' in model_name: app_name_splitted, model_name = model_name.split('.') @@ -61,8 +96,9 @@ class BootstrapModel(object): if not self.app_name: raise Exception('Pass either a dotted app plus model name or a model name and a separate app name') self.model_name = model_name - self.__class__._registry[self.get_fullname()] = self self.sanitize = sanitize + self.dependencies = dependencies if dependencies else [] + self.__class__._registry[self.get_fullname()] = self def dump(self, serialization_format): result = serializers.serialize(serialization_format, self.get_model_instance().objects.all(), indent=4, use_natural_keys=True) @@ -80,7 +116,6 @@ class FixtureMetadata(object): Class to automatically create and extract metadata from a bootstrap fixture. """ - _registry = SortedDict() @classmethod diff --git a/apps/bootstrap/managers.py b/apps/bootstrap/managers.py index 8c023b7c13..0ad9945a57 100644 --- a/apps/bootstrap/managers.py +++ b/apps/bootstrap/managers.py @@ -24,7 +24,7 @@ class BootstrapSetupManager(models.Manager): Get the current setup of Mayan in bootstrap format fixture """ result = [] - for bootstrap_model in BootstrapModel.get_all(): + for bootstrap_model in BootstrapModel.get_all(sort_by_dependencies=True): model_fixture = bootstrap_model.dump(serialization_format) # Only add non empty model fixtures if not FIXTURE_TYPE_EMPTY_FIXTURE[serialization_format](model_fixture): diff --git a/apps/bootstrap/utils.py b/apps/bootstrap/utils.py new file mode 100644 index 0000000000..e68af0fb6f --- /dev/null +++ b/apps/bootstrap/utils.py @@ -0,0 +1,40 @@ +## {{{ http://code.activestate.com/recipes/578272/ (r1) +def toposort2(data): + """Dependencies are expressed as a dictionary whose keys are items +and whose values are a set of dependent items. Output is a list of +sets in topological order. The first set consists of items with no +dependences, each subsequent set consists of items that depend upon +items in the preceeding sets. + +>>> print '\\n'.join(repr(sorted(x)) for x in toposort2({ +... 2: set([11]), +... 9: set([11,8]), +... 10: set([11,3]), +... 11: set([7,5]), +... 8: set([7,3]), +... }) ) +[3, 5, 7] +[8, 11] +[2, 9, 10] + +""" + + from functools import reduce + + # Ignore self dependencies. + for k, v in data.items(): + v.discard(k) + # Find all items that don't depend on anything. + extra_items_in_deps = reduce(set.union, data.itervalues()) - set(data.iterkeys()) + # Add empty dependences where needed + data.update({item:set() for item in extra_items_in_deps}) + while True: + ordered = set(item for item, dep in data.iteritems() if not dep) + if not ordered: + break + yield ordered + data = {item: (dep - ordered) + for item, dep in data.iteritems() + if item not in ordered} + assert not data, "Cyclic dependencies exist among these items:\n%s" % '\n'.join(repr(x) for x in data.iteritems()) +## end of http://code.activestate.com/recipes/578272/ }}} diff --git a/apps/document_indexing/registry.py b/apps/document_indexing/registry.py index dd4bca5273..58e6102652 100644 --- a/apps/document_indexing/registry.py +++ b/apps/document_indexing/registry.py @@ -5,10 +5,12 @@ from .cleanup import cleanup bootstrap_models = [ { 'name': 'index', + 'dependencies': ['documents.documenttype'] }, { 'name': 'indextemplatenode', 'sanitize': False, + 'dependencies': ['document_indexing.index'] } ] cleanup_functions = [cleanup] diff --git a/apps/documents/registry.py b/apps/documents/registry.py index 80c1373a27..8a3c25f346 100644 --- a/apps/documents/registry.py +++ b/apps/documents/registry.py @@ -8,6 +8,7 @@ bootstrap_models = [ }, { 'name': 'documenttypefilename', + 'dependencies': ['documents.documenttype'] } ] cleanup_functions = [cleanup] diff --git a/apps/metadata/registry.py b/apps/metadata/registry.py index ede5038225..7c95aee0a2 100644 --- a/apps/metadata/registry.py +++ b/apps/metadata/registry.py @@ -9,12 +9,15 @@ bootstrap_models = [ }, { 'name': 'metadataset', + 'dependencies': ['metadata.metadatatype'] }, { 'name': 'metadatasetitem', + 'dependencies': ['metadata.metadataset'] }, { 'name': 'documenttypedefaults', + 'dependencies': ['documents.documenttype'] }, ] cleanup_functions = [cleanup] diff --git a/apps/tags/registry.py b/apps/tags/registry.py index 1aa9aab0f4..36ea3daba1 100644 --- a/apps/tags/registry.py +++ b/apps/tags/registry.py @@ -10,6 +10,7 @@ bootstrap_models = [ }, { 'name': 'tagproperties', + 'dependencies': ['taggit.tag'] } ] cleanup_functions = [cleanup]