Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework create user and user profile

I am trying to create the view that will create new user using my API. I am using a custom model for my User and also created a model called Profile to manage data that are not authentication related.

I am new to the Django world and it can be quite difficult.

Here is my models.py

class UserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email:
            raise ValueError('User must have an email address')

        user = self.model(
            email = self.normalize_email(email),
        )
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password):
        user = self.create_user(email, password=password)
        user.is_admin = True
        user.save()
        return user


class User(AbstractBaseUser):
    objects = UserManager()
    email = models.EmailField(unique=True, db_index=True)
    created = models.DateTimeField('created', auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    is_active = models.BooleanField('active', default=True)
    is_admin = models.BooleanField('admin', default=False)

    USERNAME_FIELD = 'email'

    ordering = ('created',)

    def is_staff(self):
        return self.is_admin

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

    def get_short_name(self):
        return self.email

    def get_full_name(self):
        return self.email

    def __unicode__(self):
        return self.email


class Profile(models.Model):
    GENDER = (
        ('M', 'Homme'),
        ('F', 'Femme'),
    )

    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    first_name = models.CharField(max_length=120, blank=False)
    last_name = models.CharField(max_length=120, blank=False)
    gender = models.CharField(max_length=1, choices=GENDER)
    zip_code = models.CharField(max_length=5, validators=[MinLengthValidator(5)], blank=False)

    def __unicode__(self):
        return u'Profile of user: {0}'.format(self.user.email)


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


def delete_user(sender, instance=None, **kwargs):
    try:
        instance.user
    except User.DoesNotExist:
        pass
    else:
        instance.user.delete()
post_delete.connect(delete_user, sender=Profile)

Here is my serializers.py

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ('first_name', 'last_name', 'gender', 'zip_code',)


class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer(required=True)
    class Meta:
        model = User
        fields = ('url', 'email', 'profile', 'created',)

And here is the views.py

class UserList(generics.ListCreateAPIView):
    permission_classes = (IsAuthenticatedOrWriteOnly,)
    serializer_class = UserSerializer

    def get_queryset(self):
        if self.request.user.is_staff:
            return User.objects.all()
        else:
            return self.request.user

    def post(self, request, format=None):
        serializer = ProfileSerializer(data=request.data)
        print serializer.__dict__
        if serializer.is_valid():
            print "valid"
        return Response("placeholder", status=status.HTTP_201_CREATED)

My goal is to be able to create the User and the Profile at the same time while being able to valid everything, how could I achieve this ?

like image 525
soueuls Avatar asked Nov 11 '15 21:11

soueuls


2 Answers

Indeed, Django Rest Framework can't handle this job with nested relationships then you have to implement these methods yourself. I will give you some example of what your code should look like.

Your view :

class UserList(generics.ListCreateAPIView):
    permission_classes = (IsAuthenticatedOrWriteOnly,)
    serializer_class = UserSerializer

    def post(self, request, format=None):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Now, the save method of your serializer will call a create method when you want to create an object and update method when you want to update an object. So let's implement the create method of your UserSerializer which will create the profile and the user. Here is a simple example of what your UserSerializer should look like :

class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer(required=True)
    class Meta:
        model = User
        fields = ('url', 'email', 'profile', 'created',)

    def create(self, validated_data):

        # create user 
        user = User.objects.create(
            url = validated_data['url'],
            email = validated_data['email'],
            # etc ...
        )

        profile_data = validated_data.pop('profile')
        # create profile
        profile = Profile.objects.create(
            user = user
            first_name = profile_data['first_name'],
            last_name = profile_data['last_name'],
            # etc...
        )

        return user

As I said, this is an example, you have to complete it to do what you want to do but now, you know how to do it :) To define the behavior during an update, implement an update method.

like image 163
Louis Barranqueiro Avatar answered Oct 14 '22 06:10

Louis Barranqueiro


This might be late but why not bypass drf entirely and relegate profile creation trigger to models using signals like so:

models.py

class User(AbstractBaseUser):
      pass
class UserProfile(models.Model):
      user =  models.OneToOneField(User, on_delete=models.CASCADE, related_name='customer_profile')
      country = models.CharField(blank=True, max_length=250)

signals.py

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

this basically triggers a corresponding profile row creation once a user has been created via any means; admin, drf etc.

then you can use serializers to update the data. hope this helps anyone else stumbling here

like image 30
Pamilerin Avatar answered Oct 14 '22 07:10

Pamilerin