polymorphic_dumpdata management command functionality removed;

the regular Django dumpdata command now automatically works correctly
for polymorphic models and all Django versions
(this is handled by PolymorphicModelBase now).
Testing for dumpdata has been added.
This commit is contained in:
Bert Constantin
2010-10-18 17:39:06 +02:00
parent f05b854a54
commit a87481b8b5
12 changed files with 101 additions and 296 deletions
+1
View File
@@ -16,6 +16,7 @@ pushreg
pbackup pbackup
mcmd.py mcmd.py
dbconfig_local.py dbconfig_local.py
diffmanagement
pip-log.txt pip-log.txt
build build
+40
View File
@@ -0,0 +1,40 @@
[
{
"pk": 1,
"model": "pexp.project",
"fields": {
"topic": "John's gathering",
"polymorphic_ctype": 2
}
},
{
"pk": 2,
"model": "pexp.project",
"fields": {
"topic": "Sculpting with Tim",
"polymorphic_ctype": 3
}
},
{
"pk": 3,
"model": "pexp.project",
"fields": {
"topic": "Swallow Aerodynamics",
"polymorphic_ctype": 4
}
},
{
"pk": 2,
"model": "pexp.artproject",
"fields": {
"artist": "T. Turner"
}
},
{
"pk": 3,
"model": "pexp.researchproject",
"fields": {
"supervisor": "Dr. Winter"
}
}
]
+1 -1
View File
@@ -20,7 +20,7 @@ class Command(NoArgsCommand):
help = "" help = ""
def handle_noargs(self, **options): def handle_noargs(self, **options):
print 'polycmd - sqlite test db is stored in:',settings.DATABASE_NAME print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH
print print
""" """
@@ -20,7 +20,7 @@ class Command(NoArgsCommand):
help = "" help = ""
def handle_noargs(self, **options): def handle_noargs(self, **options):
#print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH
print print
Project.objects.all().delete() Project.objects.all().delete()
+27 -1
View File
@@ -3,6 +3,9 @@
Please see README.rst or DOCS.rst or http://bserve.webhop.org/wiki/django_polymorphic Please see README.rst or DOCS.rst or http://bserve.webhop.org/wiki/django_polymorphic
""" """
import sys
import inspect
from django.db import models from django.db import models
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
@@ -75,7 +78,7 @@ class PolymorphicModelBase(ModelBase):
def get_inherited_managers(self, attrs): def get_inherited_managers(self, attrs):
""" """
Return list of all managers to be inherited/propagated from the base classes; Return list of all managers to be inherited/propagated from the base classes;
use correct mro, only use managers with _inherited==False, use correct mro, only use managers with _inherited==False (they are of no use),
skip managers that are overwritten by the user with same-named class attributes (in attrs) skip managers that are overwritten by the user with same-named class attributes (in attrs)
""" """
add_managers = []; add_managers_keys = set() add_managers = []; add_managers_keys = set()
@@ -150,3 +153,26 @@ class PolymorphicModelBase(ModelBase):
raise AssertionError(e) raise AssertionError(e)
return manager return manager
# hack: a small patch to Django would be a better solution.
# Django's management command 'dumpdata' relies on non-polymorphic
# behaviour of the _default_manager. Therefore, we catch any access to _default_manager
# here and return the non-polymorphic default manager instead if we are called from 'dumpdata.py'
# (non-polymorphic default manager is 'base_objects' for polymorphic models).
# This way we don't need to patch django.core.management.commands.dumpdata
# for all supported Django versions.
# TODO: investigate Django how this can be avoided
_dumpdata_command_running = False
if len(sys.argv)>1: _dumpdata_command_running = ( sys.argv[1] == 'dumpdata' )
def __getattribute__(self, name):
if name=='_default_manager':
if self._dumpdata_command_running:
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name
if 'django/core/management/commands/dumpdata.py' in frm[1]:
return self.base_objects
#caller_mod_name = inspect.getmodule(frm[0]).__name__ # does not work with python 2.4
#if caller_mod_name == 'django.core.management.commands.dumpdata':
return super(PolymorphicModelBase, self).__getattribute__(name)
@@ -1,19 +1,9 @@
""" """
polymorphic_dumpdata is just a slightly modified version polymorphic_dumpdata has been disabled since it's no longer needed
of Django's dumpdata. In the long term, patching Django's (this is now handled by polymorphic.base.PolymorphicModelBase).
dumpdata definitely is a better solution.
Use the Django 1.1 or 1.2 variant of dumpdata, depending of the
Django version used.
""" """
import django assert False, """
ERROR: The management command polymorphic_dumpdata is no longer supported or needed.
if django.VERSION[:2]==(1,1): Please use the standard Django dumpdata management command instead!
from polymorphic_dumpdata_11 import Command """
elif django.VERSION[:2]==(1,2):
from polymorphic_dumpdata_12 import Command
else:
assert False, 'Django version not supported'
@@ -1,94 +0,0 @@
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError
from django.core import serializers
from django.utils.datastructures import SortedDict
from optparse import make_option
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--format', default='json', dest='format',
help='Specifies the output serialization format for fixtures.'),
make_option('--indent', default=None, dest='indent', type='int',
help='Specifies the indent level to use when pretty-printing output'),
make_option('-e', '--exclude', dest='exclude',action='append', default=[],
help='App to exclude (use multiple --exclude to exclude multiple apps).'),
)
help = 'Output the contents of the database as a fixture of the given format.'
args = '[appname ...]'
def handle(self, *app_labels, **options):
from django.db.models import get_app, get_apps, get_models, get_model
format = options.get('format','json')
indent = options.get('indent',None)
exclude = options.get('exclude',[])
show_traceback = options.get('traceback', False)
excluded_apps = [get_app(app_label) for app_label in exclude]
if len(app_labels) == 0:
app_list = SortedDict([(app, None) for app in get_apps() if app not in excluded_apps])
else:
app_list = SortedDict()
for label in app_labels:
try:
app_label, model_label = label.split('.')
try:
app = get_app(app_label)
except ImproperlyConfigured:
raise CommandError("Unknown application: %s" % app_label)
model = get_model(app_label, model_label)
if model is None:
raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
if app in app_list.keys():
if app_list[app] and model not in app_list[app]:
app_list[app].append(model)
else:
app_list[app] = [model]
except ValueError:
# This is just an app - no model qualifier
app_label = label
try:
app = get_app(app_label)
except ImproperlyConfigured:
raise CommandError("Unknown application: %s" % app_label)
app_list[app] = None
# Check that the serialization format exists; this is a shortcut to
# avoid collating all the objects and _then_ failing.
if format not in serializers.get_public_serializer_formats():
raise CommandError("Unknown serialization format: %s" % format)
try:
serializers.get_serializer(format)
except KeyError:
raise CommandError("Unknown serialization format: %s" % format)
objects = []
for app, model_list in app_list.items():
if model_list is None:
model_list = get_models(app)
for model in model_list:
if not model._meta.proxy:
#### patch for django_polymorphic ######################################################
# modified for django_polymorphic compatibility:
# do not use polymorphic queryset for serialisation
# (as the dumpdata/serializer implementation depends
# on non-polymorphic behavious)
base_manager=model._default_manager
if getattr(model,'polymorphic_model_marker',None) != None:
base_manager=getattr(model,'base_objects',None)
objects.extend(base_manager.all())
try:
return serializers.serialize(format, objects, indent=indent)
except Exception, e:
if show_traceback:
raise
raise CommandError("Unable to serialize database: %s" % e)
@@ -1,175 +0,0 @@
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError
from django.core import serializers
from django.db import connections, DEFAULT_DB_ALIAS
from django.utils.datastructures import SortedDict
from optparse import make_option
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--format', default='json', dest='format',
help='Specifies the output serialization format for fixtures.'),
make_option('--indent', default=None, dest='indent', type='int',
help='Specifies the indent level to use when pretty-printing output'),
make_option('--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Nominates a specific database to load '
'fixtures into. Defaults to the "default" database.'),
make_option('-e', '--exclude', dest='exclude',action='append', default=[],
help='App to exclude (use multiple --exclude to exclude multiple apps).'),
make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False,
help='Use natural keys if they are available.'),
)
help = 'Output the contents of the database as a fixture of the given format.'
args = '[appname ...]'
def handle(self, *app_labels, **options):
from django.db.models import get_app, get_apps, get_models, get_model
format = options.get('format','json')
indent = options.get('indent',None)
using = options.get('database', DEFAULT_DB_ALIAS)
connection = connections[using]
exclude = options.get('exclude',[])
show_traceback = options.get('traceback', False)
use_natural_keys = options.get('use_natural_keys', False)
excluded_apps = set(get_app(app_label) for app_label in exclude)
if len(app_labels) == 0:
app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps)
else:
app_list = SortedDict()
for label in app_labels:
try:
app_label, model_label = label.split('.')
try:
app = get_app(app_label)
except ImproperlyConfigured:
raise CommandError("Unknown application: %s" % app_label)
model = get_model(app_label, model_label)
if model is None:
raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
if app in app_list.keys():
if app_list[app] and model not in app_list[app]:
app_list[app].append(model)
else:
app_list[app] = [model]
except ValueError:
# This is just an app - no model qualifier
app_label = label
try:
app = get_app(app_label)
except ImproperlyConfigured:
raise CommandError("Unknown application: %s" % app_label)
app_list[app] = None
# Check that the serialization format exists; this is a shortcut to
# avoid collating all the objects and _then_ failing.
if format not in serializers.get_public_serializer_formats():
raise CommandError("Unknown serialization format: %s" % format)
try:
serializers.get_serializer(format)
except KeyError:
raise CommandError("Unknown serialization format: %s" % format)
# Now collate the objects to be serialized.
objects = []
for model in sort_dependencies(app_list.items()):
if not model._meta.proxy:
#### patch for django_polymorphic ######################################################
# modified for django_polymorphic compatibility:
# do not use polymorphic queryset for serialisation
# (as the dumpdata/serializer implementation depends
# on non-polymorphic behavious)
base_manager=model._default_manager
if getattr(model,'polymorphic_model_marker',None) != None:
base_manager=getattr(model,'base_objects',None)
objects.extend(base_manager.using(using).all())
try:
return serializers.serialize(format, objects, indent=indent,
use_natural_keys=use_natural_keys)
except Exception, e:
if show_traceback:
raise
raise CommandError("Unable to serialize database: %s" % e)
def sort_dependencies(app_list):
"""Sort a list of app,modellist pairs into a single list of models.
The single list of models is sorted so that any model with a natural key
is serialized before a normal model, and any model with a natural key
dependency has it's dependencies serialized first.
"""
from django.db.models import get_model, get_models
# Process the list of models, and get the list of dependencies
model_dependencies = []
models = set()
for app, model_list in app_list:
if model_list is None:
model_list = get_models(app)
for model in model_list:
models.add(model)
# Add any explicitly defined dependencies
if hasattr(model, 'natural_key'):
deps = getattr(model.natural_key, 'dependencies', [])
if deps:
deps = [get_model(*d.split('.')) for d in deps]
else:
deps = []
# Now add a dependency for any FK or M2M relation with
# a model that defines a natural key
for field in model._meta.fields:
if hasattr(field.rel, 'to'):
rel_model = field.rel.to
if hasattr(rel_model, 'natural_key'):
deps.append(rel_model)
for field in model._meta.many_to_many:
rel_model = field.rel.to
if hasattr(rel_model, 'natural_key'):
deps.append(rel_model)
model_dependencies.append((model, deps))
model_dependencies.reverse()
# Now sort the models to ensure that dependencies are met. This
# is done by repeatedly iterating over the input list of models.
# If all the dependencies of a given model are in the final list,
# that model is promoted to the end of the final list. This process
# continues until the input list is empty, or we do a full iteration
# over the input models without promoting a model to the final list.
# If we do a full iteration without a promotion, that means there are
# circular dependencies in the list.
model_list = []
while model_dependencies:
skipped = []
changed = False
while model_dependencies:
model, deps = model_dependencies.pop()
# If all of the models in the dependency list are either already
# on the final model list, or not on the original serialization list,
# then we've found another model with all it's dependencies satisfied.
found = True
for candidate in ((d not in models or d in model_list) for d in deps):
if not candidate:
found = False
if found:
model_list.append(model)
changed = True
else:
skipped.append((model, deps))
if not changed:
raise CommandError("Can't resolve dependencies for %s in serialized app list." %
', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name)
for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__))
)
model_dependencies = skipped
return model_list
+6 -1
View File
@@ -27,7 +27,12 @@ class ShowFieldBase(object):
o = getattr(self, f.name) o = getattr(self, f.name)
if isinstance(f, (models.ForeignKey)): if isinstance(f, (models.ForeignKey)):
out += ': ' + ( '"None"' if o is None else '"' + o.__class__.__name__ + '"' ) #out += ': ' + ( '"None"' if o is None else '"' + o.__class__.__name__ + '"' )
out += ': '
if o is None:
out += '"None"'
else:
out += '"' + o.__class__.__name__ + '"'
elif isinstance(f, (models.ManyToManyField)): elif isinstance(f, (models.ManyToManyField)):
out += ': %d' % o.count() out += ': %d' % o.count()
+2 -2
View File
@@ -21,12 +21,12 @@ if django.VERSION[:2][0]>=1 and django.VERSION[:2][1]>=3:
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/var/tmp/django-polymorphic-test-db.sqlite3' 'NAME': SQLITE_DB_PATH
} }
} }
else: else:
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = '/var/tmp/django-polymorphic-test-db.sqlite3' # Or path to database file if using sqlite3. DATABASE_NAME = SQLITE_DB_PATH # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3. DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3. DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+16 -5
View File
@@ -21,8 +21,19 @@ function restore_django {
} }
function test_python_version { function test_python_version {
echo ; echo ; echo
echo "#########################################################################"
echo "### Testing Python $1, Django $2"
echo "#########################################################################"
echo
if which python$1 ; then if which python$1 ; then
if ! python$1 manage.py test ; then if ! python$1 manage.py test polymorphic; then
echo ERROR
restore_django
exit 10
fi
if ! ./test_dumpdata $1 ; then
echo ERROR echo ERROR
restore_django restore_django
exit 10 exit 10
@@ -35,9 +46,9 @@ function test_python_version {
} }
function test_all_python_versions { function test_all_python_versions {
test_python_version 2.4 test_python_version 2.4 $1
test_python_version 2.5 test_python_version 2.5 $1
test_python_version 2.6 test_python_version 2.6 $1
} }
function test_django_version { function test_django_version {
@@ -51,7 +62,7 @@ function test_django_version {
rm -f django rm -f django
ln -s django-versions/django$1 django ln -s django-versions/django$1 django
cd .. cd ..
test_all_python_versions test_all_python_versions $1
} }
test_django_version 1.1 test_django_version 1.1
+1
View File
@@ -2,6 +2,7 @@
rm -f /var/tmp/django-polymorphic-test-db.sqlite3 rm -f /var/tmp/django-polymorphic-test-db.sqlite3
rm -f /ram/django-polymorphic-test-db.sqlite3 rm -f /ram/django-polymorphic-test-db.sqlite3
TMPFILE=/tmp/django-polymorphic-test.dump TMPFILE=/tmp/django-polymorphic-test.dump
PYCMD="python$1" PYCMD="python$1"