Commit 0894643e authored by Adam Zapletal's avatar Adam Zapletal Committed by Tim Graham
Browse files

Fixed #23387 -- Kept "Save as new" button after validation errors in admin.

When "Save as new" is chosen and errors occur, only show the "Save as new"
button and not the other save buttons.

Thanks to Tino de Bruijn for doing the real work on this fix.
parent a2c3c2a1
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -1342,9 +1342,8 @@ class ModelAdmin(BaseModelAdmin):
                    'name': force_text(opts.verbose_name), 'key': escape(object_id)})

            if request.method == 'POST' and "_saveasnew" in request.POST:
                return self.add_view(request, form_url=reverse('admin:%s_%s_add' % (
                    opts.app_label, opts.model_name),
                    current_app=self.admin_site.name))
                object_id = None
                obj = None

        ModelForm = self.get_form(request, obj)
        if request.method == 'POST':
@@ -1366,6 +1365,8 @@ class ModelAdmin(BaseModelAdmin):
                else:
                    self.log_change(request, new_object, change_message)
                    return self.response_change(request, new_object)
            else:
                form_validated = False
        else:
            if add:
                initial = self.get_changeform_initial_data(request)
@@ -1401,6 +1402,12 @@ class ModelAdmin(BaseModelAdmin):
            preserved_filters=self.get_preserved_filters(request),
        )

        # Hide the "Save" and "Save and continue" buttons if "Save as New" was
        # previously chosen to prevent the interface from getting confusing.
        if request.method == 'POST' and not form_validated and "_saveasnew" in request.POST:
            context['show_save'] = False
            context['show_save_and_continue'] = False

        context.update(extra_context or {})

        return self.render_change_form(request, context, add=add, change=not add, obj=obj, form_url=form_url)
+4 −2
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ def submit_row(context):
    change = context['change']
    is_popup = context['is_popup']
    save_as = context['save_as']
    show_save = context.get('show_save', True)
    show_save_and_continue = context.get('show_save_and_continue', True)
    ctx = {
        'opts': opts,
        'show_delete_link': (
@@ -41,9 +43,9 @@ def submit_row(context):
            context['has_add_permission'] and not is_popup and
            (not save_as or context['add'])
        ),
        'show_save_and_continue': not is_popup and context['has_change_permission'],
        'show_save_and_continue': not is_popup and context['has_change_permission'] and show_save_and_continue,
        'is_popup': is_popup,
        'show_save': True,
        'show_save': show_save,
        'preserved_filters': context.get('preserved_filters'),
    }
    if context.get('original') is not None:
+1 −0
Original line number Diff line number Diff line
@@ -288,6 +288,7 @@ class ChildInline(admin.StackedInline):
class ParentAdmin(admin.ModelAdmin):
    model = Parent
    inlines = [ChildInline]
    save_as = True

    list_editable = ('name',)

+9 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ from django.contrib.contenttypes.fields import (
    GenericForeignKey, GenericRelation,
)
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.core.files.storage import FileSystemStorage
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@@ -311,11 +312,19 @@ class Vodcast(Media):
class Parent(models.Model):
    name = models.CharField(max_length=128)

    def clean(self):
        if self.name == '_invalid':
            raise ValidationError('invalid')


class Child(models.Model):
    parent = models.ForeignKey(Parent, editable=False)
    name = models.CharField(max_length=30, blank=True)

    def clean(self):
        if self.name == '_invalid':
            raise ValidationError('invalid')


@python_2_unicode_compatible
class EmptyModel(models.Model):
+53 −12
Original line number Diff line number Diff line
@@ -1151,18 +1151,59 @@ class SaveAsTests(TestCase):
        self.assertEqual(len(Person.objects.filter(name='John M')), 1)
        self.assertEqual(len(Person.objects.filter(id=self.per1.pk)), 1)

    def test_save_as_display(self):
        """
        Ensure that 'save as' is displayed when activated and after submitting
        invalid data aside save_as_new will not show us a form to overwrite the
        initial model.
        """
        change_url = reverse('admin:admin_views_person_change', args=(self.per1.pk,))
        response = self.client.get(change_url)
        self.assertTrue(response.context['save_as'])
        post_data = {'_saveasnew': '', 'name': 'John M', 'gender': 3, 'alive': 'checked'}
        response = self.client.post(change_url, post_data)
        self.assertEqual(response.context['form_url'], reverse('admin:admin_views_person_add'))
    def test_save_as_new_with_validation_errors(self):
        """
        Ensure that when you click "Save as new" and have a validation error,
        you only see the "Save as new" button and not the other save buttons,
        and that only the "Save as" button is visible.
        """
        response = self.client.post(reverse('admin:admin_views_person_change', args=(self.per1.pk,)), {
            '_saveasnew': '',
            'gender': 'invalid',
            '_addanother': 'fail',
        })
        self.assertContains(response, 'Please correct the errors below.')
        self.assertFalse(response.context['show_save_and_add_another'])
        self.assertFalse(response.context['show_save_and_continue'])
        self.assertTrue(response.context['show_save_as_new'])

    def test_save_as_new_with_validation_errors_with_inlines(self):
        parent = Parent.objects.create(name='Father')
        child = Child.objects.create(parent=parent, name='Child')
        response = self.client.post(reverse('admin:admin_views_parent_change', args=(parent.pk,)), {
            '_saveasnew': 'Save as new',
            'child_set-0-parent': parent.pk,
            'child_set-0-id': child.pk,
            'child_set-0-name': 'Child',
            'child_set-INITIAL_FORMS': 1,
            'child_set-MAX_NUM_FORMS': 1000,
            'child_set-MIN_NUM_FORMS': 0,
            'child_set-TOTAL_FORMS': 4,
            'name': '_invalid',
        })
        self.assertContains(response, 'Please correct the error below.')
        self.assertFalse(response.context['show_save_and_add_another'])
        self.assertFalse(response.context['show_save_and_continue'])
        self.assertTrue(response.context['show_save_as_new'])

    def test_save_as_new_with_inlines_with_validation_errors(self):
        parent = Parent.objects.create(name='Father')
        child = Child.objects.create(parent=parent, name='Child')
        response = self.client.post(reverse('admin:admin_views_parent_change', args=(parent.pk,)), {
            '_saveasnew': 'Save as new',
            'child_set-0-parent': parent.pk,
            'child_set-0-id': child.pk,
            'child_set-0-name': '_invalid',
            'child_set-INITIAL_FORMS': 1,
            'child_set-MAX_NUM_FORMS': 1000,
            'child_set-MIN_NUM_FORMS': 0,
            'child_set-TOTAL_FORMS': 4,
            'name': 'Father',
        })
        self.assertContains(response, 'Please correct the error below.')
        self.assertFalse(response.context['show_save_and_add_another'])
        self.assertFalse(response.context['show_save_and_continue'])
        self.assertTrue(response.context['show_save_as_new'])


@override_settings(ROOT_URLCONF="admin_views.urls")