tags:

views:

422

answers:

2

Here'a an example:

If I have these classses

class Author(models.Model):
    name = models.CharField(max_length=45)

class Book(models.Model):
    name = models.CharField(max_length=45)
    authors = models.ManyToManyField(Author)

In the database I have one Author with the name "George" and another one with the name "Georfe". The last one is a mistake. So what I want is in every Book that have "Georfe" as one of his Author replace it by the Author "George".

In SQL is really easy to do. If id of "George" = 3 and id of "Georfe" = 7 and the relation table name is "author_book":

UPDATE author_book SET id=3 WHERE id=7;

Is it possible to do that with the Django ORM?

I found a way to do it: I loop trough all the related Books of the mistyped author and do:

book.authors.add(Autor.objects.get(id=3))
book.authors.remove(Author.objects.get(id=7))

But I don't find this solution really elegant and efficient. Is there a solution without the loop?

A: 

With the auto-generated through table, you have to do a two-step insert and delete.

george = Author.objects.get(name='George')
georfe = Author.objects.get(name='Georfe')

book.authors.add(george)
book.authors.remove(georfe)
assert george in book.authors

If you have an explicitly defined through table (authors = models.ManyToManyField(Author, through=BookAuthors) then you can change the relationship explicitly on BookAuthor.

book_author = BookAuthor.objects.get(author=georfe, book=great_american_novel)
book_author.author = george
book_author.save()
assert george in book.authors

If you don't have an explictly defined through table, you don't lose anything by adding and deleting explicitly, except a bit of efficiency, but this is a one-time clean up task, so simplicity should trump efficiency. If you're concerned about it, as the commenter said, drop to SQL. That's what it's there for.

jcdyer
Ha, we write mostly the same thing at the same time! But I didn't know about the "trough" argument of ManyToManyField. I will look into that. But the way Ian use the relationship table with the update seem to be more effcient.
Etienne
A: 

Can you do something like this?

george_author = Author.objects.get(name="George")
for book in Book.objects.filter(authors__name="Georfe"):
    book.authors.add(george_author.id)
    book.authors.filter(name="Georfe").delete()

I suspect that this would be easier if you had an explicit table joining the two models (with the "through" keyword arg) -- in that case, you would have access to the relationship table directly, and could just do a .update(id=george_author.id) on it.

Ian Clelland
See my comment to jcd, your solution are mostly the same (except for the delete, but I don't want to delete the mistyped author necessarily so remove is better in my case).I think your solution with the update is the proper way to do it. I accept this answer. Unfortunately my case is more complex because I extend ModelAdmin and everything have to be generic and I don't think I could use the relationship table directly (because I could not be sure if the "trough" arguemnt is set). I'm right?
Etienne
On an instance, you can see the 'through' argument: book.authors.through = 'ModelName' -- I'm not certain if you can get to it through the class itself, though.
Ian Clelland
I check the "through" argument on an instance and if it's not set in the ManyToManyField, through is None. So for the moment I will keep the solution of the loop with the add/remove (or delete). Maybe it'S not elegent and efficient but it works well enough for our needs.Thanks Ian, jcd
Etienne