'how to update an object when several other related objects get created

I have to add a progress_level field to my User model that shows where does this user stand at the moment so I added a field to the user model like this:

progress_level = models.CharField(max_length=25, null=True, choices=USER_PROGRESS_LEVELS)

I want this field to automatically update itself whenever an action gets done by the user. for instance, if the user completes their contact info and document info and submit their forms, the progress level should change to [1][0]. I really dont know how to do this but I created a signal like this:

@receiver(pre_save, sender=User, dispatch_uid='progress_change')
def progress(sender, instance, **kwargs):
    user = instance
    if DocumentInfo.objects.filter(user=user).exists() and ContactInfo.objects.filter(user=user).exists():
        user.progress_level = USER_PROGRESS_LEVELS[1][0]

it works fine but it activates only if I save the User model. how can I prevent that? how can I activate this signal whenever that statement is true? if is there a better way to do this please help me. I really dont know if signals are the right way to do this. these are my models:

class User(AbstractUser):
    id = models.AutoField(primary_key=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    isPreRegistered = models.BooleanField(default=False)
    username = models.CharField(unique=True, max_length=13)
    first_name = models.CharField(max_length=32, null=True, default=None)
    last_name = models.CharField(max_length=64, null=True, default=None)
    gender = models.CharField(max_length=10, null=True, choices=GENDER)
    progress_level = models.CharField(max_length=25, null=True, choices=USER_PROGRESS_LEVELS)
    isDetailViewed = models.BooleanField(default=False)

class DocumentInfo(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    certificate = models.FileField(upload_to="documents")
    id_card = models.FileField(upload_to="documents")
    service_certificate = models.FileField(upload_to="documents")
    educational_certificate = models.FileField(upload_to="documents")

############EDIT############## I added this save method to my user model. it works but I want this to update user model without saving it. what I mean is I want it to check the document and contact info models and if they exist, update that progress_level. I have to save the user model manually from admin page for this to happen. how can I fix it

def save(self, *args, **kwargs):
    if self.documentinfo and self.contactinfo:
        self.progress_level = USER_PROGRESS_LEVELS[4][0]
    return super(User, self).save(*args, **kwargs)


Solution 1:[1]

How about updating the model save method?

models.py

class User(...):

    ...
    document_info = models.OneToOneField(DocumentInfo, ...)
    contact_info = models.OneToOneField(ContactInfo, ...)

    def save(self, *args, **kwargs):

        if self.document_info and self.contact_info:
            self.progress_level = USER_PROGRESS_LEVELS[1][0]

        return super().save(*args, **kwargs)

Solution 2:[2]

You want change the progress_level value where you add some document or complete contact ... how about you do this in save or update method on document model or contact model ... contact or document model that related with specific user have save or update method can effect "progress_level" value on user model trough user instance. you can fetch user instance from document model on save method (cause you have foreign key) and check the process has been don ... increase "progress_level" by user instance. if you don't know how in code let me know to explain that with sample code

Solution 3:[3]

I assume that your models shaped like this:

class User(AbstractUser):
    progress_level = models.IntegerField(null=True, default=0) 
    // some other field 

class DocumentInfo(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    // some other field

You set serializer for document like this:

class DocumentInfoSerializer(serializers.ModelSerializer):

    user = UserSerializer(many=False, write_only=True)

    class Meta:
        model = DocumentInfo
        fields = ('id', 'user', /* some other fields */)
        read_only_fields = ('id',)

Now in DocumentInfo model you should code save method like this:

class DocumentInfoManager(models.Manager):
    
    def create(self, user, **kwargs):
        user_obj = User.objects.get(id=user.id)            
        user_obj.progress_level = 1 // set progress_level here 
        user.save()                 // user instance update here

        document_obj = DocumentInfo(user=user, **kwargs)
        document_obj.save()

        return document_obj 

class DocumentInfo(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    // some other field
    objects = DocumentInfoManager() // this line is necessary

You define id in User model. Note that in model is not necessary define id (primary_key) field with increment argument ... django create id for each model automatically

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mahyar Majidi
Solution 3