-
\ No newline at end of file
+
diff --git a/django/contrib/admin/templates/admin/edit_inline_tabular.html b/django/contrib/admin/templates/admin/edit_inline_tabular.html
index 95aaf0fefb7..e9535df02c5 100644
--- a/django/contrib/admin/templates/admin/edit_inline_tabular.html
+++ b/django/contrib/admin/templates/admin/edit_inline_tabular.html
@@ -1,45 +1,43 @@
{% load admin_modify %}
+
diff --git a/django/core/management.py b/django/core/management.py
index 37d699a7258..c78ebc4c28a 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -954,6 +954,16 @@ def get_validation_errors(outfile, app=None):
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:
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 4c66e364ad2..2dacd1cc559 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -74,7 +74,7 @@ class Field(object):
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 @@ class Field(object):
# 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 @@ class Field(object):
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 @@ class Field(object):
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 @@ class DateTimeField(DateField):
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 @@ class FileField(Field):
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))
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 461a4ec1625..d35e652b869 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -418,6 +418,10 @@ class ForeignKey(RelatedField, Field):
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 @@ class ForeignKey(RelatedField, Field):
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 @@ class OneToOneField(RelatedField, IntegerField):
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 @@ class OneToOneField(RelatedField, IntegerField):
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 @@ class ManyToManyField(RelatedField, Field):
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 @@ class ManyToManyField(RelatedField, Field):
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 @@ class ManyToManyField(RelatedField, Field):
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 @@ class ManyToOne:
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 {}
diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py
index a17973e4543..01964962636 100644
--- a/django/db/models/manipulators.py
+++ b/django/db/models/manipulators.py
@@ -139,6 +139,9 @@ class AutomaticManipulator(forms.Manipulator):
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 @@ class AutomaticManipulator(forms.Manipulator):
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:
diff --git a/django/db/models/related.py b/django/db/models/related.py
index b00088031e3..64b6cb2880b 100644
--- a/django/db/models/related.py
+++ b/django/db/models/related.py
@@ -41,6 +41,35 @@ class RelatedObject(object):
"""
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 @@ class RelatedObject(object):
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 "" % (self.name, self.field.name)
diff --git a/django/forms/__init__.py b/django/forms/__init__.py
index 4d3c24c373b..ba42706c9ef 100644
--- a/django/forms/__init__.py
+++ b/django/forms/__init__.py
@@ -131,10 +131,10 @@ class FormWrapper:
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 FormFieldCollection(FormFieldWrapper):
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 @@ class InlineObjectCollection:
def __iter__(self):
self.fill()
- return self._collections.values().__iter__()
+ return iter(self._collections.values())
def items(self):
self.fill()
@@ -250,22 +249,25 @@ class InlineObjectCollection:
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.