ManyToMany field not saved when using Django admin
Asked Answered
P

6

16

I'm experiencing a weird problem which I hope someone in here may be able to shed some light on.

I'm overriding the save() method of a model to add some values to a ManyToMany-field after running super(). My problem is that when I'm saving in Django admin the values seems to get added to the relationship but is then empty again.

If however I do it from manage.py shell it works without problem.

I've put two print statements in there and they produce the exact same output regardless of if I'm running it via Django admin or via shell.

class Store(models.Model):
    holidays = models.ManyToManyField(StoreHoliday, blank=True)
    copy_holidays_from = models.ForeignKey('Store', blank=True, null=True)

    def save(self):
        print '====  BEFORE SAVE:', self.holidays.all()
        super(Store, self).save()
        self.copy_holidays()
        print '====  AFTER SAVE:', self.holidays.all()

    def copy_holidays(self):
        if self.pk and self.copy_holidays_from:
            self.holidays.clear()
            for h in self.copy_holidays_from.holidays.all():
                self.holidays.add( h )

This is the output of the print statements:

====  BEFORE SAVE: []
====  AFTER SAVE: [<StoreHoliday: 10 Mar 2010, Chuck Norris birthday (Closed)>]

Does anyone have any suggestions on what might be causing this?

Edit: All manual changes to the m2m relationship in save() seems to be discarded by Django when saving through the admin interface. Is this related to how it processes the form?

Persephone answered 1/6, 2011 at 11:12 Comment(2)
I don't know where's your problem, but self.holidays = self.copy_holidays_from.holidays.all() looks much nicer then clearing and iterating.Fetter
Thank you for the tip, I didn't know that was possible. You can see below what I did wrong.Persephone
P
17

So it turns out the above was not the correct way to implement it. The code belonged in StoreAdmin, by overriding model_save().

This is how I solved it:

class StoreAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if obj.copy_holidays_from:
            form.cleaned_data['holidays'] = obj.copy_holidays_from.holidays.all()

        super(StoreAdmin, self).save_model(request, obj, form, change)
Persephone answered 1/6, 2011 at 11:33 Comment(0)
L
3

I probably ran into this same behaviour just today and yes, you are correct in assuming it's related to how django handles the data.

The django admin makes the changes to a ManyToMany field separately from changing the actual object. (Remember that the m2m is saved in a different database table).

In my case if I didn't select anything in the ManyToMany field in the admin site, this would translate into a clear()-operation on the ManyToMany relation. Everything you do in the save()-method is immediately removed by this clear. Same thing with stuff I did in the post_save signal handler.

The solution (for me) was to separate the ManyToMany-field into an inline so it doesn't automatically get saved as empty when modifying the object.

Lethalethal answered 16/9, 2011 at 13:46 Comment(1)
Hi there Jyrsa, I'm having a similar issue with Django Admin. We have an m2m field that is sometimes being cleared when the rest of the page is saved. I didn't know Django saves it separately. Could you perhaps link me to some more information about how m2m is handled? Can you be more specific as to under what circumstances the save method's actions are cleared? Thank you!Alduino
M
1

In django 2,1,4 my solution was to use save_related()

def save_related(self, request, form, formsets, change):
    super().save_related(request, form, formsets, change)
    form.instance.permissions.add(request.user)
Milt answered 24/1, 2019 at 10:57 Comment(0)
M
1

In my case, it ended up being because all of my primary key sequences in my database were out of sync (..._id_seq tables).

After fixing that issue (see this StackOverflow solution for that problem), my M2M models starting saving again.

Moia answered 29/7, 2023 at 15:17 Comment(0)
L
0

For me the problem that the admin was only saving the last selected instance of the many fields (last 'holiday' selected). So I had to override the save_model method such as this:

@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):
        form.cleaned_data['holidays'] = StoreHoliday.objects.filter(pk__in=dict(request.POST).get('holidays'))
        super(StoreAdmin, self).save_model(request, obj, form, change)

I spent a lot of time on it and other solutions were not working, so I hope it will help.

Laggard answered 28/5, 2018 at 16:55 Comment(0)
N
0

One of the solutions to update m2m, along with updating one of your models.

Django 1.11 and higher

The behavior which you can observe during updating, when changes which you made with m2m records were not saved, even after you made them in a save method one of your models or in a signal, happens only because m2m form rewrites all records after the main object is updated.

This is why, step by step:

  1. The main object is updated.

  2. Your code(in a save method or in a signal) made changes (you can look at them, just put a breakpoint in ModelAdmin):

 def save_related(self, request, form, formsets, change):
     breakpoint()
     form.save_m2m()
     for formset in formsets:
         self.save_formset(request, form, formset, change=change)
  1. form.save_m2m() takes all m2m values which were placed on a page(roughly speaking) and replace all m2m records via a related manager. That's why you can't see your changes at the end of a transaction.

There is a solution: make your changes with m2m via transaction.on_commit. transaction.on_commit will make your changes after form.save_m2m() when the transaction is committed.

Unfortunately, the downside of this solution - your changes with m2m will be executed in a separate transaction.

Nauseous answered 14/2, 2019 at 22:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.