Index: core/meta/__init__.py
===================================================================
--- core/meta/__init__.py	(revision 1494)
+++ core/meta/__init__.py	(working copy)
@@ -287,7 +287,7 @@
         fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
         where_constraints=None, object_name=None, app_label=None,
         exceptions=None, permissions=None, get_latest_by=None,
-        order_with_respect_to=None, module_constants=None):
+        order_with_respect_to=None, module_constants=None,restrict_delete=False):
 
         # Save the original function args, for use by copy(). Note that we're
         # NOT using copy.deepcopy(), because that would create a new copy of
@@ -332,6 +332,7 @@
             self.order_with_respect_to = None
         self.module_constants = module_constants or {}
         self.admin = admin
+        self.restrict_delete = restrict_delete
 
         # Calculate one_to_one_field.
         self.one_to_one_field = None
@@ -600,6 +601,7 @@
                 get_latest_by = meta_attrs.pop('get_latest_by', None),
                 order_with_respect_to = meta_attrs.pop('order_with_respect_to', None),
                 module_constants = meta_attrs.pop('module_constants', None),
+                restrict_delete = meta_attrs.pop('restrict_delete', False),
             )
 
         if meta_attrs != {}:
@@ -954,11 +956,9 @@
 
 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()
-    for related in opts.get_all_related_objects():
+    rels = set(opts.get_all_related_objects()).union(set(opts.get_all_related_many_to_many_objects())).difference(set(opts.get_followed_related_objects()))
+    has_nonfollows = False
+    for related in rels:
         rel_opts_name = related.get_method_name_part()
         if isinstance(related.field.rel, OneToOne):
             try:
@@ -966,34 +966,56 @@
             except ObjectDoesNotExist:
                 pass
             else:
-                sub_obj.delete()
+                has_nonfollows= True
+                break
         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():
+            if getattr(self, 'get_%s_count' % related.get_method_name_part())():
+                has_nonfollows= True
+                break
+
+    if has_nonfollows and self._meta.restrict_delete:
+        raise AssertionError, "Delete restricted - other objects depend upon this object and are not set to 'follow' (or don't have 'edit_inline' specified)."
+    else:
+        # Run any pre-delete hooks.
+        if hasattr(self, '_pre_delete'):
+            self._pre_delete()
+        cursor = db.db.cursor()
+        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)()
+                except ObjectDoesNotExist:
+                    pass
+                else:
+                    sub_obj.delete()
+            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():
+            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(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')),
+            (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
             [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.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()
+        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'):
