diff --git a/django/db/models/loading.py b/django/db/models/loading.py index e62188adf7..6837e070ac 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -2,6 +2,8 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.utils.datastructures import SortedDict + import sys import os import threading @@ -18,10 +20,10 @@ class AppCache(object): # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531. __shared_state = dict( # Keys of app_store are the model modules for each application. - app_store = {}, + app_store = SortedDict(), # Mapping of app_labels to a dictionary of model names to model code. - app_models = {}, + app_models = SortedDict(), # Mapping of app_labels to errors raised when trying to import the app. app_errors = {}, @@ -133,7 +135,7 @@ class AppCache(object): """ self._populate() if app_mod: - return self.app_models.get(app_mod.__name__.split('.')[-2], {}).values() + return self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values() else: model_list = [] for app_entry in self.app_models.itervalues(): @@ -149,7 +151,7 @@ class AppCache(object): """ if seed_cache: self._populate() - return self.app_models.get(app_label, {}).get(model_name.lower()) + return self.app_models.get(app_label, SortedDict()).get(model_name.lower()) def register_models(self, app_label, *models): """ @@ -159,7 +161,7 @@ class AppCache(object): # Store as 'name: model' pair in a dictionary # in the app_models dictionary model_name = model._meta.object_name.lower() - model_dict = self.app_models.setdefault(app_label, {}) + model_dict = self.app_models.setdefault(app_label, SortedDict()) if model_name in model_dict: # The same model may be imported via different paths (e.g. # appname.models and project.appname.models). We use the source diff --git a/tests/modeltests/delete/__init__.py b/tests/modeltests/delete/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/modeltests/delete/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/modeltests/delete/models.py b/tests/modeltests/delete/models.py new file mode 100644 index 0000000000..d4f7dd1d1f --- /dev/null +++ b/tests/modeltests/delete/models.py @@ -0,0 +1,102 @@ +# coding: utf-8 +""" +Tests for some corner cases with deleting. +""" + +from django.db import models + +class DefaultRepr(object): + def __repr__(self): + return u"<%s: %s>" % (self.__class__.__name__, self.__dict__) + +class A(DefaultRepr, models.Model): + pass + +class B(DefaultRepr, models.Model): + a = models.ForeignKey(A) + +class C(DefaultRepr, models.Model): + b = models.ForeignKey(B) + +class D(DefaultRepr, models.Model): + c = models.ForeignKey(C) + a = models.ForeignKey(A) + +# Simplified, we have: +# A +# B -> A +# C -> B +# D -> C +# D -> A + +# So, we must delete Ds first of all, then Cs then Bs then As. +# However, if we start at As, we might find Bs first (in which +# case things will be nice), or find Ds first. + + +__test__ = {'API_TESTS': """ +# Due to the way that transactions work in the test harness, +# doing m.delete() here can work but fail in a real situation, +# since it may delete all objects, but not in the right order. +# So we manually check that the order of deletion is correct. + +# Also, it is possible that the order is correct 'accidentally', due +# solely to order of imports etc. To check this, we set the order +# that 'get_models()' will retrieve to a known 'tricky' order, and +# then try again with the reverse and try again. Slightly naughty +# access to internals here. + +>>> from django.utils.datastructures import SortedDict +>>> from django.db.models.loading import cache + +# Nice order +>>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd'] +>>> del A._meta._related_objects_cache +>>> del B._meta._related_objects_cache +>>> del C._meta._related_objects_cache +>>> del D._meta._related_objects_cache + + + +>>> a1 = A() +>>> a1.save() +>>> b1 = B(a=a1) +>>> b1.save() +>>> c1 = C(b=b1) +>>> c1.save() +>>> d1 = D(c=c1, a=a1) +>>> d1.save() + +>>> sd = SortedDict() +>>> a1._collect_sub_objects(sd) +>>> list(reversed(sd.keys())) +[, , , ] +>>> a1.delete() + +# Same again with a known bad order +>>> cache.app_models['delete'].keyOrder = ['d', 'c', 'b', 'a'] +>>> del A._meta._related_objects_cache +>>> del B._meta._related_objects_cache +>>> del C._meta._related_objects_cache +>>> del D._meta._related_objects_cache + + +>>> a2 = A() +>>> a2.save() +>>> b2 = B(a=a2) +>>> b2.save() +>>> c2 = C(b=b2) +>>> c2.save() +>>> d2 = D(c=c2, a=a2) +>>> d2.save() + +>>> sd2 = SortedDict() +>>> a2._collect_sub_objects(sd2) +>>> list(reversed(sd2.keys())) +[, , , ] +>>> a2.delete() + + + +""" +}