Search code examples
djangodjango-modelsdjango-signals

Update the instance after save, using signals by conditionally refference back to the instance in some cases


I have the following abstract class:

class UserStamp(models.Model):
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True,
                                   related_name='%(app_label)s_%(class)s_created_by', on_delete=models.CASCADE)
    updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
                                   related_name='%(app_label)s_%(class)s_updated_by', on_delete=models.CASCADE)

    class Meta:
        abstract = True

I have a custom User that inherits from User.

class User(AbstractBaseUser,PermissionsMixin, UserStamp):
    account = models.ForeignKey(Account, blank=True, null=True, related_name='owner',on_delete=models.CASCADE)

The User can create/update himself or by other user.

When the user create/update himself I don't have anything for created_by, update_by.

The user can be created using Django Admin or outside Django Admin;

In Django Admin the user can be created by staff, outside Django is self created;

Also there is superuser that is created in terminal;

Regarding the update the user in both Django Admin and outside can be self updated or by another user.

I thought on using post_save or a custom signal. The issue is that request.user is not available in the model, but in View, and controlling the View in Admin and also in terminal(superuser) is a bottleneck.

Maybe trying to do a query after save passing the instance, but I don't exactly know how to combine all of them signal/query, check superuser.


Solution

  • Create your own custom signals.

    signals.py

    from django.dispatch import Signal 
    # you can pass any number of arguments as per your requirement.
    manage_user = Signal(providing_args=["user", "is_updated"])
    
    
    def user_handler(sender, **kwargs):
        # `user` who created/updated object i.e `request.user`
        user = kwargs['user']
        # `is_updated` will be `False` if user object created.
        is_updated = kwargs['is_updated']
        # `sender` is the user object which is created/updated.
        ...
        # do your stuff
    
    manage_user.connect(user_handler)
    

    models.py

    Override save() method of your custom user class.

    from .signals import manage_user
    
    class User(...):
        ...
        # call this save method like obj.save(created_by=request.user)
        def save(self, created_by, *args, **kwargs):
            super().save(*args, **kwargs)
            # is_updated will be True if user object is updated 
            manage_user.send(sender=self, user=created_by, is_updated=True if self.id else False)
    

    send manage_user signal when user model changed, just like post_save signal, but now you have control over parameters.

    UPDATE

    If you are using django admin to create user you can overide save_model, you have request object there.

    from django.contrib import admin
    
    @admin.register(User)
    class UserAdmin(admin.ModelAdmin):
         def save_model(self, request, obj, form, change):
             super(UserAdmin, self).save_model(request, obj, form, change)
             manage_user.send(sender=self, user=request.user, is_updated=True if self.id else False)