update M2M field post_save or on save

hey, I try to generate a default “Island” if there are none on the “Screen” model

class Screen(models.Model):
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name=_('UUID'), primary_key=True)
    name = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Screen Name'))
    
    islands = models.ManyToManyField('Island', related_name='screens', verbose_name=_('Islands'), blank=True)

    def save(self, *args, **kwargs):
        if self.pk:
            print('save: ',self.islands.all())
        super().save(*args, **kwargs)

@receiver(post_save, sender=Screen)
def create_islands(instance, **kwargs):
    if not instance.islands.exists():
        default_island = Island.objects.create(name='Default Island')
        instance.islands.add(default_island)
        instance.save() # not working without this line as well

logs when creating a Screen:

save:  <QuerySet []>
save:  <QuerySet [<Island: Default Island ()>]>
"POST /admin/core/screen/add/ HTTP/1.1" 302 0

The Islands are created but not added to the “Screen” model, why?
I also try to move it from post_save to save without success
Thanks for the help

It’s a timing / sequence of events issue.

The Django admin does a save_m2m after the model is created. Since it has no record of any islands assigned to that screen, the save_m2m is going to delete any entries you add during the save.

Instead of trying to do this in the model, do this in the save_model method of your ModelAdmin class for Screen.

(Side note: Do yourself a favor and do not use signals when they’re not necessary. You gain nothing - and potentially create unintended problems for yourself by using a signal for create_islands instead of placing that code directly in the save method if you need to do this in cases where you’re saving this model outside the admin.)

Thank you!
I will try that, I am just testing it from the Django admin but it will happen from an API call in the end, so moving it to the Admin model I don’t think it will work, but possibly it will work like it is now from the API (just change the post_save to save in the Screen model to remove signals)
Is there a way to make it work from the Django admin and from outside save as well?

Still not working
Now it crashes when adding a new Screen (can’t access M2M before pk init)
And on edit: Island is created but still not added

class ScreenAdmin(admin.ModelAdmin):
    filter_horizontal = ('islands',)
    def save_model(self, request, obj, form, change):
        
        if not obj.islands.exists():
            default_island = Island.objects.create(name='Default Island')
            obj.islands.add(default_island)
        super().save_model(request, obj, form, change)
    pass
admin.site.register(Screen, ScreenAdmin)

You do need to call super on save_model before adding the island.

And to answer your previous question, no - I don’t know a way to make this work in either situation because the Model is not in control of the sequence of events that occurs in views.

I also tried it before

class ScreenAdmin(admin.ModelAdmin):
    filter_horizontal = ('islands',)
    def save_model(self, request, obj, form, change):
        super().save_model(request, obj, form, change)
        if not obj.islands.exists():
            default_island = Island.objects.create(name='Default Island')
            obj.islands.add(default_island)
    pass
admin.site.register(Screen, ScreenAdmin)

Same result, or maybe I miss-understood you

No, that’s what I’m thinking should work.

By “same result”, are you saying that you’re getting the pk error or that it’s just not saving the island?

(Side note: Since this is a m2m relationship, are you wanting to create a different Island object for every Screen that needs to be assigned to the default?)

Ahh - I see my mistake here - it’s still a sequencing issue.

It’s the save_related method you want to override, not save_model. Call super for it, and then do your island test.

Thank you!
Now it is working

    def save_related(self, request, form, formsets, change):
        super().save_related(request, form, formsets, change)
        # if no islands are selected, then create a default island
        if not form.instance.islands.all():
            island = Island.objects.create(name=form.instance.name + ' Default Island')
            form.instance.islands.add(island)
            form.instance.save()

About “Side note”: yes, this is what I want, in reality, I will create different Islands based on some params, but for testing it is perfect

Note:

You do not need to re-save the form.instance here. Adding a reference in a many-to-many relationship does not change either of the related models. It creates an entry in the “join table” that connects those two models.