Index: django/db/models/manipulators.py
===================================================================
--- django/db/models/manipulators.py	(revision 112)
+++ django/db/models/manipulators.py	(working copy)
@@ -139,6 +139,9 @@
 
             if child_follow:
                 obj_list = expanded_data[related.var_name].items()
+                if not obj_list:
+                    continue
+
                 obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
 
                 # For each related item...
@@ -187,15 +190,8 @@
                         if param != None:
                             params[f.attname] = param
 
-                        # Related links are a special case, because we have to
-                        # manually set the "content_type_id" and "object_id" fields.
-                        if self.opts.has_related_links and related.opts.module_name == 'relatedlinks':
-                            contenttypes_mod = get_module('core', 'contenttypes')
-                            params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=self.opts.app_label, python_module_name__exact=self.opts.module_name).id
-                            params['object_id'] = new_object.id
-
                     # Create the related item.
-                    new_rel_obj = related.opts.get_model_module().Klass(**params)
+                    new_rel_obj = related.model(**params)
 
                     # If all the core fields were provided (non-empty), save the item.
                     if all_cores_given:
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 112)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -74,7 +74,7 @@
         self.primary_key = primary_key
         self.maxlength, self.unique = maxlength, unique
         self.blank, self.null = blank, null
-        self.rel, self.default = rel, default
+        self.core, self.rel, self.default = core, rel, default
         self.editable = editable
         self.validator_list = validator_list or []
         self.prepopulate_from = prepopulate_from
@@ -88,10 +88,6 @@
         # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
         self.db_index = db_index
 
-        self.deprecated_args = []
-        if core:
-            self.deprecated_args.append('core')
-
         # Increase the creation counter, and save our local copy.
         self.creation_counter = Field.creation_counter
         Field.creation_counter += 1
@@ -219,14 +215,29 @@
             params['validator_list'].append(curry(manipulator_validator_unique, self, opts, manipulator))
 
         # Only add is_required=True if the field cannot be blank. Primary keys
-        # are a special case.
-        params['is_required'] = not self.blank and not self.primary_key
+        # are a special case, and fields in a related context should set this
+        # as False, because they'll be caught by a separate validator --
+        # RequiredIfOtherFieldGiven.
+        params['is_required'] = not self.blank and not self.primary_key and not rel
 
         # BooleanFields (CheckboxFields) are a special case. They don't take
         # is_required or validator_list.
         if isinstance(self, BooleanField):
             del params['validator_list'], params['is_required']
 
+        # If this field is in a related context, check whether any other fields
+        # in the related object have core=True. If so, add a validator --
+        # RequiredIfOtherFieldsGiven -- to this FormField.
+        if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, FileField):
+            # First, get the core fields, if any.
+            core_field_names = []
+            for f in opts.fields:
+                if f.core and f != self:
+                    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.")))
+
         # Finally, add the field_names.
         field_names = self.get_manipulator_field_names(name_prefix)
         return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)]
@@ -239,9 +250,8 @@
         Given the full new_data dictionary (from the manipulator), returns this
         field's data.
         """
-        #if rel:
-        #    return new_data.get(self.name, [self.get_default()])[0]
-        #else:
+        if rel:
+            return new_data.get(self.name, [self.get_default()])[0]
         val = new_data.get(self.name, self.get_default())
         if not self.empty_strings_allowed and val == '' and self.null:
             val = None
@@ -397,12 +407,12 @@
 
     def get_manipulator_new_data(self, new_data, rel=False):
         date_field, time_field = self.get_manipulator_field_names('')
-        #if rel:
-        #    d = new_data.get(date_field, [None])[0]
-        #    t = new_data.get(time_field, [None])[0]
-        #else:
-        d = new_data.get(date_field, None)
-        t = new_data.get(time_field, None)
+        if rel:
+            d = new_data.get(date_field, [None])[0]
+            t = new_data.get(time_field, [None])[0]
+        else:
+            d = new_data.get(date_field, None)
+            t = new_data.get(time_field, None)
         if d is not None and t is not None:
             return datetime.datetime.combine(d, t)
         return self.get_default()
@@ -492,7 +502,10 @@
         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)
-            func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])
+            if rel:
+                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"])
+            else:
+                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))
Index: django/db/models/fields/related.py
===================================================================
--- django/db/models/fields/related.py	(revision 112)
+++ django/db/models/fields/related.py	(working copy)
@@ -418,6 +418,10 @@
             kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
 
         kwargs['rel'] = ManyToOne(to, to_field,
+            num_in_admin=kwargs.pop('num_in_admin', 3),
+            min_num_in_admin=kwargs.pop('min_num_in_admin', None),
+            max_num_in_admin=kwargs.pop('max_num_in_admin', None),
+            num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
             edit_inline=kwargs.pop('edit_inline', False),
             related_name=kwargs.pop('related_name', None),
             limit_choices_to=kwargs.pop('limit_choices_to', None),
@@ -427,10 +431,6 @@
 
         self.db_index = True
 
-        for name in ('num_in_admin', 'min_num_in_admin', 'max_num_in_admin', 'num_extra_on_change'):
-            if name in kwargs:
-                self.deprecated_args.append(name)
-
     def get_attname(self):
         return '%s_id' % self.name
 
@@ -501,6 +501,7 @@
             kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
 
         kwargs['rel'] = OneToOne(to, to_field,
+            num_in_admin=kwargs.pop('num_in_admin', 0),
             edit_inline=kwargs.pop('edit_inline', False),
             related_name=kwargs.pop('related_name', None),
             limit_choices_to=kwargs.pop('limit_choices_to', None),
@@ -511,10 +512,6 @@
 
         self.db_index = True
 
-        for name in ('num_in_admin',):
-            if name in kwargs:
-                self.deprecated_args.append(name)
-
     def get_attname(self):
         return '%s_id' % self.name
 
@@ -534,6 +531,7 @@
     def __init__(self, to, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', None)
         kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
+            num_in_admin=kwargs.pop('num_in_admin', 0),
             related_name=kwargs.pop('related_name', None),
             filter_interface=kwargs.pop('filter_interface', None),
             limit_choices_to=kwargs.pop('limit_choices_to', None),
@@ -542,9 +540,6 @@
         if kwargs["rel"].raw_id_admin:
             kwargs.setdefault("validator_list", []).append(self.isValidIDList)
         Field.__init__(self, **kwargs)
-        for name in ('num_in_admin'):
-            if name in kwargs:
-                self.deprecated_args.append(name)
 
         if self.rel.raw_id_admin:
             msg = gettext_lazy('Separate multiple IDs with commas.')
@@ -641,15 +636,17 @@
         pass
 
 class ManyToOne:
-    def __init__(self, to, field_name, edit_inline=False,
+    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:
             to._meta
-        except AttributeError:
+        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
             assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
         self.to, self.field_name = to, field_name
-        self.edit_inline = edit_inline
-        self.related_name = related_name
+        self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
+        self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
+        self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
         self.limit_choices_to = limit_choices_to or {}
         self.lookup_overrides = lookup_overrides or {}
         self.raw_id_admin = raw_id_admin
@@ -660,22 +657,23 @@
         return self.to._meta.get_field(self.field_name)
 
 class OneToOne(ManyToOne):
-    def __init__(self, to, field_name, edit_inline=False,
+    def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
         related_name=None, limit_choices_to=None, lookup_overrides=None,
         raw_id_admin=False):
         self.to, self.field_name = to, field_name
-        self.edit_inline = edit_inline
+        self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
         self.related_name = related_name
         self.limit_choices_to = limit_choices_to or {}
         self.lookup_overrides = lookup_overrides or {}
         self.raw_id_admin = raw_id_admin
         self.multiple = False
-        
+
 class ManyToMany:
-    def __init__(self, to, singular=None, related_name=None,
+    def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
         filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
         self.to = to
         self.singular = singular or None
+        self.num_in_admin = num_in_admin
         self.related_name = related_name
         self.filter_interface = filter_interface
         self.limit_choices_to = limit_choices_to or {}
Index: django/db/models/related.py
===================================================================
--- django/db/models/related.py	(revision 112)
+++ django/db/models/related.py	(working copy)
@@ -41,6 +41,35 @@
         """
         return data # TODO
 
+    def get_list(self, parent_instance=None):
+        "Get the list of this type of object from an instance of the parent class."
+        if parent_instance is not None:
+            attr = getattr(parent_instance, self.get_accessor_name())
+            if self.field.rel.multiple:
+                # For many-to-many relationships, return a list of objects
+                # corresponding to the xxx_num_in_admin options of the field
+                objects = list(attr.all())
+
+                count = len(objects) + self.field.rel.num_extra_on_change
+                if self.field.rel.min_num_in_admin:
+                   count = max(count, self.field.rel.min_num_in_admin)
+                if self.field.rel.max_num_in_admin:
+                   count = min(count, self.field.rel.max_num_in_admin)
+
+                change = count - len(objects)
+                if change > 0:
+                    return objects + [None for _ in range(change)]
+                if change < 0:
+                    return objects[:change]
+                else: # Just right
+                    return objects
+            else:
+                # A one-to-one relationship, so just return the single related
+                # object
+                return [attr]
+        else:
+            return [None for _ in range(self.field.rel.num_in_admin)]
+
     def editable_fields(self):
         "Get the fields in this class that should be edited inline."
         return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
@@ -62,6 +91,30 @@
         over[self.field.name] = False
         return self.opts.get_follow(over)
 
+    def get_manipulator_fields(self, opts, manipulator, change, follow):
+        if self.field.rel.multiple:
+            if change:
+                attr = getattr(manipulator.original_object, self.get_accessor_name())
+                count = attr.count()
+                count += self.field.rel.num_extra_on_change
+                if self.field.rel.min_num_in_admin:
+                    count = max(count, self.field.rel.min_num_in_admin)
+                if self.field.rel.max_num_in_admin:
+                    count = min(count, self.field.rel.max_num_in_admin)
+            else:
+                count = self.field.rel.num_in_admin
+        else:
+            count = 1
+
+        fields = []
+        for i in range(count):
+            for f in self.opts.fields + self.opts.many_to_many:
+                if follow.get(f.name, False):
+                    prefix = '%s.%d.' % (self.var_name, i)
+                    fields.extend(f.get_manipulator_fields(self.opts, manipulator, change,
+                                                           name_prefix=prefix, rel=True))
+        return fields
+
     def __repr__(self):
         return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
 
Index: django/forms/__init__.py
===================================================================
--- django/forms/__init__.py	(revision 112)
+++ django/forms/__init__.py	(working copy)
@@ -131,10 +131,10 @@
     def fill_inline_collections(self):
         if not self._inline_collections:
             ic = []
-            children = self.manipulator.children.items()
-            for rel_obj, child_manips  in children:
+            related_objects = self.manipulator.get_related_objects()
+            for rel_obj in related_objects:
                 data = rel_obj.extract_data(self.data)
-                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, child_manips, data, self.error_dict)
+                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
                 ic.append(inline_collection)
             self._inline_collections = ic
 
@@ -213,12 +213,11 @@
 
 class InlineObjectCollection:
     "An object that acts like a sparse list of form field collections."
-    def __init__(self, parent_manipulator, rel_obj,child_manips, data, errors):
+    def __init__(self, parent_manipulator, rel_obj, data, errors):
         self.parent_manipulator = parent_manipulator
         self.rel_obj = rel_obj
         self.data = data
         self.errors = errors
-        self.child_manips = child_manips
         self._collections = None
         self.name = rel_obj.name
 
@@ -240,7 +239,7 @@
 
     def __iter__(self):
         self.fill()
-        return self._collections.values().__iter__()
+        return iter(self._collections.values())
 
     def items(self):
         self.fill()
@@ -250,22 +249,25 @@
         if self._collections:
             return
         else:
-            #var_name = self.rel_obj.opts.object_name.lower()
-            cols = {}
-            #orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object  or None
-            #orig_list = self.rel_obj.get_list(orig)
+            var_name = self.rel_obj.opts.object_name.lower()
+            collections = {}
+            orig = None
+            if hasattr(self.parent_manipulator, 'original_object'):
+                orig = self.parent_manipulator.original_object
+            orig_list = self.rel_obj.get_list(orig)
 
-            for i, manip in enumerate(self.child_manips) :
-                if manip and not manip.needs_deletion:
-                    collection = {'original': manip.original_object}
-                    for field in manip.fields:
-                        errors = self.errors.get(field.field_name, [])
+            for i, instance in enumerate(orig_list):
+                collection = {'original': instance}
+                for f in self.rel_obj.editable_fields():
+                    for field_name in f.get_manipulator_field_names(''):
+                        full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+                        field = self.parent_manipulator[full_field_name]
                         data = field.extract_data(self.data)
-                        last_part = field.field_name[field.field_name.rindex('.') + 1:]
-                        collection[last_part] = FormFieldWrapper(field, data, errors)
+                        errors = self.errors.get(full_field_name, [])
+                        collection[field_name] = FormFieldWrapper(field, data, errors)
+                collections[i] = FormFieldCollection(collection)
+            self._collections = collections
 
-                    cols[i] = FormFieldCollection(collection)
-            self._collections = cols
 
 class FormField:
     """Abstract class representing a form field.
Index: django/core/management.py
===================================================================
--- django/core/management.py	(revision 112)
+++ django/core/management.py	(working copy)
@@ -956,6 +956,16 @@
                 except models.FieldDoesNotExist:
                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
 
+        # Check core=True, if needed.
+        for related in opts.get_followed_related_objects():
+            try:
+                for f in related.opts.fields:
+                    if f.core:
+                        raise StopIteration
+                e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
+            except StopIteration:
+                pass
+
         # Check unique_together.
         for ut in opts.unique_together:
             for field_name in ut:
Index: django/contrib/admin/templates/admin/edit_inline_stacked.html
===================================================================
--- django/contrib/admin/templates/admin/edit_inline_stacked.html	(revision 112)
+++ django/contrib/admin/templates/admin/edit_inline_stacked.html	(working copy)
@@ -12,11 +12,5 @@
             {% admin_field_line bound_field %}
          {% endif %}
       {% endfor %}
-      <div class="item actions">
-      <button class="deletebutton" name="command" value="{{bound_related_object.relation.var_name}}.{{fcw.index}}.delete">Delete</button>
-      </div>
     {% endfor %}
-    <div class="collection actions">
-    <button class="addbutton" name="command" value="{{bound_related_object.relation.var_name}}.add">Add</button>
-	</div>
-</fieldset>
\ No newline at end of file
+</fieldset>
Index: django/contrib/admin/templates/admin/edit_inline_tabular.html
===================================================================
--- django/contrib/admin/templates/admin/edit_inline_tabular.html	(revision 112)
+++ django/contrib/admin/templates/admin/edit_inline_tabular.html	(working copy)
@@ -1,45 +1,43 @@
 {% load admin_modify %}
+<fieldset class="module">
+   <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
+   <thead><tr>
+   {% for fw in bound_related_object.field_wrapper_list %}
+      {% if fw.needs_header %}
+         <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
+      {% endif %}
+   {% endfor %}
+   {% for fcw in bound_related_object.form_field_collection_wrappers %}
+      {% if change %}{% if original_row_needed %}
+         {% if fcw.obj.original %}
+            <tr class="row-label {% cycle row1,row2 %}"><td colspan="{{ num_headers }}"><strong>{{ fcw.obj.original }}</strong></tr>
+         {% endif %}
+      {% endif %}{% endif %}
+      {% if fcw.obj.errors %}
+         <tr class="errorlist"><td colspan="{{ num_headers }}">
+            {{ fcw.obj.html_combined_error_list }}
+         </tr>
+      {% endif %}
+      <tr class="{% cycle row1,row2 %}">
+      {% for bound_field in fcw.bound_fields %}
+         {% if not bound_field.hidden %}
+         <td {{ bound_field.cell_class_attribute }}>
+            {% field_widget bound_field %}
+         </td>
+         {% endif %}
+      {% endfor %}
+      {% if bound_related_object.show_url %}<td>
+         {% if fcw.obj.original %}<a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a>{% endif %}
+      </td>{% endif %}
+      </tr>
 
-<fieldset class="module editinline">
-    <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2>
-	<table>
-		<thead>
-			<tr>
-			    {% for fw in bound_related_object.field_wrapper_list %}
-			        {% if fw.needs_header %}
-			            <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
-			        {% endif %}
-			    {% endfor %}
-				<th>&nbsp;</th>
-			</tr> 
-		</thead>
-		<tfoot>
-			<tr>
-				<td colspan='{{ num_headers }}'>
-				    <button class="addlink" name="command" value="{{ bound_related_object.relation.var_name }}.add">
-				        Add another {{ bound_related_object.relation.opts.verbose_name }}
-				    </button>
-				</td>
-			</tr>
-		</tfoot>
-		<tbody>
-            {% for fcw in bound_related_object.form_field_collection_wrappers %}
-                <tr class="{% cycle row1,row2 %}{% if fcw.obj.errors %} error{% endif %}">
-                    {% for bound_field in fcw.bound_fields %}
-                        {% if not bound_field.hidden %}
-                            <td {{ bound_field.cell_class_attribute }}>
-                                {{ bound_field.html_error_list }}
-                                {% field_widget bound_field %}
-                            </td>
-                        {% endif %}
-                    {% endfor %}
-                    <td class="controls" >
-                    	<button class="inline-deletelink" name="command" value="{{ bound_related_object.relation.var_name }}.{{ fcw.index }}.delete">
-                    	    Delete {{ bound_related_object.relation.opts.verbose_name }}
-                    	</button>
-                    </td>
-                </tr>
-            {% endfor %}
-		</tbody>
-	</table>
-</fieldset>
\ No newline at end of file
+   {% endfor %} </table>
+
+   {% for fcw in bound_related_object.form_field_collection_wrappers %}
+      {% for bound_field in fcw.bound_fields %}
+         {% if bound_field.hidden %}
+            {% field_widget bound_field %}
+         {% endif %}
+      {% endfor %}
+   {% endfor %}
+</fieldset>
