Browse Source

Fixed #14007 -- Added model discovery in models module without the need to specify app_label.

Thanks mark@ and Aramgutang for work on the patch.
Tim Graham 11 years ago
parent
commit
2333c9662b

+ 15 - 3
django/db/models/base.py

@@ -19,7 +19,7 @@ from django.db.models.query_utils import DeferredAttribute, deferred_class_facto
 from django.db.models.deletion import Collector
 from django.db.models.options import Options
 from django.db.models import signals
-from django.db.models.loading import register_models, get_model
+from django.db.models.loading import register_models, get_model, MODELS_MODULE_NAME
 from django.utils.translation import ugettext_lazy as _
 from django.utils.functional import curry
 from django.utils.encoding import force_str, force_text
@@ -86,10 +86,22 @@ class ModelBase(type):
         base_meta = getattr(new_class, '_meta', None)
 
         if getattr(meta, 'app_label', None) is None:
-            # Figure out the app_label by looking one level up.
+            # Figure out the app_label by looking one level up from the package
+            # or module named 'models'. If no such package or module exists,
+            # fall back to looking one level up from the module this model is
+            # defined in.
+
             # For 'django.contrib.sites.models', this would be 'sites'.
+            # For 'geo.models.places' this would be 'geo'.
+
             model_module = sys.modules[new_class.__module__]
-            kwargs = {"app_label": model_module.__name__.split('.')[-2]}
+            package_components = model_module.__name__.split('.')
+            package_components.reverse()  # find the last occurrence of 'models'
+            try:
+                app_label_index = package_components.index(MODELS_MODULE_NAME) + 1
+            except ValueError:
+                app_label_index = 1
+            kwargs = {"app_label": package_components[app_label_index]}
         else:
             kwargs = {}
 

+ 4 - 2
django/db/models/loading.py

@@ -15,6 +15,8 @@ import os
 __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
         'load_app', 'app_cache_ready')
 
+MODELS_MODULE_NAME = 'models'
+
 
 class UnavailableApp(Exception):
     pass
@@ -98,12 +100,12 @@ class AppCache(object):
         self.nesting_level += 1
         app_module = import_module(app_name)
         try:
-            models = import_module('.models', app_name)
+            models = import_module('.' + MODELS_MODULE_NAME, app_name)
         except ImportError:
             self.nesting_level -= 1
             # If the app doesn't have a models module, we can just ignore the
             # ImportError and return no models for it.
-            if not module_has_submodule(app_module, 'models'):
+            if not module_has_submodule(app_module, MODELS_MODULE_NAME):
                 return None
             # But if the app does have a models module, we need to figure out
             # whether to suppress or propagate the error. If can_postpone is

+ 8 - 3
docs/ref/models/options.txt

@@ -24,12 +24,17 @@ Available ``Meta`` options
 
 .. attribute:: Options.app_label
 
-    If a model exists outside of the standard :file:`models.py` (for instance,
-    if the app's models are in submodules of ``myapp.models``), the model must
-    define which app it is part of::
+    If a model exists outside of the standard locations (:file:`models.py` or
+    a ``models`` package in an app), the model must define which app it is part
+    of::
 
         app_label = 'myapp'
 
+    .. versionadded:: 1.7
+
+        ``app_label`` is no longer required for models that are defined
+        in a ``models`` package within an app.
+
 ``db_table``
 ------------
 

+ 3 - 0
docs/releases/1.7.txt

@@ -57,6 +57,9 @@ Minor features
 * The :meth:`QuerySet.update_or_create()
   <django.db.models.query.QuerySet.update_or_create>` method was added.
 
+* :attr:`~django.db.models.Options.app_label` is no longer required for models
+  that are defined in a ``models`` package within an app.
+
 Backwards incompatible changes in 1.7
 =====================================
 

+ 0 - 3
tests/model_package/models/article.py

@@ -6,6 +6,3 @@ class Article(models.Model):
     sites = models.ManyToManyField(Site)
     headline = models.CharField(max_length=100)
     publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,)
-
-    class Meta:
-        app_label = 'model_package'

+ 0 - 3
tests/model_package/models/publication.py

@@ -3,6 +3,3 @@ from django.db import models
 
 class Publication(models.Model):
     title = models.CharField(max_length=30)
-
-    class Meta:
-        app_label = 'model_package'

+ 0 - 3
tests/model_package/tests.py

@@ -14,9 +14,6 @@ class Advertisment(models.Model):
         "model_package.Publication", null=True, blank=True
     )
 
-    class Meta:
-        app_label = 'model_package'
-
 
 class ModelPackageTests(TestCase):
     def test_model_packages(self):