7

In django, creating a User has a different and unique flow from the usual Model instance creation. You need to call create_user() which is a method of BaseUserManager.

Since django REST framework's flow is to do restore_object() and then save_object(), it's not possible to simply create Users using a ModelSerializer in a generic create API endpoint, without hacking you way through.

What would be a clean way to solve this? or at least get it working using django's built-in piping?

Edit:

Important to note that what's specifically not working is that once you try to authenticate the created user instance using django.contrib.auth.authenticate it fails if the instance was simply created using User.objects.create() and not .create_user().

ydaniv
  • 1,289
  • 11
  • 18
  • Weird... I'm using just `User.objects.create()` and then authenticating the user afterwards. Now I need to figure out why it works. – yprez Nov 06 '13 at 08:10
  • 1
    Are you also setting the user's password on creation? If not, there's no reason not to use `create()`. Or if you are setting the password you can call `user.set_password()` separately in restore_object() after the save (the problem here is that you'll be saving the user twice). – yprez Nov 07 '13 at 10:19
  • Yes, the password here is the root of all evil. Probably it's being set in its raw form to DB and that's what failing authentication, probably what @alexarsh is suggesting. What I'll probably go for is moving all the logic form `create_user()` to the `save()` method. – ydaniv Nov 07 '13 at 11:29
  • @YuriPrezument, yes, the password setting was the root of the problem. Now I am setting it using `user.set_password()` in `restore_object()` but that happens before `save()` so you we're sort of *on it* (: – ydaniv Nov 24 '13 at 08:37
  • http://stackoverflow.com/questions/30085996/djangorestframework-registering-a-user-difference-between-userserializer-save/30087194#30087194 – Yeo May 06 '15 at 21:02

4 Answers4

2

Eventually I've overridden the serializer's restore_object method and made sure that the password being sent is then processes using instance.set_password(password), like so:

def restore_object(self, attrs, instance=None):
    if not instance:
        instance = super(RegisterationSerializer, self).restore_object(attrs, instance)
    instance.set_password(attrs.get('password'))
    return instance

Thanks everyone for help!

ydaniv
  • 1,289
  • 11
  • 18
  • 1
    I can confirm that this is the right answer when using `Django==1.6.5` and `djangorestframework==2.3.14`. If using `djangorestframework > 3.0` it is likely a different solution involving `post_save()` or `create()` on the ModelSerializer. – e.thompsy Feb 06 '15 at 14:26
1

Another way to fix this is to overwrite pre_save(self, obj) method in your extension of viewsets.GenericViewSet like so:

def pre_save(self, obj):
    """ We have to encode the password in the user object that will be
    saved before saving it.
    """
    viewsets.GenericViewSet.pre_save(self, obj)
    # Password is raw right now, so set it properly (encoded password will
    # overwrite the raw one then).
    obj.user.set_password(obj.user.password)

Edit:

Note that the obj in the code above contains the instance of User class. If you use Django's user model class directly, replace obj.user with obj in the code (the last line in 2 places).

0

I'm working with DRF. And here is how I create users:

I have a Serializer with overrided save method:

def save(self, **kwargs ):
    try:
        user = create_new_user(self.init_data)
    except UserDataValidationError as e:
        raise FormValidationFailed(e.form)
    self.object = user.user_profile
    return self.object

create_new_user is just my function for user creation and in the view, I just have:

def post(self, request, *args, **kwargs):
    return self.create(request, *args, **kwargs)
alexarsh
  • 5,123
  • 12
  • 42
  • 51
  • It's not a problem to use a simple API endpoint which calls user.create(**...). The problem is that when trying to authenticate (django's `authenticate()` function) the same user you've just created it simply fails and returns `None`. Probably cause it's setting the raw password on the instance and not calling `user.set_password(password)`? – ydaniv Nov 05 '13 at 13:18
  • It looks like something you need to handle in your create_new_user function and is not related to DRF. – alexarsh Nov 05 '13 at 13:34
  • 1
    Can you look at your DB and check if your user is created? If yes, which fields does it have? Did you set a password to the user? – alexarsh Nov 06 '13 at 10:05
0

It seems like you should be overriding restore_object() in your serializer, not save(). This will allow you to create your object correctly.

However, it looks like you are trying to abuse the framework -- you are trying to make a single create() create two objects (the user and the profile). I am no DRF expert, but I suspect this may cause some problems.

You would probably do better by using a custom user model (which would also include the profile in the same object).

Shai Berger
  • 2,963
  • 1
  • 20
  • 14
  • No, I'm not creating a profile, I'm overriding AbstractBaseUser with my own model, so just creating a single instance. And also, instantiating is not the problem, the problem is that the save instance created by `create()`, and not `create_user()` can't be authenticated with same credentials later on. – ydaniv Nov 06 '13 at 15:37