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 ?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With