[1.7.x] 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.

Backport of 8c4ca16c65 and b62f72498a from master
This commit is contained in:
Loic Bistuer 2014-10-22 23:16:32 +07:00
parent 7227ec8ecc
commit 7fa6781f81
3 changed files with 54 additions and 3 deletions

View File

@ -208,9 +208,17 @@ 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:
raise RuntimeError( if (model.__name__ == app_models[model_name].__name__ and
"Conflicting '%s' models in application '%s': %s and %s." % model.__module__ == app_models[model_name].__module__):
(model_name, app_label, app_models[model_name], model)) warnings.warn(
"Model '%s.%s' was already registered. "
"Reloading models is not advised as it can lead to inconsistencies, "
"most notably with related models." % (model_name, app_label),
RuntimeWarning, stacklevel=2)
else:
raise RuntimeError(
"Conflicting '%s' models in application '%s': %s and %s." %
(model_name, app_label, app_models[model_name], model))
app_models[model_name] = model app_models[model_name] = model
self.clear_cache() self.clear_cache()

View File

@ -14,3 +14,7 @@ Bugfixes
* Fixed a migration crash when adding an explicit ``id`` field to a model on * Fixed a migration crash when adding an explicit ``id`` field to a model on
SQLite (:ticket:`23702`). SQLite (:ticket:`23702`).
* Warn for duplicate models when a module is reloaded. Previously a
RuntimeError was raised every time two models clashed in the app registry.
(:ticket:`23621`).

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,44 @@ 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. "
"Reloading models is not advised as it can lead to inconsistencies, "
"most notably with related models.")
# 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):