Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19806 -- Ensure that content types and permissions aren't crea…

…ted for swapped models.

Thanks to rizumu for the report.
  • Loading branch information...
commit c8985a8a7317042a641e870cb75b3005cc5d67b1 1 parent 0a0a0d6
Russell Keith-Magee authored November 24, 2012
2  django/contrib/contenttypes/management.py
@@ -5,6 +5,7 @@
5 5
 from django.utils import six
6 6
 from django.utils.six.moves import input
7 7
 
  8
+
8 9
 def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, **kwargs):
9 10
     """
10 11
     Creates content types for models in the given app, removing any model
@@ -77,6 +78,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, *
77 78
             if verbosity >= 2:
78 79
                 print("Stale content types remain.")
79 80
 
  81
+
80 82
 def update_all_contenttypes(verbosity=2, **kwargs):
81 83
     for app in get_apps():
82 84
         update_contenttypes(app, None, verbosity, **kwargs)
28  django/core/management/validation.py
@@ -35,7 +35,10 @@ def get_validation_errors(outfile, app=None):
35 35
     for (app_name, error) in get_app_errors().items():
36 36
         e.add(app_name, error)
37 37
 
38  
-    for cls in models.get_models(app):
  38
+    inc = set(models.get_models(app, include_swapped=True))
  39
+    no_inc = set(models.get_models(app))
  40
+
  41
+    for cls in models.get_models(app, include_swapped=True):
39 42
         opts = cls._meta
40 43
 
41 44
         # Check swappable attribute.
@@ -138,16 +141,17 @@ def get_validation_errors(outfile, app=None):
138 141
             # fields, m2m fields, m2m related objects or related objects
139 142
             if f.rel:
140 143
                 if f.rel.to not in models.get_models():
141  
-                    e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
  144
+                    # If the related model is swapped, provide a hint;
  145
+                    # otherwise, the model just hasn't been installed.
  146
+                    if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped:
  147
+                        e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
  148
+                    else:
  149
+                        e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
142 150
                 # it is a string and we could not find the model it refers to
143 151
                 # so skip the next section
144 152
                 if isinstance(f.rel.to, six.string_types):
145 153
                     continue
146 154
 
147  
-                # Make sure the model we're related hasn't been swapped out
148  
-                if f.rel.to._meta.swapped:
149  
-                    e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
150  
-
151 155
                 # Make sure the related field specified by a ForeignKey is unique
152 156
                 if not f.rel.to._meta.get_field(f.rel.field_name).unique:
153 157
                     e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.rel.field_name, f.rel.to.__name__))
@@ -184,16 +188,18 @@ def get_validation_errors(outfile, app=None):
184 188
             # existing fields, m2m fields, m2m related objects or related
185 189
             # objects
186 190
             if f.rel.to not in models.get_models():
187  
-                e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
  191
+                # If the related model is swapped, provide a hint;
  192
+                # otherwise, the model just hasn't been installed.
  193
+                if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped:
  194
+                    e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
  195
+                else:
  196
+                    e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
  197
+
188 198
                 # it is a string and we could not find the model it refers to
189 199
                 # so skip the next section
190 200
                 if isinstance(f.rel.to, six.string_types):
191 201
                     continue
192 202
 
193  
-            # Make sure the model we're related hasn't been swapped out
194  
-            if f.rel.to._meta.swapped:
195  
-                e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
196  
-
197 203
             # Check that the field is not set to unique.  ManyToManyFields do not support unique.
198 204
             if f.unique:
199 205
                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
33  django/db/models/loading.py
@@ -24,24 +24,24 @@ class AppCache(object):
24 24
     # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
25 25
     __shared_state = dict(
26 26
         # Keys of app_store are the model modules for each application.
27  
-        app_store = SortedDict(),
  27
+        app_store=SortedDict(),
28 28
 
29 29
         # Mapping of installed app_labels to model modules for that app.
30  
-        app_labels = {},
  30
+        app_labels={},
31 31
 
32 32
         # Mapping of app_labels to a dictionary of model names to model code.
33 33
         # May contain apps that are not installed.
34  
-        app_models = SortedDict(),
  34
+        app_models=SortedDict(),
35 35
 
36 36
         # Mapping of app_labels to errors raised when trying to import the app.
37  
-        app_errors = {},
  37
+        app_errors={},
38 38
 
39 39
         # -- Everything below here is only used when populating the cache --
40  
-        loaded = False,
41  
-        handled = {},
42  
-        postponed = [],
43  
-        nesting_level = 0,
44  
-        _get_models_cache = {},
  40
+        loaded=False,
  41
+        handled={},
  42
+        postponed=[],
  43
+        nesting_level=0,
  44
+        _get_models_cache={},
45 45
     )
46 46
 
47 47
     def __init__(self):
@@ -167,7 +167,7 @@ def get_app_errors(self):
167 167
 
168 168
     def get_models(self, app_mod=None,
169 169
                    include_auto_created=False, include_deferred=False,
170  
-                   only_installed=True):
  170
+                   only_installed=True, include_swapped=False):
171 171
         """
172 172
         Given a module containing models, returns a list of the models.
173 173
         Otherwise returns a list of all installed models.
@@ -179,8 +179,16 @@ def get_models(self, app_mod=None,
179 179
         By default, models created to satisfy deferred attribute
180 180
         queries are *not* included in the list of models. However, if
181 181
         you specify include_deferred, they will be.
  182
+
  183
+        By default, models that aren't part of installed apps will *not*
  184
+        be included in the list of models. However, if you specify
  185
+        only_installed=False, they will be.
  186
+
  187
+        By default, models that have been swapped out will *not* be
  188
+        included in the list of models. However, if you specify
  189
+        include_swapped, they will be.
182 190
         """
183  
-        cache_key = (app_mod, include_auto_created, include_deferred, only_installed)
  191
+        cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
184 192
         try:
185 193
             return self._get_models_cache[cache_key]
186 194
         except KeyError:
@@ -203,7 +211,8 @@ def get_models(self, app_mod=None,
203 211
             model_list.extend(
204 212
                 model for model in app.values()
205 213
                 if ((not model._deferred or include_deferred) and
206  
-                    (not model._meta.auto_created or include_auto_created))
  214
+                    (not model._meta.auto_created or include_auto_created) and
  215
+                    (not model._meta.swapped or include_swapped))
207 216
             )
208 217
         self._get_models_cache[cache_key] = model_list
209 218
         return model_list
0  tests/regressiontests/swappable_models/__init__.py
No changes.
15  tests/regressiontests/swappable_models/models.py
... ...
@@ -0,0 +1,15 @@
  1
+from django.db import models
  2
+
  3
+
  4
+class Article(models.Model):
  5
+    title =  models.CharField(max_length=100)
  6
+    publication_date = models.DateField()
  7
+
  8
+    class Meta:
  9
+        swappable = 'TEST_ARTICLE_MODEL'
  10
+
  11
+
  12
+class AlternateArticle(models.Model):
  13
+    title =  models.CharField(max_length=100)
  14
+    publication_date = models.DateField()
  15
+    byline = models.CharField(max_length=100)
46  tests/regressiontests/swappable_models/tests.py
... ...
@@ -0,0 +1,46 @@
  1
+from __future__ import absolute_import, unicode_literals
  2
+
  3
+from django.utils.six import StringIO
  4
+
  5
+from django.contrib.auth.models import Permission
  6
+from django.contrib.contenttypes.models import ContentType
  7
+from django.core import management
  8
+from django.db.models.loading import cache
  9
+from django.test import TestCase
  10
+from django.test.utils import override_settings
  11
+
  12
+
  13
+class SwappableModelTests(TestCase):
  14
+    def setUp(self):
  15
+        # This test modifies the installed apps, so we need to make sure
  16
+        # we're not dealing with a cached app list.
  17
+        cache._get_models_cache.clear()
  18
+
  19
+    def tearDown(self):
  20
+        # By fiddling with swappable models, we alter the installed models
  21
+        # cache, so flush it to make sure there are no side effects.
  22
+        cache._get_models_cache.clear()
  23
+
  24
+    @override_settings(TEST_ARTICLE_MODEL='swappable_models.AlternateArticle')
  25
+    def test_generated_data(self):
  26
+        "Permissions and content types are not created for a swapped model"
  27
+
  28
+        # Delete all permissions and content_types
  29
+        Permission.objects.all().delete()
  30
+        ContentType.objects.all().delete()
  31
+
  32
+        # Re-run syncdb. This will re-build the permissions and content types.
  33
+        new_io = StringIO()
  34
+        management.call_command('syncdb', load_initial_data=False, interactive=False, stdout=new_io)
  35
+
  36
+        # Check that content types and permissions exist for the swapped model,
  37
+        # but not for the swappable model.
  38
+        apps_models = [(p.content_type.app_label, p.content_type.model)
  39
+                       for p in Permission.objects.all()]
  40
+        self.assertIn(('swappable_models', 'alternatearticle'), apps_models)
  41
+        self.assertNotIn(('swappable_models', 'article'), apps_models)
  42
+
  43
+        apps_models = [(ct.app_label, ct.model)
  44
+                       for ct in ContentType.objects.all()]
  45
+        self.assertIn(('swappable_models', 'alternatearticle'), apps_models)
  46
+        self.assertNotIn(('swappable_models', 'article'), apps_models)

0 notes on commit c8985a8

Please sign in to comment.
Something went wrong with that request. Please try again.