kharandziuk
kharandziuk

Reputation: 12890

Django and DRF: serializer for a user model

I am wrote a serializer for the User model in Django with DRF:

the model:

from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
from django.db import models
from django.utils.translation import ugettext


class BaseModel(models.Model):
    # all models should be inheritted from this model
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class User(AbstractBaseUser, BaseModel):
    username = models.CharField(
        ugettext('Username'), max_length=255,
        db_index=True, unique=True
    )
    email = models.EmailField(
        ugettext('Email'), max_length=255, db_index=True,
        blank=True, null=True, unique=True
    )

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ('email', 'password',)

    class Meta:
        app_label = 'users'

the serializer:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['email', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = super().create(validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user

    def update(self, user, validated_data):
        user = super().update(user, validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user

It works. But I probably do two calls instead of one on every create/update and the code looks a little bit weird(not DRY). Is there an idiomatic way to do that?

$python -V
Python 3.7.3

Django==2.2.3
djangorestframework==3.10.1

Upvotes: 0

Views: 503

Answers (2)

JPG
JPG

Reputation: 88459

I hope this will solve the issue,

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['email', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        return models.User.objects.create_user(**validated_data)

    def update(self, user, validated_data):
        password = validated_data.pop('password', None)
        if password is not None:
            user.set_password(password)
        for field, value in validated_data.items():
            setattr(user, field, value)
        user.save()
        return user

The create_user() method uses the set_password() method to set the hashable password.

Upvotes: 1

cagrias
cagrias

Reputation: 1847

You can create your own user manager by overriding BaseUserManager and use set_password() method there. There is a full example in django's documentation. So your models.py will be:

# models.py
from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)


class MyUserManager(BaseUserManager):
    def create_user(self, email, username, password=None):

        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
            username=username,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user


class BaseModel(models.Model):
    # all models should be inheritted from this model
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class User(AbstractBaseUser, BaseModel):
    username = models.CharField(
        ugettext('Username'), max_length=255,
        db_index=True, unique=True
    )
    email = models.EmailField(
        ugettext('Email'), max_length=255, db_index=True,
        blank=True, null=True, unique=True
    )

    # don't forget to set your custom manager
    objects = MyUserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ('email', 'password',)

    class Meta:
        app_label = 'users'

Then, you can directly call create_user() in your serializer's create() method. You can also add a custom update method in your custom manager.

# serializers.py
class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.User
        fields = ['email', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = models.User.objects.create_user(
            username=validated_data['username'],
            email=validated_data['email'],
            password=validated_data['password']
        )
        return user

Upvotes: 1

Related Questions