diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index fbd42524ae..c5e3e6232e 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -447,6 +447,7 @@ class ModelAdmin(BaseModelAdmin): 'form_url': mark_safe(form_url), 'opts': opts, 'content_type_id': ContentType.objects.get_for_model(model).id, + 'save_as': self.save_as, 'save_on_top': self.save_on_top, } context.update(extra_context) @@ -477,7 +478,8 @@ class ModelAdmin(BaseModelAdmin): if request.method == 'POST': form = ModelForm(request.POST, request.FILES) for FormSet in self.get_formsets(request): - inline_formset = FormSet(data=request.POST, files=request.FILES, instance=obj) + inline_formset = FormSet(data=request.POST, files=request.FILES, + instance=obj, save_as_new=request.POST.has_key("_saveasnew")) inline_formsets.append(inline_formset) if all_valid(inline_formsets) and form.is_valid(): return self.save_add(request, model, form, inline_formsets, '../%s/') diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index ed3768bbc5..3264e89b80 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -20,9 +20,7 @@ def submit_row(context): opts = context['opts'] change = context['change'] is_popup = context['is_popup'] - # TODO: Fix this hack. - # save_as = opts.admin.save_as - save_as = False + save_as = context['save_as'] return { 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), diff --git a/django/newforms/formsets.py b/django/newforms/formsets.py index b9e4a1fd02..8a76a54042 100644 --- a/django/newforms/formsets.py +++ b/django/newforms/formsets.py @@ -57,14 +57,18 @@ class BaseFormSet(StrAndUnicode): initial = {TOTAL_FORM_COUNT: self._total_form_count, INITIAL_FORM_COUNT: self._initial_form_count} self.management_form = ManagementForm(initial=initial, auto_id=self.auto_id, prefix=self.prefix) - # instantiate all the forms and put them in self.forms - self.forms = [] - for i in range(self._total_form_count): - self.forms.append(self._construct_form(i)) + # construct the forms in the formset + self._construct_forms() def __unicode__(self): return self.as_table() + def _construct_forms(self): + # instantiate all the forms and put them in self.forms + self.forms = [] + for i in xrange(self._total_form_count): + self.forms.append(self._construct_form(i)) + def _construct_form(self, i): """ Instantiates and returns the i-th form instance in a formset. diff --git a/django/newforms/models.py b/django/newforms/models.py index 0d946b68b0..b097cd7155 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -302,11 +302,13 @@ class BaseModelFormSet(BaseFormSet): """ model = None - def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, queryset=None): + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + queryset=None, **kwargs): self.queryset = queryset - kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} - kwargs['initial'] = [model_to_dict(obj) for obj in self.get_queryset()] - super(BaseModelFormSet, self).__init__(**kwargs) + defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} + defaults['initial'] = [model_to_dict(obj) for obj in self.get_queryset()] + defaults.update(kwargs) + super(BaseModelFormSet, self).__init__(**defaults) def get_queryset(self): if self.queryset is not None: @@ -386,12 +388,20 @@ def _modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f. class BaseInlineFormset(BaseModelFormSet): """A formset for child objects related to a parent.""" - def __init__(self, data=None, files=None, instance=None): + def __init__(self, data=None, files=None, instance=None, save_as_new=False): from django.db.models.fields.related import RelatedObject self.instance = instance + self.save_as_new = save_as_new # is there a better way to get the object descriptor? self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name) + + def _construct_forms(self): + from django.newforms.formsets import INITIAL_FORM_COUNT + if self.save_as_new: + self._total_form_count = self.management_form.cleaned_data[INITIAL_FORM_COUNT] + self._initial_form_count = 0 + super(BaseInlineFormset, self)._construct_forms() def get_queryset(self): """ diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index 89a4e0bf5c..9fd50e7521 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/modeltests/model_formsets/models.py @@ -232,4 +232,26 @@ As you can see, 'Le Spleen de Paris' is now a book belonging to Charles Baudelai Le Spleen de Paris Les Fleurs du Mal +The save_as_new parameter lets you re-associate the data to a new instance. +This is used in the admin for save_as functionality. + +>>> data = { +... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered +... 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data +... 'book_set-0-id': '1', +... 'book_set-0-title': 'Les Fleurs du Mal', +... 'book_set-1-id': '2', +... 'book_set-1-title': 'Le Spleen de Paris', +... 'book_set-2-title': '', +... } + +>>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True) +>>> formset.is_valid() +True + +>>> new_author = Author.objects.create(name='Charles Baudelaire') +>>> formset.instance = new_author +>>> [book for book in formset.save() if book.author.pk == new_author.pk] +[, ] + """}