=== django/contrib/admin/views/main.py
==================================================================
--- django/contrib/admin/views/main.py	(/django/trunk)	(revision 3877)
+++ django/contrib/admin/views/main.py	(/local/mutually-referential)	(revision 3877)
@@ -552,11 +552,11 @@
     nh = _nest_help # Bind to local variable for performance
     if current_depth > 16:
         return # Avoid recursing too deep.
-    objects_seen = []
+    opts_seen = []
     for related in opts.get_all_related_objects():
-        if related.opts in objects_seen:
+        if related.opts in opts_seen:
             continue
-        objects_seen.append(related.opts)
+        opts_seen.append(related.opts)
         rel_opts_name = related.get_method_name_part()
         if isinstance(related.field.rel, meta.OneToOne):
             try:
@@ -600,9 +600,9 @@
                 if not user.has_perm(p):
                     perms_needed.add(rel_opts.verbose_name)
     for related in opts.get_all_related_many_to_many_objects():
-        if related.opts in objects_seen:
+        if related.opts in opts_seen:
             continue
-        objects_seen.append(related.opts)
+        opts_seen.append(related.opts)
         rel_opts_name = related.get_method_name_part()
         has_related_objs = False
         for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
@@ -625,6 +625,7 @@
             if not user.has_perm(p):
                 perms_needed.add(related.opts.verbose_name)
 
+from pprint import *
 def delete_stage(request, app_label, module_name, object_id):
     import sets
     mod, opts = _get_mod_opts(app_label, module_name)
@@ -637,7 +638,7 @@
     deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []]
     perms_needed = sets.Set()
     _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
-
+    pprint(deleted_objects)
     if request.POST: # The user has already confirmed the deletion.
         if perms_needed:
             raise PermissionDenied
=== django/models/__init__.py
==================================================================
--- django/models/__init__.py	(/django/trunk)	(revision 3877)
+++ django/models/__init__.py	(/local/mutually-referential)	(revision 3877)
@@ -1,5 +1,6 @@
 from django.core import meta
 from django.utils.functional import curry
+from django.core.meta.fields import RECURSIVE_RELATIONSHIP_CONSTANT 
 
 __all__ = ['auth', 'core']
 
@@ -12,84 +13,60 @@
 # First, import all models so the metaclasses run.
 modules = meta.get_installed_model_modules(__all__)
 
-# Now, create the extra methods that we couldn't create earlier because
-# relationships hadn't been known until now.
-for mod in modules:
-    for klass in mod._MODELS:
 
-        # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods
-        # for all related objects.
-        for related in klass._meta.get_all_related_objects():
-            # Determine whether this related object is in another app.
-            # If it's in another app, the method names will have the app
-            # label prepended, and the add_BLAH() method will not be
-            # generated.
-            rel_mod = related.opts.get_model_module()
-            rel_obj_name = related.get_method_name_part()
-            if isinstance(related.field.rel, meta.OneToOne):
-                # Add "get_thingie" methods for one-to-one related objects.
-                # EXAMPLE: Place.get_restaurants_restaurant()
-                func = curry(meta.method_get_related, 'get_object', rel_mod, related.field)
-                func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name)
-                setattr(klass, 'get_%s' % rel_obj_name, func)
-            elif isinstance(related.field.rel, meta.ManyToOne):
-                # Add "get_thingie" methods for many-to-one related objects.
-                # EXAMPLE: Poll.get_choice()
-                func = curry(meta.method_get_related, 'get_object', rel_mod, related.field)
-                func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \
-                    (related.opts.app_label, related.opts.module_name)
-                setattr(klass, 'get_%s' % rel_obj_name, func)
-                # Add "get_thingie_count" methods for many-to-one related objects.
-                # EXAMPLE: Poll.get_choice_count()
-                func = curry(meta.method_get_related, 'get_count', rel_mod, related.field)
-                func.__doc__ = "Returns the number of associated `%s.%s` objects." % \
-                    (related.opts.app_label, related.opts.module_name)
-                setattr(klass, 'get_%s_count' % rel_obj_name, func)
-                # Add "get_thingie_list" methods for many-to-one related objects.
-                # EXAMPLE: Poll.get_choice_list()
-                func = curry(meta.method_get_related, 'get_list', rel_mod, related.field)
-                func.__doc__ = "Returns a list of associated `%s.%s` objects." % \
-                     (related.opts.app_label, related.opts.module_name)
-                setattr(klass, 'get_%s_list' % rel_obj_name, func)
-                # Add "add_thingie" methods for many-to-one related objects,
-                # but only for related objects that are in the same app.
-                # EXAMPLE: Poll.add_choice()
-                if related.opts.app_label == klass._meta.app_label:
-                    func = curry(meta.method_add_related, related.opts, rel_mod, related.field)
-                    func.alters_data = True
-                    setattr(klass, 'add_%s' % rel_obj_name, func)
-                del func
-            del rel_obj_name, rel_mod, related # clean up
+def fixup_modules():
+    #resolve string referencing models
+    for mod in modules: 
+        for klass in mod._MODELS:
+            for f in klass._meta.fields + klass._meta.many_to_many:
+                if f.rel and isinstance(f.rel.to, basestring):
+                    #find the class
+                    other = None
+                    
+                    if f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
+                        other = klass
+                    else:
+                        for cls in mod._MODELS:
+                            if cls.__name__ == f.rel.to:
+                                other = cls
+                                break
+                    assert other != None, "'to' must be either a model, the string '%s', or the name of a model" \
+                                           % RECURSIVE_RELATIONSHIP_CONSTANT
+                    f.rel.to = other._meta
+                    f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name)
+                    f.verbose_name = f.verbose_name or f.rel.to.verbose_name
+                    f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
 
-        # Do the same for all related many-to-many objects.
-        for related in klass._meta.get_all_related_many_to_many_objects():
-            rel_mod = related.opts.get_model_module()
-            rel_obj_name = related.get_method_name_part()
-            setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, related.field))
-            setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, related.field))
-            setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, related.field))
-            if related.opts.app_label == klass._meta.app_label:
-                func = curry(meta.method_set_related_many_to_many, related.opts, related.field)
+    # Now, create the extra methods that we couldn't create earlier because
+    # relationships might not have been known until now.
+    for mod in modules: 
+        for klass in mod._MODELS:        
+            #Allow fields to add stuff to the class and module
+            for f in klass._meta.fields + klass._meta.many_to_many:
+                f.contribute_to_class(klass._meta, klass._meta.get_model_module() , klass)
+            
+            # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods
+            # for all related objects.
+            for related in klass._meta.get_all_related_objects():
+                related.field.contribute_to_related_class( related, klass)
+    
+            # Do the same for all related many-to-many objects.
+            for related in klass._meta.get_all_related_many_to_many_objects():
+                related.field.contribute_to_related_class( related, klass)
+                
+            # Add "set_thingie_order" and "get_thingie_order" methods for objects
+            # that are ordered with respect to this.
+            for obj in klass._meta.get_ordered_objects():
+                func = curry(meta.method_set_order, obj)
+                func.__doc__ = "Sets the order of associated `%s.%s` objects to the given ID list." % (obj.app_label, obj.module_name)
                 func.alters_data = True
-                setattr(klass, 'set_%s' % related.opts.module_name, func)
-                del func
-            del rel_obj_name, rel_mod, related # clean up
+                setattr(klass, 'set_%s_order' % obj.object_name.lower(), func)
+    
+                func = curry(meta.method_get_order, obj)
+                func.__doc__ = "Returns the order of associated `%s.%s` objects as a list of IDs." % (obj.app_label, obj.module_name)
+                setattr(klass, 'get_%s_order' % obj.object_name.lower(), func)
 
-        # Add "set_thingie_order" and "get_thingie_order" methods for objects
-        # that are ordered with respect to this.
-        for obj in klass._meta.get_ordered_objects():
-            func = curry(meta.method_set_order, obj)
-            func.__doc__ = "Sets the order of associated `%s.%s` objects to the given ID list." % (obj.app_label, obj.module_name)
-            func.alters_data = True
-            setattr(klass, 'set_%s_order' % obj.object_name.lower(), func)
+fixup_modules()
 
-            func = curry(meta.method_get_order, obj)
-            func.__doc__ = "Returns the order of associated `%s.%s` objects as a list of IDs." % (obj.app_label, obj.module_name)
-            setattr(klass, 'get_%s_order' % obj.object_name.lower(), func)
-            del func, obj # clean up
-        del klass # clean up
-    del mod
-del modules
-
 # Expose get_app and get_module.
 from django.core.meta import get_app, get_module
=== django/core/meta/__init__.py
==================================================================
--- django/core/meta/__init__.py	(/django/trunk)	(revision 3877)
+++ django/core/meta/__init__.py	(/local/mutually-referential)	(revision 3877)
@@ -167,7 +167,13 @@
         self.edit_inline = field.rel.edit_inline
         self.name = opts.module_name
         self.var_name = opts.object_name.lower()
+        self.klass = None
 
+    def get_class(self):
+        if not self.klass:
+            self.klass = self.opts.get_model_module().Klass
+        return self.klass
+            
     def flatten_data(self, follow, obj=None):
         new_data = {}
         rel_instances = self.get_list(obj)
@@ -739,34 +745,7 @@
             attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to)
             attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to)
 
-        for f in opts.fields:
-            # If the object has a relationship to itself, as designated by
-            # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
-            if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
-                f.rel.to = opts
-                f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name)
-                f.verbose_name = f.verbose_name or f.rel.to.verbose_name
-                f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
-            # Add "get_thingie" methods for many-to-one related objects.
-            # EXAMPLES: Choice.get_poll(), Story.get_dateline()
-            if isinstance(f.rel, ManyToOne):
-                func = curry(method_get_many_to_one, f)
-                func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name)
-                attrs['get_%s' % f.name] = func
-
-        for f in opts.many_to_many:
-            # Add "get_thingie" methods for many-to-many related objects.
-            # EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
-            func = curry(method_get_many_to_many, f)
-            func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name)
-            attrs['get_%s_list' % f.rel.singular] = func
-            # Add "set_thingie" methods for many-to-many related objects.
-            # EXAMPLES: Poll.set_sites(), Story.set_bylines()
-            func = curry(method_set_many_to_many, f)
-            func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs. Note that it doesn't check whether the given IDs are valid." % (f.rel.to.app_label, f.rel.to.module_name)
-            func.alters_data = True
-            attrs['set_%s' % f.name] = func
-
+                
         # Create the class, because we need it to use in currying.
         new_class = type.__new__(cls, name, bases, attrs)
 
@@ -802,41 +781,9 @@
         if opts.get_latest_by:
             new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
 
-        for f in opts.fields:
             #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class.
-            if f.choices:
-                # Add "get_thingie_display" method to get human-readable value.
-                func = curry(method_get_display_value, f)
-                setattr(new_class, 'get_%s_display' % f.name, func)
-            if isinstance(f, DateField) or isinstance(f, DateTimeField):
-                # Add "get_next_by_thingie" and "get_previous_by_thingie" methods
-                # for all DateFields and DateTimeFields that cannot be null.
-                # EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()
-                if not f.null:
-                    setattr(new_class, 'get_next_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, True))
-                    setattr(new_class, 'get_previous_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, False))
-                # Add "get_thingie_list" for all DateFields and DateTimeFields.
-                # EXAMPLE: polls.get_pub_date_list()
-                func = curry(function_get_date_list, opts, f)
-                func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects) in which %s objects are available. The first parameter ('kind') must be one of 'year', 'month' or 'day'." % name
-                setattr(new_mod, 'get_%s_list' % f.name, func)
 
-            elif isinstance(f, FileField):
-                setattr(new_class, 'get_%s_filename' % f.name, curry(method_get_file_filename, f))
-                setattr(new_class, 'get_%s_url' % f.name, curry(method_get_file_url, f))
-                setattr(new_class, 'get_%s_size' % f.name, curry(method_get_file_size, f))
-                func = curry(method_save_file, f)
-                func.alters_data = True
-                setattr(new_class, 'save_%s_file' % f.name, func)
-                if isinstance(f, ImageField):
-                    # Add get_BLAH_width and get_BLAH_height methods, but only
-                    # if the image field doesn't have width and height cache
-                    # fields.
-                    if not f.width_field:
-                        setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f))
-                    if not f.height_field:
-                        setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f))
-
+            
         # Add the class itself to the new module we've created.
         new_mod.__dict__[name] = new_class
 
@@ -938,10 +885,13 @@
     def __repr__(self):
         return '<%s object>' % self.__class__.__name__
 
+
 ############################################
 # HELPER FUNCTIONS (CURRIED MODEL METHODS) #
 ############################################
 
+
+
 # CORE METHODS #############################
 
 def method_init(opts, self, *args, **kwargs):
@@ -966,7 +916,7 @@
                         try:
                             val = getattr(rel_obj, f.rel.get_related_field().attname)
                         except AttributeError:
-                            raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj))
+                            raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj) )
                 setattr(self, f.attname, val)
             else:
                 val = kwargs.pop(f.attname, f.get_default())
@@ -1031,48 +981,98 @@
     if hasattr(self, '_post_save'):
         self._post_save()
 
-def method_delete(opts, self):
-    assert getattr(self, opts.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID."
-    # Run any pre-delete hooks.
-    if hasattr(self, '_pre_delete'):
-        self._pre_delete()
-    cursor = db.db.cursor()
+
+def add_sub_objects(opts, instance, seen_objs):
+    pk_val = str(getattr(instance, opts.pk.attname))
+    
+    if (opts,pk_val) in seen_objs:
+        return
+    seen_objs[(opts,pk_val)] = instance
+    
     for related in opts.get_all_related_objects():
         rel_opts_name = related.get_method_name_part()
         if isinstance(related.field.rel, OneToOne):
             try:
-                sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
+                sub_obj = getattr(instance, 'get_%s' % rel_opts_name)()
             except ObjectDoesNotExist:
                 pass
             else:
-                sub_obj.delete()
+                add_sub_objects(related.opts, sub_obj, seen_objs)
         else:
-            for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
-                sub_obj.delete()
-    for related in opts.get_all_related_many_to_many_objects():
+            for sub_obj in getattr(instance, 'get_%s_list' % rel_opts_name)():
+                add_sub_objects(related.opts, sub_obj, seen_objs)
+
+def cmp_opts(x, y):
+    for field in x.fields:
+        if field.rel and field.null and field.rel.to == y:
+            return -1
+    for field in y.fields:
+        if field.rel and field.null and field.rel.to == x:
+            return 1
+    return 0
+    
+def method_delete(opts, self, seen_objs = None):
+    assert getattr(self, opts.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID."
+   
+    if seen_objs == None:
+        seen_objs = {}
+    cursor = db.db.cursor()
+    add_sub_objects(opts, self, seen_objs)
+    
+    seen_opts = set([opts for opts,pk in seen_objs.keys()])
+    opts_order = list(seen_opts)
+    opts_order.sort(cmp=cmp_opts)
+    
+    seen_tups = [ (s_opts, pk_val, instance) for (s_opts, pk_val),instance in seen_objs.items() ]
+    seen_tups.sort(key=lambda x: opts_order.index(x[0]))
+    
+    for s_opts, pk_val, instance in seen_tups:
+        
+        # Run any pre-delete hooks.
+        if hasattr(instance, '_pre_delete'):
+            instance._pre_delete()
+        
+        for related in s_opts.get_all_related_many_to_many_objects():
+            cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+                (db.db.quote_name(related.field.get_m2m_db_table(related.opts)),
+                db.db.quote_name(s_opts.object_name.lower() + '_id')),
+                [pk_val])
+        for f in s_opts.many_to_many:
+            cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+                (db.db.quote_name(f.get_m2m_db_table(s_opts)),
+                db.db.quote_name(s_opts.object_name.lower() + '_id')),
+                [pk_val])
+                
+        for field in s_opts.fields:
+            if field.rel and field.null and field.rel.to in seen_opts:
+                cursor.execute("UPDATE %s SET %s = NULL WHERE %s =%%s" % \
+                               ( db.db.quote_name(s_opts.db_table), 
+                                 db.db.quote_name(field.column),
+                                 db.db.quote_name(s_opts.pk.column)),
+                               [pk_val] )
+              
+    seen_tups.reverse()
+    
+    for s_opts, pk_val, instance in seen_tups:
         cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
-            (db.db.quote_name(related.field.get_m2m_db_table(related.opts)),
-            db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)])
-    for f in opts.many_to_many:
-        cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
-            (db.db.quote_name(f.get_m2m_db_table(opts)),
-            db.db.quote_name(self._meta.object_name.lower() + '_id')),
-            [getattr(self, opts.pk.attname)])
-    cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
-        (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
-        [getattr(self, opts.pk.attname)])
+            (db.db.quote_name(s_opts.db_table), db.db.quote_name(s_opts.pk.column)),
+            [pk_val])
+            
+        
+        setattr(self, opts.pk.attname, None)
+        for f in s_opts.fields:
+            if isinstance(f, FileField) and getattr(self, f.attname):
+                file_name = getattr(instance, 'get_%s_filename' % f.name)()
+                # If the file exists and no other object of this type references it,
+                # delete it from the filesystem.
+                if os.path.exists(file_name) and not s_opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
+                    os.remove(file_name)
+        # Run any post-delete hooks.
+        if hasattr(instance, '_post_delete'):
+            instance._post_delete()
+        
     db.db.commit()
-    setattr(self, opts.pk.attname, None)
-    for f in opts.fields:
-        if isinstance(f, FileField) and getattr(self, f.attname):
-            file_name = getattr(self, 'get_%s_filename' % f.name)()
-            # If the file exists and no other object of this type references it,
-            # delete it from the filesystem.
-            if os.path.exists(file_name) and not opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
-                os.remove(file_name)
-    # Run any post-delete hooks.
-    if hasattr(self, '_post_delete'):
-        self._post_delete()
+    
 
 def method_get_next_in_order(opts, order_field, self):
     if not hasattr(self, '_next_in_order_cache'):
@@ -1113,109 +1113,6 @@
         setattr(self, cache_var, retrieved_obj)
     return getattr(self, cache_var)
 
-# Handles getting many-to-many related objects.
-# Example: Poll.get_site_list()
-def method_get_many_to_many(field_with_rel, self):
-    rel = field_with_rel.rel.to
-    cache_var = '_%s_cache' % field_with_rel.name
-    if not hasattr(self, cache_var):
-        mod = rel.get_model_module()
-        sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
-            (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]),
-            db.db.quote_name(rel.db_table),
-            db.db.quote_name(field_with_rel.get_m2m_db_table(self._meta)),
-            db.db.quote_name(rel.pk.column),
-            db.db.quote_name(rel.object_name.lower() + '_id'),
-            db.db.quote_name(self._meta.object_name.lower() + '_id'), rel.get_order_sql('a'))
-        cursor = db.db.cursor()
-        cursor.execute(sql, [getattr(self, self._meta.pk.attname)])
-        setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
-    return getattr(self, cache_var)
-
-# Handles setting many-to-many relationships.
-# Example: Poll.set_sites()
-def method_set_many_to_many(rel_field, self, id_list):
-    current_ids = [obj.id for obj in method_get_many_to_many(rel_field, self)]
-    ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []
-    for current_id in current_ids:
-        if current_id in id_list:
-            del ids_to_add[current_id]
-        else:
-            ids_to_delete.append(current_id)
-    ids_to_add = ids_to_add.keys()
-    # Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.
-    if not ids_to_delete and not ids_to_add:
-        return False # No change
-    rel = rel_field.rel.to
-    m2m_table = rel_field.get_m2m_db_table(self._meta)
-    cursor = db.db.cursor()
-    this_id = getattr(self, self._meta.pk.attname)
-    if ids_to_delete:
-        sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
-            (db.db.quote_name(m2m_table),
-            db.db.quote_name(self._meta.object_name.lower() + '_id'),
-            db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))
-        cursor.execute(sql, [this_id])
-    if ids_to_add:
-        sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
-            (db.db.quote_name(m2m_table),
-            db.db.quote_name(self._meta.object_name.lower() + '_id'),
-            db.db.quote_name(rel.object_name.lower() + '_id'))
-        cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
-    db.db.commit()
-    try:
-        delattr(self, '_%s_cache' % rel_field.name) # clear cache, if it exists
-    except AttributeError:
-        pass
-    return True
-
-# Handles related-object retrieval.
-# Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()
-def method_get_related(method_name, rel_mod, rel_field, self, **kwargs):
-    if self._meta.has_related_links and rel_mod.Klass._meta.module_name == 'relatedlinks':
-        kwargs['object_id__exact'] = getattr(self, rel_field.rel.field_name)
-    else:
-        kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname)
-    kwargs.update(rel_field.rel.lookup_overrides)
-    return getattr(rel_mod, method_name)(**kwargs)
-
-# Handles adding related objects.
-# Example: Poll.add_choice()
-def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs):
-    init_kwargs = dict(zip([f.attname for f in rel_obj.fields if f != rel_field and not isinstance(f, AutoField)], args))
-    init_kwargs.update(kwargs)
-    for f in rel_obj.fields:
-        if isinstance(f, AutoField):
-            init_kwargs[f.attname] = None
-    init_kwargs[rel_field.name] = self
-    obj = rel_mod.Klass(**init_kwargs)
-    obj.save()
-    return obj
-
-# Handles related many-to-many object retrieval.
-# Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()
-def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs):
-    kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname)
-    return getattr(rel_mod, method_name)(**kwargs)
-
-# Handles setting many-to-many related objects.
-# Example: Album.set_songs()
-def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
-    id_list = map(int, id_list) # normalize to integers
-    rel = rel_field.rel.to
-    m2m_table = rel_field.get_m2m_db_table(rel_opts)
-    this_id = getattr(self, self._meta.pk.attname)
-    cursor = db.db.cursor()
-    cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
-        (db.db.quote_name(m2m_table),
-        db.db.quote_name(rel.object_name.lower() + '_id')), [this_id])
-    sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
-        (db.db.quote_name(m2m_table),
-        db.db.quote_name(rel.object_name.lower() + '_id'),
-        db.db.quote_name(rel_opts.object_name.lower() + '_id'))
-    cursor.executemany(sql, [(this_id, i) for i in id_list])
-    db.db.commit()
-
 # ORDERING METHODS #########################
 
 def method_set_order(ordered_obj, self, id_list):
@@ -1241,91 +1138,7 @@
     cursor.execute(sql, [rel_val])
     return [r[0] for r in cursor.fetchall()]
 
-# DATE-RELATED METHODS #####################
 
-def method_get_next_or_previous(get_object_func, opts, field, is_next, self, **kwargs):
-    op = is_next and '>' or '<'
-    kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
-        (db.db.quote_name(field.column), op, db.db.quote_name(field.column),
-        db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), op))
-    param = str(getattr(self, field.attname))
-    kwargs.setdefault('params', []).extend([param, param, getattr(self, opts.pk.attname)])
-    kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + opts.pk.name]
-    kwargs['limit'] = 1
-    return get_object_func(**kwargs)
-
-# CHOICE-RELATED METHODS ###################
-
-def method_get_display_value(field, self):
-    value = getattr(self, field.attname)
-    return dict(field.choices).get(value, value)
-
-# FILE-RELATED METHODS #####################
-
-def method_get_file_filename(field, self):
-    return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
-
-def method_get_file_url(field, self):
-    if getattr(self, field.attname): # value is not blank
-        import urlparse
-        return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
-    return ''
-
-def method_get_file_size(field, self):
-    return os.path.getsize(method_get_file_filename(field, self))
-
-def method_save_file(field, self, filename, raw_contents):
-    directory = field.get_directory_name()
-    try: # Create the date-based directory if it doesn't exist.
-        os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
-    except OSError: # Directory probably already exists.
-        pass
-    filename = field.get_filename(filename)
-
-    # If the filename already exists, keep adding an underscore to the name of
-    # the file until the filename doesn't exist.
-    while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
-        try:
-            dot_index = filename.rindex('.')
-        except ValueError: # filename has no dot
-            filename += '_'
-        else:
-            filename = filename[:dot_index] + '_' + filename[dot_index:]
-
-    # Write the file to disk.
-    setattr(self, field.attname, filename)
-    fp = open(getattr(self, 'get_%s_filename' % field.name)(), 'wb')
-    fp.write(raw_contents)
-    fp.close()
-
-    # Save the width and/or height, if applicable.
-    if isinstance(field, ImageField) and (field.width_field or field.height_field):
-        from django.utils.images import get_image_dimensions
-        width, height = get_image_dimensions(getattr(self, 'get_%s_filename' % field.name)())
-        if field.width_field:
-            setattr(self, field.width_field, width)
-        if field.height_field:
-            setattr(self, field.height_field, height)
-
-    # Save the object, because it has changed.
-    self.save()
-
-# IMAGE FIELD METHODS ######################
-
-def method_get_image_width(field, self):
-    return _get_image_dimensions(field, self)[0]
-
-def method_get_image_height(field, self):
-    return _get_image_dimensions(field, self)[1]
-
-def _get_image_dimensions(field, self):
-    cachename = "__%s_dimensions_cache" % field.name
-    if not hasattr(self, cachename):
-        from django.utils.images import get_image_dimensions
-        fname = getattr(self, "get_%s_filename" % field.name)()
-        setattr(self, cachename, get_image_dimensions(fname))
-    return getattr(self, cachename)
-
 ##############################################
 # HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
 ##############################################
@@ -1659,28 +1472,6 @@
     kwargs['limit'] = 1
     return function_get_object(opts, klass, does_not_exist_exception, **kwargs)
 
-def function_get_date_list(opts, field, *args, **kwargs):
-    from django.core.db.typecasts import typecast_timestamp
-    kind = args and args[0] or kwargs['kind']
-    assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
-    order = 'ASC'
-    if kwargs.has_key('_order'):
-        order = kwargs['_order']
-        del kwargs['_order']
-    assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
-    kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
-    if field.null:
-        kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \
-            (db.db.quote_name(opts.db_table), db.db.quote_name(field.column)))
-    select, sql, params = function_get_sql_clause(opts, **kwargs)
-    sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql)
-    cursor = db.db.cursor()
-    cursor.execute(sql, params)
-    # We have to manually run typecast_timestamp(str()) on the results, because
-    # MySQL doesn't automatically cast the result of date functions as datetime
-    # objects -- MySQL returns the values as strings, instead.
-    return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
-
 ###################################
 # HELPER FUNCTIONS (MANIPULATORS) #
 ###################################
@@ -1892,7 +1683,7 @@
                 # If, in the change stage, all of the core fields were blank and
                 # the primary key (ID) was provided, delete the item.
                 if change and all_cores_blank and old_rel_obj:
-                    new_rel_obj.delete()
+                    new_rel_obj.delete(seen_objs=[(opts, str(self.obj_key))])
                     self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
 
     # Save the order, if applicable.
=== django/core/meta/fields.py
==================================================================
--- django/core/meta/fields.py	(/django/trunk)	(revision 3877)
+++ django/core/meta/fields.py	(/local/mutually-referential)	(revision 3877)
@@ -55,9 +55,12 @@
         old_obj = opts.get_model_module().get_object(**{lookup_type: field_data})
     except ObjectDoesNotExist:
         return
-    if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
+    if hasattr(self, 'original_object') and \
+       getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname):
         return
-    raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
+    raise validators.ValidationError, \
+           _("%(optname)s with this %(fieldname)s already exists.") % \
+           {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
 
 class BoundField(object):
     def __init__(self, field, field_mapping, original):
@@ -282,7 +285,9 @@
                     core_field_names.extend(f.get_manipulator_field_names(name_prefix))
             # Now, if there are any, add the validator to this FormField.
             if core_field_names:
-                params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
+                params['validator_list'].append(
+                    validators.RequiredIfOtherFieldsGiven(core_field_names, 
+                                                           gettext_lazy("This field is required.")))
 
         # BooleanFields (CheckboxFields) are a special case. They don't take
         # is_required or validator_list.
@@ -344,6 +349,14 @@
     def bind(self, fieldmapping, original, bound_field_class=BoundField):
         return bound_field_class(self, fieldmapping, original)
 
+    def contribute_to_class(self, opts, new_mod, new_class ):
+        if self.choices:
+                # Add "get_thingie_display" method to get human-readable value.
+                setattr(new_class, 'get_%s_display' % self.name, self.method_get_display_value)
+                
+    def method_get_display_value(self, instance):
+        value = getattr(instance, self.attname)
+        return dict(self.choices).get(value, value)
 class AutoField(Field):
     empty_strings_allowed = False
     def __init__(self, *args, **kwargs):
@@ -378,7 +391,7 @@
 class CommaSeparatedIntegerField(CharField):
     def get_manipulator_field_objs(self):
         return [formfields.CommaSeparatedIntegerField]
-
+    
 class DateField(Field):
     empty_strings_allowed = False
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
@@ -421,6 +434,70 @@
         val = self._get_val_from_obj(obj)
         return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
 
+    def contribute_to_class(self, opts, new_mod, new_class ):
+        super(DateField,self).contribute_to_class(opts, new_mod, new_class)
+        # Add "get_next_by_thingie" and "get_previous_by_thingie" methods
+        # for all DateFields and DateTimeFields that cannot be null.
+        # EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()
+        if not self.null:
+            setattr(new_class, 'get_next_by_%s' % self.name, 
+                     curry(self.method_get_next, new_mod.get_object, opts))
+            setattr(new_class, 'get_previous_by_%s' % self.name, 
+                     curry(self.method_get_previous, new_mod.get_object, opts))
+        # Add "get_thingie_list" for all DateFields and DateTimeFields.
+        # EXAMPLE: polls.get_pub_date_list()
+        func = curry(self.function_get_date_list, opts)
+        func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects)" \
+                       "in which %s objects are available. The first parameter ('kind') must be" \
+                       " one of 'year', 'month' or 'day'." % opts.object_name
+        setattr(new_mod, 'get_%s_list' % self.name, func)
+
+    def function_get_date_list(self, opts, *args, **kwargs):
+        from django.core.db.typecasts import typecast_timestamp
+        from django.core import db
+        kind = args and args[0] or kwargs['kind']
+        assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
+        order = 'ASC'
+        if kwargs.has_key('_order'):
+            order = kwargs['_order']
+            del kwargs['_order']
+        assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
+        kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
+        if field.null:
+            kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \
+                (db.db.quote_name(opts.db_table), db.db.quote_name(field.column)))
+        select, sql, params = function_get_sql_clause(opts, **kwargs)
+        sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % \
+               (db.get_date_trunc_sql(kind, '%s.%s' % \
+                    (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql)
+        cursor = db.db.cursor()
+        cursor.execute(sql, params)
+        # We have to manually run typecast_timestamp(str()) on the results, because
+        # MySQL doesn't automatically cast the result of date functions as datetime
+        # objects -- MySQL returns the values as strings, instead.
+        return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
+
+    def method_get_next(self, get_object_func, opts, instance, **kwargs ):
+        return self.method_get_next_or_previous(get_object_func, opts, True, instance, **kwargs)
+
+    def method_get_previous(self, get_object_func, opts, instance, **kwargs ):
+        return self.method_get_next_or_previous(get_object_func, opts, False, instance, **kwargs)
+
+    def method_get_next_or_previous(self, get_object_func, opts, is_next, instance, **kwargs):
+        from django.core import db
+        op = is_next and '>' or '<'
+        kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
+            (db.db.quote_name(self.column), op, db.db.quote_name(self.column),
+            db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), op))
+        param = str(getattr(instance, self.attname))
+        kwargs.setdefault('params', []).extend([param, param, getattr(instance, opts.pk.attname)])
+        kwargs['order_by'] = [(not is_next and '-' or '') + self.name, (not is_next and '-' or '') + opts.pk.name]
+        kwargs['limit'] = 1
+        
+        
+        obj = get_object_func(**kwargs)
+        return obj
+
 class DateTimeField(DateField):
     def get_db_prep_save(self, value):
         # Casts dates into string format for entry into database.
@@ -485,7 +562,8 @@
                         self.always_test = True
                     def __call__(self, field_data, all_data):
                         if not all_data.get(self.other_file_field_name, False):
-                            c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, gettext_lazy("This field is required."))
+                            c = validators.RequiredIfOtherFieldsGiven(
+                                self.other_field_names, gettext_lazy("This field is required."))
                             c(field_data, all_data)
                 # First, get the core fields, if any.
                 core_field_names = []
@@ -496,7 +574,8 @@
                 if core_field_names:
                     field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
             else:
-                v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, gettext_lazy("This field is required."))
+                v = validators.RequiredIfOtherFieldNotGiven(
+                                field_list[1].field_name, gettext_lazy("This field is required."))
                 v.always_test = True
                 field_list[0].validator_list.append(v)
                 field_list[0].is_required = field_list[1].is_required = False
@@ -518,10 +597,11 @@
     def save_file(self, new_data, new_object, original_object, change, rel):
         upload_field_name = self.get_manipulator_field_names('')[0]
         if new_data.get(upload_field_name, False):
+            func = getattr(new_object, 'save_%s_file' % self.name)
             if rel:
-                getattr(new_object, 'save_%s_file' % self.name)(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"])
+                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"])
             else:
-                getattr(new_object, 'save_%s_file' % self.name)(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])
+                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])
 
     def get_directory_name(self):
         return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
@@ -531,6 +611,58 @@
         f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
         return os.path.normpath(f)
 
+    def contribute_to_class(self, opts, new_mod, new_class ):
+        super(FileField, self).contribute_to_class(opts, new_mod, new_class)
+        
+        setattr(new_class, 'get_%s_filename' % self.name, self.method_get_file_filename)
+        setattr(new_class, 'get_%s_url' % self.name, self.method_get_file_url)
+        setattr(new_class, 'get_%s_size' % self.name, self.method_get_file_size)
+        setattr(new_class, 'save_%s_file' % self.name, self.method_save_file)
+        
+    
+    def method_get_file_filename(self, instance):
+        return os.path.join(settings.MEDIA_ROOT, getattr(instance, self.attname))
+
+    def method_get_file_url(self, instance):
+        if getattr(instance, self.attname): # value is not blank
+            import urlparse
+            return urlparse.urljoin(settings.MEDIA_URL, getattr(instance, self.attname)).replace('\\', '/')
+        return ''
+    
+    def method_get_file_size(self, instance):
+        return os.path.getsize(method_get_file_filename(self, instance))
+    
+    def write_file(self, instance, filename, raw_contents):
+        directory = self.get_directory_name()
+        try: # Create the date-based directory if it doesn't exist.
+            os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
+        except OSError: # Directory probably already exists.
+            pass
+        filename = self.get_filename(filename)
+    
+        # If the filename already exists, keep adding an underscore to the name of
+        # the file until the filename doesn't exist.
+        while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
+            try:
+                dot_index = filename.rindex('.')
+            except ValueError: # filename has no dot
+                filename += '_'
+            else:
+                filename = filename[:dot_index] + '_' + filename[dot_index:]
+    
+        # Write the file to disk.
+        setattr(instance, self.attname, filename)
+        fp = open(getattr(instance, 'get_%s_filename' % self.name)(), 'wb')
+        fp.write(raw_contents)
+        fp.close()
+        
+    def method_save_file(self, instance, filename, raw_contents):
+        self.write_file(instance, filename, raw_contents)
+        
+        # Save the object, because it has changed.
+        instance.save()
+    method_save_file.alters_data = True
+
 class FilePathField(Field):
     def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
         self.path, self.match, self.recursive = path, match, recursive
@@ -568,6 +700,45 @@
                 setattr(new_object, self.height_field, getattr(original_object, self.height_field))
             new_object.save()
 
+
+    def contribute_to_class(self, opts, new_mod, new_class ):
+        super(ImageField,self).contribute_to_class(opts, new_mod, new_class)
+        
+        # Add get_BLAH_width and get_BLAH_height methods, but only
+        # if the image field doesn't have width and height cache
+        # fields.
+        if not self.width_field:
+            setattr(new_class, 'get_%s_width' % self.name, self.method_get_image_width)
+        if not self.height_field:
+            setattr(new_class, 'get_%s_height' % self.name, self.method_get_image_height)
+
+    def method_save_file(self, instance, filename, raw_contents):
+        self.write_file(instance, filename, raw_contents)
+        # Save the width and/or height, if applicable.
+        if (self.width_field or self.height_field):
+            from django.utils.images import get_image_dimensions
+            width, height = get_image_dimensions(getattr(instance, 'get_%s_filename' % self.name)())
+            if self.width_field:
+                setattr(instance, field.width_field, width)
+            if self.height_field:
+                setattr(instance, field.height_field, height)
+        # Save the object, because it has changed.
+        instance.save()
+
+    def method_get_image_width(self, instance):
+        return self._get_image_dimensions(instance)[0]
+    
+    def method_get_image_height(self, instance):
+        return self._get_image_dimensions(instance)[1]
+    
+    def _get_image_dimensions(self, instance):
+        cachename = "__%s_dimensions_cache" % self.name
+        if not hasattr(instance, cachename):
+            from django.utils.images import get_image_dimensions
+            fname = getattr(instance, "get_%s_filename" % field.name)()
+            setattr(instance, cachename, get_image_dimensions(fname))
+        return getattr(instance, cachename)
+
 class IntegerField(Field):
     empty_strings_allowed = False
     def get_manipulator_field_objs(self):
@@ -682,13 +853,45 @@
     def get_manipulator_field_objs(self):
         return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
 
-class ForeignKey(Field):
+class RelatedField(Field):
+    # Handles related-object retrieval.
+    # Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()
+    def method_get_related(self, method_name, rel_mod, instance,  **kwargs):
+        if instance._meta.has_related_links and rel_mod.Klass._meta.module_name == 'relatedlinks':
+            kwargs['object_id__exact'] = getattr(instance, self.rel.field_name)
+        else:
+            kwargs['%s__%s__exact' % (self.name, self.rel.to.pk.name)] = getattr(instance, self.rel.get_related_field().attname)
+        kwargs.update(self.rel.lookup_overrides)
+        return getattr(rel_mod, method_name)(**kwargs)
+    
+    # Example: Story.get_dateline()
+    def method_get_many_to_one(self, instance):
+        cache_var = self.get_cache_name()
+        if not hasattr(instance, cache_var):
+            val = getattr(instance, self.attname)
+            mod = self.rel.to.get_model_module()
+            if val is None:
+                raise getattr(mod, '%sDoesNotExist' % self.rel.to.object_name)
+            other_field = self.rel.get_related_field()
+            if other_field.rel:
+                params = {'%s__%s__exact' % (self.rel.field_name, other_field.rel.field_name): val}
+            else:
+                params = {'%s__exact'% self.rel.field_name: val}
+            retrieved_obj = mod.get_object(**params)
+            setattr(instance, cache_var, retrieved_obj)
+        return getattr(instance, cache_var)
+
+class ForeignKey(RelatedField):
     empty_strings_allowed = False
     def __init__(self, to, to_field=None, **kwargs):
         try:
             to_name = to._meta.object_name.lower()
         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
-            assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
+            #assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey" \
+            #                     " must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
+            assert isinstance(to, basestring) , "ForeignKey(%r) is invalid. First parameter to ForeignKey " \
+                                                "must be either a model, the string %r, " \
+                                                "or the name of a model" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
             kwargs['verbose_name'] = kwargs.get('verbose_name', '')
         else:
             to_field = to_field or to._meta.pk.name
@@ -722,7 +925,7 @@
         if value == '' or value == None:
            return None
         else:
-           return int(value)
+           return value
 
     def flatten_data(self, follow, obj = None):
         if not obj:
@@ -736,6 +939,59 @@
                   return { self.attname : choice_list[1][0] }
         return Field.flatten_data(self, follow, obj)
 
+    def contribute_to_related_class(self, related, klass):
+        " Add the 'get_thingie', 'get_thingie_count' and 'get_thingie_list' methods "
+        rel_mod = related.opts.get_model_module()
+        rel_obj_name = related.get_method_name_part()
+        
+        # Add "get_thingie" methods for many-to-one related objects.
+        # EXAMPLE: Poll.get_choice()
+        func = curry(self.method_get_related, 'get_object', rel_mod)
+        func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \
+            (related.opts.app_label, related.opts.module_name)
+        setattr(klass, 'get_%s' % rel_obj_name, func)
+        # Add "get_thingie_count" methods for many-to-one related objects.
+        # EXAMPLE: Poll.get_choice_count()
+        func = curry(self.method_get_related, 'get_count', rel_mod)
+        func.__doc__ = "Returns the number of associated `%s.%s` objects." % \
+            (related.opts.app_label, related.opts.module_name)
+        setattr(klass, 'get_%s_count' % rel_obj_name, func)
+        # Add "get_thingie_list" methods for many-to-one related objects.
+        # EXAMPLE: Poll.get_choice_list()
+        func = curry(self.method_get_related, 'get_list', rel_mod)
+        func.__doc__ = "Returns a list of associated `%s.%s` objects." % \
+             (related.opts.app_label, related.opts.module_name)
+        setattr(klass, 'get_%s_list' % rel_obj_name, func)
+        # Add "add_thingie" methods for many-to-one related objects,
+        # but only for related objects that are in the same app.
+        # EXAMPLE: Poll.add_choice()
+        if related.opts.app_label == klass._meta.app_label:
+            func = curry(self.method_add_related, related)
+            func.alters_data = True
+            setattr(klass, 'add_%s' % rel_obj_name, func)
+        del func, rel_obj_name, rel_mod # clean up
+    
+    # Handles adding related objects.
+    # Example: Poll.add_choice()
+    def method_add_related(self, related, instance,  *args, **kwargs):
+        init_kwargs = dict(zip([f.attname for f in related.opts.fields if f != related.field and not isinstance(f, AutoField)], args))
+        init_kwargs.update(kwargs)
+        for f in related.opts.fields:
+            if isinstance(f, AutoField):
+                init_kwargs[f.attname] = None
+        init_kwargs[related.field.name] = instance
+        obj = related.get_class()(**init_kwargs)
+        obj.save()
+        return obj
+    
+    def contribute_to_class(self, opts, new_mod, new_class):
+        # Add "get_thingie" methods for many-to-one related objects.
+        # EXAMPLES: Choice.get_poll(), Story.get_dateline()
+        func = lambda instance : self.method_get_many_to_one(instance)
+        func.__doc__ = "Returns the associated `%s.%s` object." % (self.rel.to.app_label, self.rel.to.module_name)
+        setattr(new_class, 'get_%s' % self.name, func)
+
+
 class ManyToManyField(Field):
     def __init__(self, to, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
@@ -799,7 +1055,122 @@
                    new_data[self.name] = [choices_list[0][0]]
         return new_data
 
-class OneToOneField(IntegerField):
+    def contribute_to_related_class(self, related, klass):
+        " Add 'get_thingie', 'get_thingie_count' and 'get_thingie_list' methods"
+        rel_mod = related.opts.get_model_module()
+        rel_obj_name = related.get_method_name_part()
+        setattr(klass, 'get_%s' % rel_obj_name, curry(self.__class__.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, self))
+        setattr(klass, 'get_%s_count' % rel_obj_name, curry(self.__class__.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, self))
+        setattr(klass, 'get_%s_list' % rel_obj_name, curry(self.__class__.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, self))
+        if related.opts.app_label == klass._meta.app_label:
+            func = curry(self.__class__.method_set_related_many_to_many, related.opts, self)
+            func.alters_data = True
+            setattr(klass, 'set_%s' % related.opts.module_name, func)
+        
+    # Handles related many-to-many object retrieval.
+    # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()
+    def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs):
+        kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname)
+        return getattr(rel_mod, method_name)(**kwargs)
+    method_get_related_many_to_many = staticmethod(method_get_related_many_to_many)
+    
+    # Handles setting many-to-many related objects.
+    # Example: Album.set_songs()
+    def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
+        from django.core import db
+        id_list = map(int, id_list) # normalize to integers
+        rel = rel_field.rel.to
+        m2m_table = rel_field.get_m2m_db_table(rel_opts)
+        this_id = getattr(self, self._meta.pk.attname)
+        cursor = db.db.cursor()
+        cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
+            (db.db.quote_name(m2m_table),
+             db.db.quote_name(rel.object_name.lower() + '_id')), 
+             [this_id])
+        sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % \
+             (db.db.quote_name(m2m_table),
+              db.db.quote_name(rel.object_name.lower() + '_id'),
+              db.db.quote_name(rel_opts.object_name.lower() + '_id'))
+        cursor.executemany(sql, [(this_id, i) for i in id_list])
+        db.db.commit()
+    method_set_related_many_to_many = staticmethod(method_set_related_many_to_many)
+
+    def contribute_to_class(self, opts, new_mod, new_class ):
+        # Add "get_thingie" methods for many-to-many related objects.
+        # EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
+        func = curry(self.method_get_many_to_many)
+        func.__doc__ = "Returns a list of associated `%s.%s` objects." %\
+                       (self.rel.to.app_label, self.rel.to.module_name)
+        setattr(new_class, 'get_%s_list' % self.rel.singular, func)
+        # Add "set_thingie" methods for many-to-many related objects.
+        # EXAMPLES: Poll.set_sites(), Story.set_bylines()
+        func = curry(self.method_set_many_to_many)
+        func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs." \
+                       "Note that it doesn't check whether the given IDs are valid." % \
+                       (self.rel.to.app_label, self.rel.to.module_name)
+        func.alters_data = True
+        setattr(new_class,'set_%s' % self.name , func)
+
+    # Handles getting many-to-many related objects.
+    # Example: Poll.get_site_list()
+    def method_get_many_to_many(self, instance):
+        from django.core import db
+        rel = self.rel.to
+        cache_var = '_%s_cache' % self.name
+        if not hasattr(instance, cache_var):
+            mod = rel.get_model_module()
+            sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
+                (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]),
+                db.db.quote_name(rel.db_table),
+                db.db.quote_name(self.get_m2m_db_table(instance._meta)),
+                db.db.quote_name(rel.pk.column),
+                db.db.quote_name(rel.object_name.lower() + '_id'),
+                db.db.quote_name(instance._meta.object_name.lower() + '_id'), rel.get_order_sql('a'))
+            cursor = db.db.cursor()
+            cursor.execute(sql, [getattr(instance, instance._meta.pk.attname)])
+            setattr(instance, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
+        return getattr(instance, cache_var)
+    
+    # Handles setting many-to-many relationships.
+    # Example: Poll.set_sites()
+    def method_set_many_to_many(self, instance, id_list):
+        from django.core import db
+        current_ids = [obj.id for obj in self.method_get_many_to_many(instance)]
+        ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []
+        for current_id in current_ids:
+            if current_id in id_list:
+                del ids_to_add[current_id]
+            else:
+                ids_to_delete.append(current_id)
+        ids_to_add = ids_to_add.keys()
+        # Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.
+        if not ids_to_delete and not ids_to_add:
+            return False # No change
+        rel = self.rel.to
+        m2m_table = self.get_m2m_db_table(instance._meta)
+        cursor = db.db.cursor()
+        this_id = getattr(instance, instance._meta.pk.attname)
+        if ids_to_delete:
+            sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
+                (db.db.quote_name(m2m_table),
+                db.db.quote_name(instance._meta.object_name.lower() + '_id'),
+                db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))
+            cursor.execute(sql, [this_id])
+        if ids_to_add:
+            sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
+                (db.db.quote_name(m2m_table),
+                db.db.quote_name(instance._meta.object_name.lower() + '_id'),
+                db.db.quote_name(rel.object_name.lower() + '_id'))
+            cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
+        db.db.commit()
+        try:
+            delattr(instance, '_%s_cache' % self.name) # clear cache, if it exists
+        except AttributeError:
+            pass
+        return True
+
+
+class OneToOneField(IntegerField,RelatedField):
     def __init__(self, to, to_field=None, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
         to_field = to_field or to._meta.pk.name
@@ -819,14 +1190,32 @@
         kwargs['primary_key'] = True
         IntegerField.__init__(self, **kwargs)
 
+    #HACK
+    def contribute_to_class(self, opts, new_mod, new_class ):
+        # Add "get_thingie" methods for one-to-one related objects.
+        # EXAMPLES: Choice.get_poll(), Story.get_dateline()
+        func = lambda instance: self.method_get_many_to_one(instance)
+        func.__doc__ = "Returns the associated `%s.%s` object." % (self.rel.to.app_label, self.rel.to.module_name)
+        setattr(new_class, 'get_%s' % self.name, func)
+        
+    def contribute_to_related_class(self, related, klass):
+        " Add the 'get_thingie' "
+        rel_mod = related.opts.get_model_module()
+        rel_obj_name = related.get_method_name_part()
+        # Add "get_thingie" methods for one-to-one related objects.
+        # EXAMPLE: Place.get_restaurants_restaurant()
+        func = curry(self.method_get_related, 'get_object', rel_mod)
+        func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name)
+        setattr(klass, 'get_%s' % rel_obj_name, func)
+
 class ManyToOne:
     def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
         max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
         related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
         try:
             self.to = to._meta
-        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
-            assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
+        except AttributeError: # to._meta doesn't exist, so it must be a string
+            #assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
             self.to = to
         self.field_name = field_name
         self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
@@ -840,6 +1229,7 @@
         "Returns the Field in the 'to' object to which this relationship is tied."
         return self.to.get_field(self.field_name)
 
+
 class ManyToMany:
     def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
         filter_interface=None, limit_choices_to=None, raw_id_admin=False):
@@ -864,6 +1254,7 @@
         self.lookup_overrides = lookup_overrides or {}
         self.raw_id_admin = raw_id_admin
 
+
 class BoundFieldLine(object):
     def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField):
         self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line]
=== django/core/management.py
==================================================================
--- django/core/management.py	(/django/trunk)	(revision 3877)
+++ django/core/management.py	(/local/mutually-referential)	(revision 3877)
@@ -62,6 +62,8 @@
     "Returns a list of the CREATE TABLE SQL statements for the given module."
     from django.core import db, meta
     final_output = []
+    opts_output = set()
+    pending_references = {} 
     for klass in mod._MODELS:
         opts = klass._meta
         table_output = []
@@ -81,9 +83,14 @@
                 if f.primary_key:
                     field_output.append('PRIMARY KEY')
                 if f.rel:
-                    field_output.append('REFERENCES %s (%s)' % \
-                        (db.db.quote_name(f.rel.to.db_table),
-                        db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column)))
+                    if f.rel.to in opts_output:
+                        field_output.append('REFERENCES %s (%s)' % \
+                            (db.db.quote_name(f.rel.to.db_table),
+                            db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column)))
+                    else:
+                        pr = pending_references.get(f.rel.to, [])
+                        pr.append( (opts, f) )
+                        pending_references[f.rel.to] = pr
                 table_output.append(' '.join(field_output))
         if opts.order_with_respect_to:
             table_output.append('%s %s NULL' % (db.db.quote_name('_order'), db.DATA_TYPES['IntegerField']))
@@ -96,6 +103,19 @@
             full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
         full_statement.append(');')
         final_output.append('\n'.join(full_statement))
+        if opts in pending_references:
+            for (rel_opts, f) in pending_references[opts]:
+                r_table = rel_opts.db_table
+                r_col = f.column
+                table =  opts.db_table
+                col = opts.get_field(f.rel.field_name).column
+                final_output.append( 'ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);'  % \
+                                     (db.db.quote_name(r_table), 
+                                      db.db.quote_name("%s_referencing_%s_%s" % (r_col,table,col)),
+                                      db.db.quote_name(r_col), db.db.quote_name(table),db.db.quote_name(col))
+                                     )
+            del pending_references[opts]
+        opts_output.add(opts)
 
     for klass in mod._MODELS:
         opts = klass._meta
@@ -145,6 +165,9 @@
     output = []
 
     # Output DROP TABLE statements for standard application tables.
+    to_delete = set()
+    
+    references_to_delete = {}
     for klass in mod._MODELS:
         try:
             if cursor is not None:
@@ -154,8 +177,42 @@
             # The table doesn't exist, so it doesn't need to be dropped.
             db.db.rollback()
         else:
+            opts = klass._meta
+            for f in opts.fields:
+                if f.rel and f.rel.to not in to_delete:
+                    refs = references_to_delete.get(f.rel.to, [])
+                    refs.append( (opts, f) )
+                    references_to_delete[f.rel.to] = refs
+
+            to_delete.add(opts) 
+    
+    for klass in mod._MODELS:
+        try:
+            if cursor is not None:
+                # Check whether the table exists.
+                cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(klass._meta.db_table))
+        except:
+            # The table doesn't exist, so it doesn't need to be dropped.
+            db.db.rollback()
+        else:
             output.append("DROP TABLE %s;" % db.db.quote_name(klass._meta.db_table))
-
+            if references_to_delete.has_key(klass._meta):
+                for opts, f in references_to_delete[klass._meta]: 
+                    col = f.column
+                    table = opts.db_table
+                    r_table = f.rel.to.db_table
+                    r_col = f.rel.to.get_field(f.rel.field_name).column
+                
+                    output.append( 'ALTER TABLE %s DROP CONSTRAINT %s;'  % \
+                                         (db.db.quote_name(table), 
+                                          db.db.quote_name("%s_referencing_%s_%s" % (col,r_table,r_col))
+                                         ))
+                
+            
+            
+            
+           
+           
     # Output DROP TABLE statements for many-to-many tables.
     for klass in mod._MODELS:
         opts = klass._meta

Property changes on: 
___________________________________________________________________
Name: svk:merge
 -bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:1054
 +bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/branches/new-admin:1433
 +bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:1565

