ogcPYTHON
ogcPYTHON

Reputation: 78

How to Create a User in nested serializer using generic ListCreateAPIView?

I am working on genericAPIViews in DRF. I am using a built in user model with UserProfile model having one to one relation with it. But I am unable to create user due to nested serializer. My question is that how I can create my built in User model and Profile User model at the same time as UserProfile model is nested in User model.Here is my code:

Models.py

USER_CHOICE = (
    ('SS', 'SS'),
    ('SP', 'SP')
)
LOGIN_TYPE = (
    ('Local', 'Local'),
    ('Facebook', 'Facebook'),
    ('Google', 'Google')
)


class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    cell_phone = models.CharField(max_length=15, blank=True, default="", null=True)
    country = models.CharField(max_length=50, blank=True, default="", null=True)
    state = models.CharField(max_length=50, blank=True, default="", null=True)
    profile_image = models.FileField(upload_to='user_images/', default='', blank=True)
    postal_code = models.CharField(max_length=50, blank=True, default="", null=True)
    registration_id = models.CharField(max_length=200, null=True, blank=True, default=None)
    active = models.BooleanField(default=True)
    # roles = models.ForeignKey(Role, null=True, on_delete=models.CASCADE, related_name='role', blank=True)
    user_type = models.CharField(max_length=50, choices=USER_CHOICE, null=True, blank=True)
    login_type = models.CharField(max_length=40, choices=LOGIN_TYPE, default='local')
    reset_pass = models.BooleanField(default=False)
    confirmed_email = models.BooleanField(default=False)
    remember_me = models.BooleanField(default=False)
    reset_code = models.CharField(max_length=200, null=True, blank=True, default="")
    reset_code_time = models.DateTimeField(auto_now_add=True, blank=True)
    longitude = models.DecimalField(max_digits=80, decimal_places=10, default=0.00)
    latitude = models.DecimalField(max_digits=80, decimal_places=10, default=0.00)
    r_code = models.CharField(max_length=15, null=True, blank=True)
    refer_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="user_refer")
    referred = models.ManyToManyField(User, related_name="user_referred", null=True, blank=True)
    otp = models.CharField(max_length=6, blank=True, default="", null=True)

    def __str__(self):
        return self.user.username

Seralizers.py

from rest_framework import serializers
from django.contrib.auth.models import User
from .models import UserProfile


class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = '__all__'


class UserSerializer(serializers.ModelSerializer):
    profile = UserProfileSerializer()

    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'first_name', 'last_name', 'profile']

Views.py

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer



class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAdminUser]

I know there is .create() method which can be override according to DRF documentation. Plus I want to override this method in views.py. Is there any approach to do this. Thanks in advance for your addition in my knowledge.

Upvotes: 0

Views: 1790

Answers (1)

Iftieaq
Iftieaq

Reputation: 1964

There are two ways of doing that in my knowledge.

The first one is overriding the create method of the generic view. Which is as follows:

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def create(self, request, *args, **kwargs):

        #let django create the user with generic view
        response = super().create(request, *args, **kwargs)

        # response.data contains the serialized data of the created user
        user_data = response.data
        user_id = response.data.get("id")

        profile = UserProfile()
        profile.user = User.objects.get(id=user_id)
        profile.save()
        
        # return the response created by the generic view
        return response

Be careful that Django Rest Framework does not support the create method for nested serializers. Set the profile field to read-only in UserSerializer

class UserSerializer(serializers.ModelSerializer):
    profile = SerializerMethodField()

    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'first_name', 'last_name', 'profile']
        read_only_fields = ('profile',)

    def get_profile(self, user):
        profile = UserProfile.objects.filter(user=user).first()
        return UserProfileSerializer(profile).data if profile is not None else None

The second way is using signals. Signals is an event dispatcher that keeps track of events such as update, delete, m2m changed, etc. Here, we can use post_save signals which trigger whenever a model gets updated. So if the User model is created, we will receive a signal and therefore crate a UserProfile for that user. You can learn more about signals from the Django Signals page.

@receiver(post_save, sender=User, dispatch_uid="create_user_profile")
def on_user_create(sender, instance, created, **kwargs):
    
    # here instance is the user object that just got created
    # created is a boolean value which will be true if the record is newly created
    if created:
        
        profile = UserProfile()
        profile.user = instance
        profile.save()

Upvotes: 1

Related Questions