deletion of "distant" related objects

9 views
Skip to first unread message

Margie

unread,
Apr 12, 2009, 2:29:41 AM4/12/09
to Django users
I am having some trouble with the deletion of related objects that are
multiple levels away from the object being deleted. I've read a bunch
of stuff written on deletion of related objects, but nothing on this
particular problem - hoping someone can help.

Say I have a model like this:

class Publisher(models.Model):
name = models.CharField(max_length=100)

class Book(models.Model):
publisher = models.ForeignKey(Publisher, blank=True, null=True)

class Reader(models.Model):
book = models.ForeignKey(Book, blank=True, null=True)

When a publisher is deleted, I would its book_set to be deleted, and
this happens by default. However, when a book is deleted, either by
deleting the book explicitely or due to its related publisher being
deleted, I don't want the book's reader_set to be deleted. In Book I
have this:

class Book(models.Model):
def delete(self):
self.reader_set.clear() # make any readers of the book no longer
reference the book to be deleted
super(Book, self).delete()

This works if I explicitly delete a book (ie, by "works", I mean that
the book's readers stay around). However, if I delete a publisher, it
seems that during Model::delete(), when _collect_sub_objects() runs,
it runs through all of the related objects finds the related books,
and then runs through the books' related objects and find the related
readers. It targets them for deletion and then at some point they get
deleted. It seems to me (I don't fully grock this code, so I could be
wrong here) that by the time my Book::delete() code runs, the readers
of the book have already been targeted for deletion and the
self.reader_set.clear() is not having the effect I want. So I'm
trying to figure out how I should avoid the readers getting deleted in
this case?

Thanks for any insights!

Margie

Malcolm Tredinnick

unread,
Apr 12, 2009, 3:06:13 AM4/12/09
to django...@googlegroups.com
On Sat, 2009-04-11 at 23:29 -0700, Margie wrote:
> I am having some trouble with the deletion of related objects that are
> multiple levels away from the object being deleted. I've read a bunch
> of stuff written on deletion of related objects, but nothing on this
> particular problem - hoping someone can help.
>
> Say I have a model like this:
>
> class Publisher(models.Model):
> name = models.CharField(max_length=100)
>
> class Book(models.Model):
> publisher = models.ForeignKey(Publisher, blank=True, null=True)
>
> class Reader(models.Model):
> book = models.ForeignKey(Book, blank=True, null=True)
>
> When a publisher is deleted, I would its book_set to be deleted, and
> this happens by default. However, when a book is deleted, either by
> deleting the book explicitely or due to its related publisher being
> deleted, I don't want the book's reader_set to be deleted. In Book I
> have this:
>
> class Book(models.Model):
> def delete(self):
> self.reader_set.clear() # make any readers of the book no longer
> reference the book to be deleted
> super(Book, self).delete()

The basic problem you're up against here is that a model's delete()
method is not necessarily going to be run when it is deleted indirectly.
Django tries to optimise deletes and updates so that they are single (or
minimal numbers of) SQL queries, which means not calling a method on
each instance, but, rather, doing a bulk delete at the database level.

This is documented, although it's one of those things that doesn't
immediately jump out:
http://docs.djangoproject.com/en/dev/topics/db/queries/#topics-db-queries-delete

(The queryset and model documentation is a bit all over the place at the
moment -- we need a few more links between places.)

I think that's really going to be the showstopper here. You can't hope
to control the bulk delete (which includes related object deletion) at
that sort of level.

Regards,
Malcolm

Margie

unread,
Apr 12, 2009, 3:40:28 AM4/12/09
to Django users
I see. So then I think what you are saying is that if I want to avoid
these "readers" getting deleted, prior to deleting my publisher I need
to find all readers of the books publised by that publisher, and clear
them out so that they are no longer readers of those books.

Let's say that my Publisher and Book classes are in one app, and that
app doesn't know anything about the readers. Is there any simple way
to find all related object fields that point to book and clear them
out, without having to know their names?

One other thought - let me know if you see any issue with this. Let's
say I just never want my reader objects to get deleted based on
related object deletions. I think I can just define

class Reader:
def delete(self):
pass

Then delete will do nothing and if I really want to delete my reader I
can call some other method that I define that then calls super(Reader,
self).delete().

The actual object that is my "reader" (the above example was used just
for simplicity) is really my UserProfile object. I would think that
this is a common problem - not wanting one's userProfiles to ever get
deleted. In my case my UserProfile contains a ForeignKey to a
particular "Chip" object, identifying it as the "current chip" (ie,
the one they want to be looking at). If that Chip gets deleted, I
don't want my UserProfile to get deleted. As I write this, it occurs
to me that another option would be to make the UserProfile contain a
manytomany field that identifies the current chip (rather than a
foreingKey). I think if I did that, I would not have this problem,
right? Is there a down side to making it a ManyToMany field rather
than a ForeignKey? It's misleading (since there can be only one
current chip pointed at by the userprofile), but it seems to solve the
problem.

Margie

On Apr 12, 12:06 am, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:
> immediately jump out:http://docs.djangoproject.com/en/dev/topics/db/queries/#topics-db-que...

Malcolm Tredinnick

unread,
Apr 12, 2009, 4:04:02 AM4/12/09
to django...@googlegroups.com
On Sun, 2009-04-12 at 00:40 -0700, Margie wrote:
[...]

> Let's say that my Publisher and Book classes are in one app, and that
> app doesn't know anything about the readers. Is there any simple way
> to find all related object fields that point to book and clear them
> out, without having to know their names

For a model such as Book, you could iterate through
Book._meta.get_all_field_names(), call Book._meta.get_field_by_name()
for each name and look at the "direct" component of the returned result
to see which are the reverse relations. Those are then the things
pointing to your model.

There are docstrings on get_field_by_name() and get_all_field_names() in
django/db/models/options.py that will help you out there.

> ?


>
> One other thought - let me know if you see any issue with this. Let's
> say I just never want my reader objects to get deleted based on
> related object deletions. I think I can just define
>
> class Reader:
> def delete(self):
> pass
>
> Then delete will do nothing and if I really want to delete my reader I
> can call some other method that I define that then calls super(Reader,
> self).delete().

That will work for a direct call. Isn't there going to be some related
object deletion cases that still won't be caught, though? Isn't this
exactly the case that you were examining in the initial post?

> The actual object that is my "reader" (the above example was used just
> for simplicity) is really my UserProfile object. I would think that
> this is a common problem

Boy do I hate that phrase! For every possible programming problem, there
is certainly a non-zero number of people who want some particular piece
of behaviour. But there's pretty much no way to measure "common" unless
you have a total userbase of, like, 17 people. At some point, everything
is both common to some group and pretty much irrelevant to the majority.

> - not wanting one's userProfiles to ever get
> deleted.

Ultimately, Python is a language for consenting adults. If you don't
want the object to be deleted, don't call delete on things involving
that object.

It's understood that delete behaviour is something that has a few
different options. Coming up with a save API for controlling those which
doesn't leak SQL-juice all over the Python level code or make things
horribly an untenably inefficient, has been something we've been
wrestling with for quite a while. So far without really having a great
solution that we're happy committing. This isn't for want of actual hard
thinking on the problem by a number of people.

For now, it's a matter of being careful and trusting your users to not
do crazy stuff.

It's not optimal, but it is survivable.

Regards,
Malcolm

Margie

unread,
Apr 12, 2009, 2:29:57 PM4/12/09
to Django users


> For a model such as Book, you could iterate through
> Book._meta.get_all_field_names(), call Book._meta.get_field_by_name()
> for each name and look at the "direct" component of the returned result
> to see which are the reverse relations. Those are then the things
> pointing to your model.
>
> There are docstrings on get_field_by_name() and get_all_field_names() in
> django/db/models/options.py that will help you out there.
>

Great, this was very helpful.

> > One other thought - let me know if you see any issue with this. Let's
> > say I just never want my reader objects to get deleted based on
> > related object deletions. I think I can just define
>
> > class Reader:
> > def delete(self):
> > pass
>
> > Then delete will do nothing and if I really want to delete my reader I
> > can call some other method that I define that then calls super(Reader,
> > self).delete().
>
> That will work for a direct call. Isn't there going to be some related
> object deletion cases that still won't be caught, though? Isn't this
> exactly the case that you were examining in the initial post?

Yes, of course you are right. I still hadn't fully grocked that delete
() never get called in this case. I get that now.


> Ultimately, Python is a language for consenting adults. If you don't
> want the object to be deleted, don't call delete on things involving
> that object.
>
Well, that seems a little heavy handed. I mean, when trying to create
db driven web app, I think it is important to allow the users to
delete the objects and not have bad side effects. That said, I
understand that django is a developer's tool and there is always a
workaround that a developer can come up with. I found one that works
fine for my particular app.

> It's understood that delete behaviour is something that has a few
> different options. Coming up with a save API for controlling those which
> doesn't leak SQL-juice all over the Python level code or make things
> horribly an untenably inefficient, has been something we've been
> wrestling with for quite a while. So far without really having a great
> solution that we're happy committing. This isn't for want of actual hard
> thinking on the problem by a number of people.

>
> For now, it's a matter of being careful and trusting your users to not
> do crazy stuff.
>
> It's not optimal, but it is survivable.


yup, agreed it is a hard problem. I appreciate that you guys no doubt
have your hands full and am absolutely fine with dealing with it as
is. Thanks for your pointers, they were very helpful and I've dug my
self out of my hole.

Margie
Reply all
Reply to author
Forward
0 new messages