#27852: Admin Delete Object Block Page Doesn't Show All Related Objects Blocking
Deletion
-----------------------------------------+------------------------
               Reporter:  Kenny Loveall  |          Owner:  nobody
                   Type:  Bug            |         Status:  new
              Component:  contrib.admin  |        Version:  1.8
               Severity:  Normal         |       Keywords:
           Triage Stage:  Unreviewed     |      Has patch:  0
    Needs documentation:  0              |    Needs tests:  0
Patch needs improvement:  0              |  Easy pickings:  0
                  UI/UX:  0              |
-----------------------------------------+------------------------
 **STR:**

 1. Create three models, two of which reference the third and the
 references PROTECT on delete, like such:

 {{{
 class ModelA(models.Model):
     pass

 class ModelB(models.Model):
     fk = models.ForeignKey(ModelA, on_delete=models.PROTECT)

 class ModelC(models.Model):
     fk = models.ForeignKey(ModelA, on_delete=models.PROTECT)
 }}}

 2. Register these models with the admin site

 3. Create a ModelA, ModelB, & ModelC object. Make ModelB & ModelC
 reference ModelA, like such:
 {{{
 a = ModelA()
 b = ModelB(fk=a)
 c = ModelC(fk=a)
 }}}

 4. Go to the admin site and try to delete the ModelA object.

 **Expected Result:** Page listing both b & c as blocking objects
 **Actual Result:** Page listing only b or c, but not both.

 After investigation, I've figured out it's because the field.rel.on_delete
 call in db.models.deletion.py (excerpted below) throws a
 models.ProtectedError exception and therefore exits the loop early and
 does not check the rest of the related fields.
 {{{

     def collect(self, objs, source=None, nullable=False,
 collect_related=True,
             source_attr=None, reverse_dependency=False):

        [... omitted for brevity ...]

         if collect_related:
             for related in get_candidate_relations_to_delete(model._meta):
                 field = related.field
                 if field.rel.on_delete == DO_NOTHING:
                     continue
                 batches = self.get_del_batches(new_objs, field)
                 for batch in batches:
                     sub_objs = self.related_objects(related, batch)
                     if self.can_fast_delete(sub_objs, from_field=field):
                         self.fast_deletes.append(sub_objs)
                     elif sub_objs:
                         field.rel.on_delete(self, field, sub_objs,
 self.using)
             for field in model._meta.virtual_fields:
                 if hasattr(field, 'bulk_related_objects'):
                     # Its something like generic foreign key.
                     sub_objs = field.bulk_related_objects(new_objs,
 self.using)
                     self.collect(sub_objs,
                                  source=model,
                                  source_attr=field.rel.related_name,
                                  nullable=True)
 }}}

 This was discovered because we're implementing a soft-delete override in
 the Model layer (so anytime you try to delete things it just sets a
 timestamp instead of actually removing the data). This became an issue
 because we take the output of this and filtered it to only objects that we
 had that were deleted by our standards and found that under certain
 circumstances we were allowed to delete things when we shouldn't have
 been. Given this scenario being such an edge case, my hopes for getting
 this fixed aren't high; although I would argue that it's still a poor user
 experience for people that aren't trying to do what we're doing where it
 shows them only *some* of the things blocking the deletion instead of all
 of them. I'm hoping, however, that hopefully someone can at the very least
 show us if we can get a list of all the delete-blocking objects from
 somewhere else?

 Given where this is, the bug really seems to be coming from
 db.models.deletion.Collector so I'm not sure if the component field should
 be admin or models but I chose admin because that's where it's easily
 visible (and would impact general Django users).

--
Ticket URL: <https://code.djangoproject.com/ticket/27852>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/052.66acf8c25b0f356c28016fdc5092d758%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to