| 286 | |
| 287 | |
| 288 | # mixin class for #14099 regression |
| 289 | from django.forms.formsets import BaseFormSet, DELETION_FIELD_NAME |
| 290 | from django.forms.models import BaseModelFormSet |
| 291 | |
| 292 | class BaseCustomDeleteFormSet(BaseFormSet): |
| 293 | """ |
| 294 | A formset mix-in that lets a form decide if it's to be deleted |
| 295 | Works for BaseFormSets. Also works for ModelFormSets with #14099 |
| 296 | |
| 297 | form.should_delete() is called. The formset delete field is also suppressed. |
| 298 | """ |
| 299 | def add_fields(self, form, index): |
| 300 | super(BaseCustomDeleteFormSet, self).add_fields(form, index) |
| 301 | self.can_delete = True |
| 302 | if DELETION_FIELD_NAME in form.fields: |
| 303 | del form.fields[DELETION_FIELD_NAME] |
| 304 | |
| 305 | def _should_delete_form(self, form): |
| 306 | return hasattr(form, 'should_delete') and form.should_delete() |
| 307 | |
| 308 | |
| 309 | class FormfieldShouldDeleteFormTests(TestCase): |
| 310 | """ |
| 311 | Regression for #14099: BaseModelFormSet should use ModelFormSet method _should_delete_form |
| 312 | """ |
| 313 | |
| 314 | class BaseCustomDeleteModelFormSet(BaseModelFormSet, BaseCustomDeleteFormSet): |
| 315 | """ Model FormSet with CustomDelete MixIn """ |
| 316 | |
| 317 | class CustomDeleteUserForm(forms.ModelForm): |
| 318 | """ A model form with a 'should_delete' method """ |
| 319 | class Meta: |
| 320 | model = User |
| 321 | |
| 322 | def should_delete(self): |
| 323 | """ delete form if odd PK """ |
| 324 | return self.instance.id % 2 |
| 325 | |
| 326 | Normal_Formset = modelformset_factory(User, form=CustomDeleteUserForm, can_delete=True) |
| 327 | Delete_Formset = modelformset_factory(User, form=CustomDeleteUserForm, formset=BaseCustomDeleteModelFormSet) |
| 328 | |
| 329 | data = { |
| 330 | 'form-TOTAL_FORMS': '4', |
| 331 | 'form-INITIAL_FORMS': '0', |
| 332 | 'form-MAX_NUM_FORMS': '4', |
| 333 | 'form-0-username': 'John', |
| 334 | 'form-0-serial': '1', |
| 335 | 'form-1-username': 'Paul', |
| 336 | 'form-1-serial': '2', |
| 337 | 'form-2-username': 'George', |
| 338 | 'form-2-serial': '3', |
| 339 | 'form-3-username': 'Ringo', |
| 340 | 'form-3-serial': '5', |
| 341 | } |
| 342 | |
| 343 | bound_ids = { |
| 344 | 'form-INITIAL_FORMS': '4', |
| 345 | 'form-0-id': '1', |
| 346 | 'form-1-id': '2', |
| 347 | 'form-2-id': '3', |
| 348 | 'form-3-id': '4', |
| 349 | } |
| 350 | |
| 351 | delete_all_ids = { |
| 352 | 'form-0-DELETE': '1', |
| 353 | 'form-1-DELETE': '1', |
| 354 | 'form-2-DELETE': '1', |
| 355 | 'form-3-DELETE': '1', |
| 356 | } |
| 357 | |
| 358 | def test_init_database(self): |
| 359 | """ Add test data to database via formset """ |
| 360 | formset = self.Normal_Formset(self.data) |
| 361 | self.assertTrue(formset.is_valid()) |
| 362 | self.assertEqual(len(formset.save()), 4) |
| 363 | |
| 364 | def test_no_delete(self): |
| 365 | """ Verify base formset doesn't modify database """ |
| 366 | # reload database |
| 367 | self.test_init_database() |
| 368 | |
| 369 | # pass standard data dict & see none updated |
| 370 | data = dict(self.data) |
| 371 | data.update(self.bound_ids) |
| 372 | formset = self.Normal_Formset(data, queryset=User.objects.all()) |
| 373 | self.assertTrue(formset.is_valid()) |
| 374 | self.assertEqual(len(formset.save()), 0) |
| 375 | self.assertEqual(len(User.objects.all()), 4) |
| 376 | |
| 377 | def test_all_delete(self): |
| 378 | """ Verify base formset honors DELETE field """ |
| 379 | # reload database |
| 380 | self.test_init_database() |
| 381 | |
| 382 | # create data dict with all fields marked for deletion |
| 383 | data = dict(self.data) |
| 384 | data.update(self.bound_ids) |
| 385 | data.update(self.delete_all_ids) |
| 386 | formset = self.Normal_Formset(data, queryset=User.objects.all()) |
| 387 | self.assertTrue(formset.is_valid()) |
| 388 | self.assertEqual(len(formset.save()), 0) |
| 389 | self.assertEqual(len(User.objects.all()), 0) |
| 390 | |
| 391 | def test_custom_delete(self): |
| 392 | """ Verify custom_dete formset ignores DELETE field & uses form method """ |
| 393 | # reload database |
| 394 | self.test_init_database() |
| 395 | |
| 396 | # Create formset with custom Delete function |
| 397 | # create data dict with all fields marked for deletion |
| 398 | data = dict(self.data) |
| 399 | data.update(self.bound_ids) |
| 400 | data.update(self.delete_all_ids) |
| 401 | formset = self.Delete_Formset(data, queryset=User.objects.all()) |
| 402 | |
| 403 | # verify two were deleted |
| 404 | self.assertTrue(formset.is_valid()) |
| 405 | self.assertEqual(len(formset.save()), 0) |
| 406 | self.assertEqual(len(User.objects.all()), 2) |
| 407 | |
| 408 | # verify no "odd" PKs left |
| 409 | odd_ids = [user.id for user in User.objects.all() if user.id % 2] |
| 410 | self.assertEqual(len(odd_ids), 0) |