Updating the a user profile upon user save

1.4k views Asked by At

I'm following the 'User profile' approach to extend my User model, like so:

# models.py
class UserProfile(models.Model):
    user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE, primary_key=True)
    my_field = models.CharField(max_length=100)

# signals.py
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

With this approach, I have to explicitly call user.profile.save(), which to me feels clunky, as I want the profile to give the illusion it is part of the User object:

# views.py
def some_func(request):
    user = User.objects.create_user('dummy', '[email protected]', '12345678')
    user.profile.my_field = 'hello'
    user.save()          # This does not persist the profile object...
    user.profile.save()  # ...this does

To remedy this, I've changed create_user_profile() to the following, which works:

# signals.py
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
   profile = UserProfile.objects.get_or_create(user=instance)
   profile.save()

Numerous examples I've encountered do not use this approach. Are there any caveats to using this approach?

3

There are 3 answers

3
e4c5 On BEST ANSWER

Yes, there are a few. In the following situations the post_save signal would not be fired.

1 If the save method does not successfully save the object (such as when an IntegrityError occurs)

2 When you call MyModel.objects.update()

3 When you override the save method and forget to call the superclass method.

4 When your signal receiver hasn't been successfully registered.

In these situations your profile wouldn't be saved.

2
SmoQ On

The better way is to specify a custom user model.

from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    custom_field = models.ForeignKey(
        'contracts.Contract'
    )
    ...

    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

You have to update the settings.py defining the AUTH_USER_MODEL property:

AUTH_USER_MODEL = 'app_name.User'
0
Roast Biter On

You can use a custom User model like this :

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.user.username} Profile'

and then the signals.py file :

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
    instance.profile.save()

what the signals.py file does here is to inform you when a new Profile object is created of the User type which you can further use to create forms that update/create the user's profile.

And to make all this work, you need to import the signals.py file in the apps.py file of your app. For example, this is what your apps.py file would look like :

from django.apps import AppConfig


class UsersConfig(AppConfig):
    name = 'users'

    def ready(self):
        import users.signals