Commit 8c4ca16c authored by Loic Bistuer's avatar Loic Bistuer
Browse files

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.
parent 4bf86d25
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -208,6 +208,12 @@ class Apps(object):
        model_name = model._meta.model_name
        app_models = self.all_models[app_label]
        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(
                    "Conflicting '%s' models in application '%s': %s and %s." %
                    (model_name, app_label, app_models[model_name], model))
+36 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import os
import sys
from unittest import skipUnless
import warnings

from django.apps import apps, AppConfig
from django.apps.registry import Apps
@@ -208,6 +209,41 @@ class AppsTests(TestCase):
            apps.get_model("apps", "SouthPonies")
        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):
    def __init__(self, **kwargs):