Add bootstrap model dependecies support
This commit is contained in:
@@ -56,7 +56,7 @@ class App(object):
|
|||||||
|
|
||||||
for bootstrap_model in getattr(registration, 'bootstrap_models', []):
|
for bootstrap_model in getattr(registration, 'bootstrap_models', []):
|
||||||
logger.debug('bootstrap_model: %s' % bootstrap_model)
|
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):
|
def __unicode__(self):
|
||||||
return unicode(self.label)
|
return unicode(self.label)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
@@ -8,6 +9,7 @@ from django.utils.datastructures import SortedDict
|
|||||||
|
|
||||||
from .exceptions import ExistingData
|
from .exceptions import ExistingData
|
||||||
from .literals import FIXTURE_TYPE_PK_NULLIFIER, FIXTURE_TYPE_MODEL_PROCESS
|
from .literals import FIXTURE_TYPE_PK_NULLIFIER, FIXTURE_TYPE_MODEL_PROCESS
|
||||||
|
from .utils import toposort2
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -43,16 +45,49 @@ class BootstrapModel(object):
|
|||||||
raise ExistingData
|
raise ExistingData
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
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()
|
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):
|
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])
|
return '.'.join([self.app_name, self.model_name])
|
||||||
|
|
||||||
def get_model_instance(self):
|
def get_model_instance(self):
|
||||||
|
"""
|
||||||
|
Returns an actual Model class instance of the model.
|
||||||
|
"""
|
||||||
return models.get_model(self.app_name, self.model_name)
|
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
|
app_name_splitted = None
|
||||||
if '.' in model_name:
|
if '.' in model_name:
|
||||||
app_name_splitted, model_name = model_name.split('.')
|
app_name_splitted, model_name = model_name.split('.')
|
||||||
@@ -61,8 +96,9 @@ class BootstrapModel(object):
|
|||||||
if not self.app_name:
|
if not self.app_name:
|
||||||
raise Exception('Pass either a dotted app plus model name or a model name and a separate 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.model_name = model_name
|
||||||
self.__class__._registry[self.get_fullname()] = self
|
|
||||||
self.sanitize = sanitize
|
self.sanitize = sanitize
|
||||||
|
self.dependencies = dependencies if dependencies else []
|
||||||
|
self.__class__._registry[self.get_fullname()] = self
|
||||||
|
|
||||||
def dump(self, serialization_format):
|
def dump(self, serialization_format):
|
||||||
result = serializers.serialize(serialization_format, self.get_model_instance().objects.all(), indent=4, use_natural_keys=True)
|
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
|
Class to automatically create and extract metadata from a bootstrap
|
||||||
fixture.
|
fixture.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_registry = SortedDict()
|
_registry = SortedDict()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class BootstrapSetupManager(models.Manager):
|
|||||||
Get the current setup of Mayan in bootstrap format fixture
|
Get the current setup of Mayan in bootstrap format fixture
|
||||||
"""
|
"""
|
||||||
result = []
|
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)
|
model_fixture = bootstrap_model.dump(serialization_format)
|
||||||
# Only add non empty model fixtures
|
# Only add non empty model fixtures
|
||||||
if not FIXTURE_TYPE_EMPTY_FIXTURE[serialization_format](model_fixture):
|
if not FIXTURE_TYPE_EMPTY_FIXTURE[serialization_format](model_fixture):
|
||||||
|
|||||||
40
apps/bootstrap/utils.py
Normal file
40
apps/bootstrap/utils.py
Normal file
@@ -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/ }}}
|
||||||
@@ -5,10 +5,12 @@ from .cleanup import cleanup
|
|||||||
bootstrap_models = [
|
bootstrap_models = [
|
||||||
{
|
{
|
||||||
'name': 'index',
|
'name': 'index',
|
||||||
|
'dependencies': ['documents.documenttype']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'indextemplatenode',
|
'name': 'indextemplatenode',
|
||||||
'sanitize': False,
|
'sanitize': False,
|
||||||
|
'dependencies': ['document_indexing.index']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
cleanup_functions = [cleanup]
|
cleanup_functions = [cleanup]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ bootstrap_models = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'documenttypefilename',
|
'name': 'documenttypefilename',
|
||||||
|
'dependencies': ['documents.documenttype']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
cleanup_functions = [cleanup]
|
cleanup_functions = [cleanup]
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ bootstrap_models = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'metadataset',
|
'name': 'metadataset',
|
||||||
|
'dependencies': ['metadata.metadatatype']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'metadatasetitem',
|
'name': 'metadatasetitem',
|
||||||
|
'dependencies': ['metadata.metadataset']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'documenttypedefaults',
|
'name': 'documenttypedefaults',
|
||||||
|
'dependencies': ['documents.documenttype']
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
cleanup_functions = [cleanup]
|
cleanup_functions = [cleanup]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ bootstrap_models = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'tagproperties',
|
'name': 'tagproperties',
|
||||||
|
'dependencies': ['taggit.tag']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
cleanup_functions = [cleanup]
|
cleanup_functions = [cleanup]
|
||||||
|
|||||||
Reference in New Issue
Block a user