Commits

Andrew Godwin committed dd77244

Refactoring the hacky bits of code into a separate package.

  • Participants
  • Parent commits 0d9bbe2

Comments (0)

Files changed (5)

south/hacks/__init__.py

+"""
+The hacks module encapsulates all the horrible things that play with Django
+internals in one, evil place.
+This top file will automagically expose the correct Hacks class.
+"""
+
+from south.hacks.django_1_0 import Hacks
+
+hacks = Hacks()

south/hacks/django_1_0.py

+"""
+Hacks for the Django 1.0/1.0.2 releases.
+"""
+
+from django.conf import settings
+from django.db import models
+
+class Hacks:
+    
+    def set_installed_apps(self, apps):
+        """
+        Sets Django's INSTALLED_APPS setting to be effectively the list passed in.
+        """
+        
+        # Make sure it's a list.
+        apps = list(apps)
+        
+        # This function will be monkeypatched into place.
+        def new_get_apps():
+            return apps
+        
+        # Monkeypatch in!
+        models.get_apps_old, models.get_apps = models.get_apps, new_get_apps
+        settings.INSTALLED_APPS, settings.OLD_INSTALLED_APPS = (
+            apps,
+            settings.INSTALLED_APPS,
+        )
+        self._redo_app_cache()
+    
+    
+    def reset_installed_apps(self):
+        """
+        Undoes the effect of set_installed_apps.
+        """
+        models.get_apps = models.get_apps_old
+        settings.INSTALLED_APPS = settings.OLD_INSTALLED_APPS
+        self._redo_app_cache()
+    
+    
+    def _redo_app_cache(self):
+        """
+        Used to repopulate AppCache after fiddling with INSTALLED_APPS.
+        """
+        from django.db.models.loading import AppCache
+        a = AppCache()
+        a.loaded = False
+        a._populate()

south/migration.py

     # Annotate tree with 'backwards edges'
     for app, classes in tree.items():
         for name, cls in classes.items():
-            cls.needs = []
-            if not hasattr(cls, "needed_by"):
-                cls.needed_by = []
+            if not hasattr(cls, "_dependency_parents"):
+                cls._dependency_parents = []
+            if not hasattr(cls, "_dependency_children"):
+                cls._dependency_children = []
+            # Get forwards dependencies
             if hasattr(cls, "depends_on"):
                 for dapp, dname in cls.depends_on:
                     dapp = get_app(dapp)
                             get_app_name(dapp),
                         )
                         sys.exit(1)
-                    cls.needs.append((dapp, dname))
-                    if not hasattr(tree[dapp][dname], "needed_by"):
-                        tree[dapp][dname].needed_by = []
-                    tree[dapp][dname].needed_by.append((app, name))
+                    cls._dependency_parents.append((dapp, dname))
+                    if not hasattr(tree[dapp][dname], "_dependency_children"):
+                        tree[dapp][dname]._dependency_children = []
+                    tree[dapp][dname]._dependency_children.append((app, name))
+            # Get backwards dependencies
+            if hasattr(cls, "needed_by"):
+                for dapp, dname in cls.needed_by:
+                    dapp = get_app(dapp)
+                    if dapp not in tree:
+                        print "Migration %s in app %s claims to be needed by unmigrated app %s." % (
+                            name,
+                            get_app_name(app),
+                            dapp,
+                        )
+                        sys.exit(1)
+                    if dname not in tree[dapp]:
+                        print "Migration %s in app %s claims to be needed by nonexistent migration %s in app %s." % (
+                            name,
+                            get_app_name(app),
+                            dname,
+                            get_app_name(dapp),
+                        )
+                        sys.exit(1)
+                    cls._dependency_children.append((dapp, dname))
+                    if not hasattr(tree[dapp][dname], "_dependency_parents"):
+                        tree[dapp][dname]._dependency_parents = []
+                    tree[dapp][dname]._dependency_parents.append((app, name))
     
     # Sanity check whole tree
     for app, classes in tree.items():
     # Get the dependencies of a migration
     deps = []
     migration = tree[app][name]
-    for dapp, dname in migration.needs:
+    for dapp, dname in migration._dependency_parents:
         deps.extend(
             dependencies(tree, dapp, dname, trace+[(app,name)])
         )
         for aname in app_migrations[:app_migrations.index(name)]:
             needed += needed_before_forwards(tree, app, aname, False)
             needed += [(app, aname)]
-    for dapp, dname in tree[app][name].needs:
+    for dapp, dname in tree[app][name]._dependency_parents:
         needed += needed_before_forwards(tree, dapp, dname)
         needed += [(dapp, dname)]
     return remove_duplicates(needed)
         for aname in reversed(app_migrations[app_migrations.index(name)+1:]):
             needed += needed_before_backwards(tree, app, aname, False)
             needed += [(app, aname)]
-    for dapp, dname in tree[app][name].needed_by:
+    for dapp, dname in tree[app][name]._dependency_children:
         needed += needed_before_backwards(tree, dapp, dname)
         needed += [(dapp, dname)]
     return remove_duplicates(needed)

south/tests/__init__.py

 
 import unittest
-from django.conf import settings
+
+from south.hacks import hacks
 
 # Note: the individual test files are imported below this.
 
         return fake
     
     
-    def monkeypatch(self):
-        """Swaps out various Django calls for fake ones for our own nefarious purposes."""
-        
-        def new_get_apps():
-            return ['fakeapp']
-        
-        from django.db import models
-        from django.conf import settings
-        models.get_apps_old, models.get_apps = models.get_apps, new_get_apps
-        settings.INSTALLED_APPS, settings.OLD_INSTALLED_APPS = (
-            ["fakeapp"],
-            settings.INSTALLED_APPS,
-        )
-        self.redo_app_cache()
-    setUp = monkeypatch
+    def setUp(self):
+        """
+        Changes the Django environment so we can run tests against our test apps.
+        """
+        hacks.set_installed_apps(["fakeapp"])
     
     
-    def unmonkeypatch(self):
-        """Undoes what monkeypatch did."""
-        
-        from django.db import models
-        from django.conf import settings
-        models.get_apps = models.get_apps_old
-        settings.INSTALLED_APPS = settings.OLD_INSTALLED_APPS
-        self.redo_app_cache()
-    tearDown = unmonkeypatch
-    
-    
-    def redo_app_cache(self):
-        from django.db.models.loading import AppCache
-        a = AppCache()
-        a.loaded = False
-        a._populate()
+    def tearDown(self):
+        """
+        Undoes what setUp did.
+        """
+        hacks.reset_installed_apps()
+
 
 # Try importing all tests if asked for (then we can run 'em)
 try:
 
 if not skiptest:
     from south.tests.db import *
-    from south.tests.logic import *
-    from south.tests.modelsparser import *
+    from south.tests.logic import *

south/tests/modelsparser.py

-# -*- coding: UTF-8 -*-
-
-import unittest
-
-from south.db import db
-from south.tests import Monkeypatcher
-from south.tests.fakeapp.models import HorribleModel, Other1, Other2
-
-from south.modelsparser import get_model_fields, get_model_meta
-
-class TestModelParsing(Monkeypatcher):
-
-    """
-    Tests parsing of models.py files against the test one.
-    """
-    
-    def test_fields(self):
-        
-        fields = get_model_fields(HorribleModel)
-        self.assertEqual(
-            fields,
-            {
-                'id': ('models.AutoField', [], {'primary_key': 'True'}),
-                
-                'name': ('models.CharField', [], {'max_length': '255'}), 
-                'short_name': ('models.CharField', [], {'max_length': '50'}),
-                'slug': ('models.SlugField', [], {'unique': 'True'}), 
-                
-                'o1': ('models.ForeignKey', ['Other1'], {}),
-                'o2': ('models.ForeignKey', ["'Other2'"], {}),
-                
-                'user': ('models.ForeignKey', ['User'], {'related_name': '"horribles"'}),
-                
-                'code': ('models.CharField', [], {'max_length': '25', 'default': "'\\xe2\\x86\\x91\\xe2\\x86\\x91\\xe2\\x86\\x93\\xe2\\x86\\x93\\xe2\\x86\\x90\\xe2\\x86\\x92\\xe2\\x86\\x90\\xe2\\x86\\x92BA'"}),
-                
-                'class_attr': ('models.IntegerField', [], {'default': '0'}),
-                'func': ('models.CharField', [], {'default': "default_func", 'max_length': '25'}),
-                
-                'choiced': ('models.CharField', [], {'max_length': '20', 'choices': 'choices'}),
-                
-                'multiline': ('models.TextField', [], {}),
-            },
-        )
-        
-        fields2 = get_model_fields(Other2)
-        self.assertEqual(
-            fields2,
-            {'close_but_no_cigar': ('models.PositiveIntegerField', [], {'primary_key': 'True'})},
-        )
-        
-        fields3 = get_model_fields(Other1)
-        self.assertEqual(
-            fields3,
-            {'id': ('models.AutoField', [], {'primary_key': 'True'})},
-        )
-    
-    
-    def test_meta(self):
-        
-        meta = get_model_meta(HorribleModel)
-        self.assertEqual(
-            meta,
-            {'db_table': '"my_fave"', 'verbose_name': '"Dr. Strangelove,"+"""or how I learned to stop worrying\nand love the bomb"""'},
-        )