Fixed #23621 -- Warn for duplicate models when a module is reloaded.

Previously a RuntimeError was raised every time two models clashed
in the app registry. This prevented reloading a module in a REPL;
while it's not recommended to do so, we decided not to forbid this
use-case by turning the error into a warning.

Thanks @dfunckt and Sergey Pashinin for the initial patches.
This commit is contained in:
Loic Bistuer 2014-10-22 23:16:32 +07:00
parent 4bf86d25e5
commit 8c4ca16c65
2 changed files with 45 additions and 3 deletions

View File

@ -208,6 +208,12 @@ class Apps(object):
model_name = model._meta.model_name model_name = model._meta.model_name
app_models = self.all_models[app_label] app_models = self.all_models[app_label]
if model_name in app_models: if model_name in app_models:
if (model.__name__ == app_models[model_name].__name__ and
model.__module__ == app_models[model_name].__module__):
warnings.warn(
"Model '%s.%s' was already registered." % (model_name, app_label),
RuntimeWarning, stacklevel=2)
else:
raise RuntimeError( raise RuntimeError(
"Conflicting '%s' models in application '%s': %s and %s." % "Conflicting '%s' models in application '%s': %s and %s." %
(model_name, app_label, app_models[model_name], model)) (model_name, app_label, app_models[model_name], model))

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os import os
import sys import sys
from unittest import skipUnless from unittest import skipUnless
import warnings
from django.apps import apps, AppConfig from django.apps import apps, AppConfig
from django.apps.registry import Apps from django.apps.registry import Apps
@ -208,6 +209,41 @@ class AppsTests(TestCase):
apps.get_model("apps", "SouthPonies") apps.get_model("apps", "SouthPonies")
self.assertEqual(new_apps.get_model("apps", "SouthPonies"), temp_model) self.assertEqual(new_apps.get_model("apps", "SouthPonies"), temp_model)
def test_model_clash(self):
"""
Test for behavior when two models clash in the app registry.
"""
new_apps = Apps(["apps"])
meta_contents = {
'app_label': "apps",
'apps': new_apps,
}
body = {}
body['Meta'] = type(str("Meta"), tuple(), meta_contents)
body['__module__'] = TotallyNormal.__module__
type(str("SouthPonies"), (models.Model,), body)
# When __name__ and __module__ match we assume the module
# was reloaded and issue a warning. This use-case is
# useful for REPL. Refs #23621.
body = {}
body['Meta'] = type(str("Meta"), tuple(), meta_contents)
body['__module__'] = TotallyNormal.__module__
with warnings.catch_warnings(record=True) as w:
type(str("SouthPonies"), (models.Model,), body)
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
self.assertEqual(str(w[-1].message), "Model 'southponies.apps' was already registered.")
# If it doesn't appear to be a reloaded module then we expect
# a RuntimeError.
body = {}
body['Meta'] = type(str("Meta"), tuple(), meta_contents)
body['__module__'] = TotallyNormal.__module__ + '.whatever'
with six.assertRaisesRegex(self, RuntimeError,
"Conflicting 'southponies' models in application 'apps':.*"):
type(str("SouthPonies"), (models.Model,), body)
class Stub(object): class Stub(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):