2

I have a CustomUser model and am using Django Rest Framework. I have several needs before/after saving a user object. For example:

  1. Adding the user's email, first/last name to a mailchimp list using mail chimp's API.
  2. Hashing a user's password before saving.

I successfully do this in the UserSerializer class thus:

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = CustomUser
        fields = '__all__'

    def create(self, validated_data):
        groups = validated_data.pop('groups', None)
        password = validated_data.pop('password', None)
        user_permissions = validated_data.pop('user_permissions')
        u = CustomUser.objects.create(
            password=make_password(password), **validated_data)
        u.groups.set(groups)
        u.user_permissions.set(user_permissions)
        return u

However, let's say I want to also create a user in the admin tool and not repeat this logic? Doesn't it make more sense to add this logic by overriding the CustomUser's save method, or is that problematic? What's the best solution for this use-case?

2 Answers 2

2

I'd probably use post_save signal for this.

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def do_something_after_create_user(sender, instance=None, created=False, **kwargs):
    if created:
        # Do your thing 

This signal will be triggered after CustomUser object is saved and do your logic if object is created. If you want to do something before saving a user, you'd use pre_save signal.

Sign up to request clarification or add additional context in comments.

3 Comments

This is the best and simplest solution, using signals. So what is the use-case for overriding methods in the ViewSet or Serializer classes in DRF? Is this for only modifying what JSON is returned, not for performing side-effects when a model instance is saved etc.?
@Aaron, I override model methods only for rather simple things, like ensuring the consistency of values, for example storing email address in lowercase, creating custom object id based on provided values, creating slug, simple calculations etc. Anything more complex is either in views or serializers. And in serializers I only override methods when I need to modify or verify/validate the data that is returned or received before it's saved. Saying that, I hardly ever override Django methods in practice.
That makes sense. I was thinking that I don't think you have access to the return value in signals, but those are a good place to do things like make API calls etc. when the operation has nothing to do with the API's request or return value.
1

Put the logic in the save() method doesn't seem like a good idea to me:

  1. You have to run this logic only on first save (create)
  2. If you use CustomerUser.objects.create(...) the logic won't be processed.

If you want to run this logic on every create, I'd rather override CustomUser.objects.create using a custom manager.

class CustomUserManager(models.Manager):

    def create(self, **kwargs):
        groups = kwargs.pop('groups', None)
        password = kwargs.pop('password', None)
        user_permissions = kwargs.pop('user_permissions', None)
        u = super().create(password=make_password(password), **kwargs)
        u.groups.set(groups)
        u.user_permissions.set(user_permissions)
        return u

 
class CustomerUser(models.Model):

    objects = CustomUserManager()

Thus, you can use it in your serializer:

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = CustomUser
        fields = '__all__'

    def create(self, validated_data):
        # To be tested but I think you don't even need to redefine this method.
        return CustomUser.objects.create(**validated_data)

And anywhere else you want. Beware however that creating a user using CustomUser(...).save() will skip the logic in CustomUser.objects.create.

If you have different creation processes, you can also define multiple create_<...> methods in your manager:

class CustomUserManager(models.Manager):

    # def create(self, **kwargs):
    #     Default create() inherited from models.Manager,
    #     no need to redefine it.

    def create_with_groups_and_permissions(self, *, groups, user_permissions, password, **kwargs):
        u = self.create(password=make_password(password), **kwargs)
        u.groups.set(groups)
        u.user_permissions.set(user_permissions)
        return u

    def create_from_api(self, api_data):
        model_data = convert_api_data_to_model_data(api_data)
        return self.create(**model_data)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.