[Fixed]-Django signals – kwargs['update_fields'] is always None on model update via django admin

19👍

The set of fields should be passed to Model.save() to make them available in update_fields.

Like this

model.save(update_fields=['tax_rate'])

If you are creating something from django admin and getting always None it means that update_fields has not been passed to model’s save method. And because of that it will always be None.

If you check ModelAdmin class and save_model method you’ll see that call happens without update_fields keyword argument.

enter image description here

It will work if you write your own save_model.

The code below will solve your problem:

class ProductAdmin(admin.ModelAdmin):
    ...
    def save_model(self, request, obj, form, change):
        update_fields = []

        # True if something changed in model
        # Note that change is False at the very first time
        if change: 
            if form.initial['tax_rate'] != form.cleaned_data['tax_rate']:
                update_fields.append('tax_rate')

        obj.save(update_fields=update_fields)

Now you’ll be able to test memberships in update_model.

7👍

To add to Davit Tovmasyan’s post. I made a more universal version that covers any field change using a for loop:

class ProductAdmin(admin.ModelAdmin): 
    ...
    def save_model(self, request, obj, form, change):
        update_fields = []
        for key, value in form.cleaned_data.items():
            # True if something changed in model
            if value != form.initial[key]:
                update_fields.append(key)

        obj.save(update_fields=update_fields)

EDIT: WARNING This isnt actually a full solution. Doesnt seem to work for object creation, only changes. I will try to figure out the full solution soon.

3👍

I wanted to add an alternative that relies on the pre_save signal to get the previous version of the instance you’re evaluating (from this SO answer):

@receiver(pre_save, sender=Product)
def pre_update_model(sender, **kwargs):
    
    # check if the updated fields exist and if you're not creating a new object
    if not kwargs['update_fields'] and kwargs['instance'].id:
        # Save it so it can be used in post_save
        kwargs['instance'].old = User.objects.get(id=kwargs['instance'].id)


@receiver(post_save, sender=Product)
def update_model(sender, **kwargs):
    instance = kwargs['instance']

    # Add updated_fields, from old instance, so the method logic remains unchanged
    if not kwargs['update_fields'] and hasattr(instance, 'old'):
        kwargs['update_fields'] = []
        if (kwargs['update_fields'].instance.tax_rate != 
                kwargs['update_fields'].instance.old.tax_rate):
            kwargs['update_fields'].append('tax_rate')

    if 'tax_rate' in kwargs['update_fields']:

comparing to the accepted answer

Disadvantages

  • Extra query on every save that doesn’t have update_fields (if you’re not opening Django Admin to the world, this shouldn’t be problematic)

Advantages

  • Don’t need to override any method or class
  • You only need to implement the logic for the fields you want to evaluate, and they are in the same method, so no excuse for mistakes πŸ˜‰

If you’re doing this for many classes, you should probably look at other solutions (but the accepted answer is also not perfect for that!)

1👍

You can do this.

    def save_model(self, request, obj, form, change):
        if change:
            obj.save(update_fields=form.changed_data)
        else:
            super().save_model(request, obj, form, change)

0👍

Just for anyone who is coming in!

I believe this is the complete solution to this case, note that if you have any ManyToMany fields in your model then you should skip adding them to update fields

def save_model(self, request, obj, form, change):
        """
        Given a model instance save it to the database.
        """
        update_fields = set()
        if change:
            for key, value in form.cleaned_data.items():
                # assuming that you have ManyToMany fields that are called groups and user_permissions
                # we want to avoid adding them to update_fields 
                if key in ['user_permissions', 'groups']:
                    continue
                if value != form.initial[key]:
                    update_fields.add(key)

        obj.save(update_fields=update_fields)

Leave a comment