Django Rest Framework ModelSerializer giving error for primary field - django

I am working on a project where I have created custom user by extending AbstractBaseUser and PermissionMixin, the model class is following.
class User(AbstractBaseUser, PermissionsMixin):
phone_number = models.CharField(
primary_key=True, validators=[MinLengthValidator(10)], max_length=10
)
password = models.CharField(
null=False, blank=False, validators=[MinLengthValidator(8)], max_length=225
)
date_joined = models.DateTimeField(null=False, blank=False, default=timezone.now)
last_login = models.DateTimeField(null=True, blank=True)
last_logout = models.DateTimeField(null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = "phone_number"
REQUIRED_FIELDS = []
objects = CustomUserManager()
#staticmethod
def hash_password(sender, instance, *args, **kwargs):
if not instance.is_staff and not instance.is_superuser:
instance.set_password(instance.password)
def get_token(self):
return Token.objects.get(user=self)
def __str__(self):
return self.phone_number
# signals for Model User
pre_save.connect(User.hash_password, sender=User)
And the following ModelSerializer corresponding to it.
class UserLoginSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["phone_number", "password"]
Now if If I pass the post data as:-
{
"phone_number":8888888888,
"password":12345678
}
What I am getting:
The serializer.is_valid() is returning False.
if I am doing serializer.is_valid(raise_exception=True) then I am
getting response as:
{
"phone_number": [
"user with this phone number already exists."
]
}
My Doubts are:
I know that 8888888888 is already in the DataBase but I still want to
access it using serializer.validated_data.get('phone_number', None)
I also want to know the reason, why this is happening, it is acting
like as if I am trying to insert a record, but if I pass phone number
like 8888888887(Not present in the database), then its working fine

As phone_number is a primary key, it has unique validator by default.
You can add custom validators in the serializer as shown below.
class UserLoginSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["phone_number", "password"]
extra_kwargs = {
'phone_number': {
'validators': [MinLengthValidator(10)],
}
}
By adding the just the validators we require, the serializer only validates according to the given validators. if the 'validators': [], are set to empty list, then no validation is performed on the particular field.
I have added MinLengthValidator(10) validator, which you have used in the User Model. You can import it and use it here.

It is happening because phone_number is the primary key. The primary key is a unique field, so you cannot have duplicate records. Check here.

Related

How to update custom user's model fields using APIView update method

I am trying to code up a APIView's update method so I can change model fields in my custom AbstractUser model.
I read the documentation for APIViews and other examples, but most of them involved another OneToOne 'profile' model like in this example and or coding up the serializer which I don't think it's necessary for user model(correct me if I'm wrong)
I am unsure how to implement the same update method for a user model.
This is my custom user model. I am trying to update referred_count and tokens fields here from frontend
users/models.py
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
username = None
first_name = models.CharField(max_length=100, default="unknown")
last_name = models.CharField(max_length=100, default="unknown", blank=True)
profile_pic = models.CharField(max_length=200, default="unknown")
premium = models.BooleanField(default=False)
referred_count = models.IntegerField(default=0)
tokens = models.IntegerField(default=0)
email = models.EmailField(unique=True, db_index=True)
secret_key = models.CharField(max_length=255, default=get_random_secret_key)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = UserManager()
class Meta:
swappable = "AUTH_USER_MODEL"
users/api.py
class UpdateFields(ApiAuthMixin, ApiErrorsMixin, APIView):
def update(self, request, *args, **kwargs):
# update various user fields based on request.data
# I am not sure what should go inside here.
return request.user.update(request, *args, **kwargs)
I want to send something like the following from frontend which will pass through the UpdateFields APIView and 'patch' these user fields
{
'tokens': 100,
'referred_count': 12,
}
users/urls.py
urlpatterns = [
path("me/", UserMeApi.as_view(), name="me"),
path("update/", UpdatePremium.as_view(), name="update"),
]
Try out this.
user = request.user
user.tokens = request.data.get('token')
user.referred_count = request.data.get('referred_count')
user.save()

Django Rest Framework - Nested Serialization

I have listed the models and Serializers below.
Models:
class CustomGroup(Group):
description = models.CharField(max_length=150, null=True, blank=True, verbose_name="Human readable name")
def __str__(self):
return self.description or self.name
class Meta:
db_table = "groups"
class User(AbstractBaseUser, PermissionsMixin):
"""
Custom user model that supports email.
"""
groups = models.ManyToManyField(
CustomGroup,
verbose_name=('groups'),
blank=True,
help_text= (
'The groups this user belongs to. A user will get all permissions '
'granted to each of their groups.'
),
related_name="user_set",
related_query_name="user",
through="UserGroup"
)
email = models.EmailField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
tenant = models.ForeignKey(
Tenant, on_delete=models.CASCADE, null=True, db_column="tenant_id", blank=True
)
......
objects = UserManager()
USERNAME_FIELD = "email"
# REQUIRED_FIELDS = ["tenant_id"]
class Meta:
db_table = "users"
class UserGroup(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
group = models.ForeignKey(CustomGroup, on_delete=models.CASCADE)
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, null=True)
class Meta:
db_table = "user_groups"
Serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ("first_name","last_name")
class UserGroupSerializer(serializers.ModelSerializer):
users = UserSerializer(many=True,read_only=True)
class Meta:
model = UserGroup
fields = ('group_id','users')
I want response like this :
{
"group_id":
"users": [
{
"first_name": "",
"last_name":""
}
...
]
...
]
}
I am only getting:
[{"group_id":1}, ...}]
How does the UserSerializer serialize the required user ids from the User model? Since user is defined as a foreign key in UserGroup does it happen automatically? or am i missing any relation between User and UserGroup?
Your UserGroup is only linked to one User and one Group. So you will only be able to access to one user and one group directly.
(Didn't you just mispell user in your serializer ? you wrote users)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ("first_name","last_name")
class UserGroupSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = UserGroup
fields = ('group_id','user')
but this will give you something like
{
"group": "<group_pk>",
"user": {
"first_name": "first_name",
"last_name": "last_name"
}
}
If you want to associate a specific Group with all related user, it takes something different. You would need to retrieve all the Users linked to a UserGroup having the group_id of the current UserGroup.
class UserGroupSerializer(serializers.ModelSerializer):
class Meta:
model = UserGroup
fields = ('group_id', )
def to_representation(self, instance):
data = super().to_representation(instance)
related_users = get_user_model().objects.filter(usergroup__group=instance.group)
data['users'] = UserSerializer(instance=related_users, many=true).data
return data
But, again, this may not be the most efficient way to achieve this as it will probably result in duplicated data. So you should probably consider accessing it from your "GroupSerializer". The same logic would be applied.

Django restframework unique error not processed

When I did not fill in the necessary fields, Django would return a message to the client, but when the field was unique, an error message would appear.
Why didn't Django handle it?
models.py
class UserModel(models.Model):
email = models.EmailField(unique=True)
username = models.CharField(max_length=16, blank=True, null=True)
password = models.CharField(max_length=512)
is_active = models.BooleanField(default=False)
sign_up_date = models.DateTimeField(auto_now_add=True)
sign_in_date = models.DateTimeField(null=True, blank=True)
serializers.py
class UserSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField()
username = serializers.CharField(max_length=16)
password = serializers.CharField(max_length=512)
is_active = serializers.BooleanField(read_only=True, default=False)
sign_up_date = serializers.DateTimeField(read_only=True)
sign_in_date = serializers.DateTimeField(read_only=True)
class Meta:
model = UserModel
fields = (
'id', 'email', 'username', 'password', 'is_active', 'sign_up_date', 'sign_in_date',)
views.py
class SignupView(CreateAPIView):
serializer_class = UserSerializer
queryset = UserModel.objects.all()
error
IntegrityError at /api/signup/
duplicate key value violates unique constraint "User_email_key"
DETAIL: Key (email)=(test#gmail.com) already exists.
I hope Django can return error messages to the client.
{"email": ["already exists"]}
In the UserSerializer you are declaring some of the model fields again such as email etc. By doing that the behavior of the field is not copied from how it was defined on the model. It works as if that behavior has been overridden.
You can drop email = serializers.EmailField() and then the default behaviour would kick in and you will get to see the error message corresponding to unique field.
In the same fashion you could drop other fields too which are just replicas of the fields on the model.

Access field from intermediary model in Django Rest Framework

Having a hard time trying to access a field from an intermediary model in DRF.
Let's see the related models:
class School(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50, verbose_name=_(u'Name'))
staff_order = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_(u'Staff ordering'), through='StaffOrder', related_name='school_staff_order')
class User(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
address = models.CharField(max_length=150, blank=True, null=True)
about_me = models.CharField(max_length=200, blank=True, null=True)
REQUIRED_FIELDS = ['email']
def __unicode__(self):
return u'{0}'.format(self.username)
class StaffOrder(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
school = models.ForeignKey(School)
order = models.PositiveSmallIntegerField(default=0, verbose_name=_(u'Staff ordering for this school'))
class Meta:
verbose_name_plural = _(u'Staff ordering')
Now what I'm expecting is being able to read order field from StaffOrder in when returning a QuerySet for users (StaffSerializer). Here are the Serializers:
class StaffRoleSerializer(serializers.ModelSerializer):
class Meta:
model = StaffOrder
fields = (
'order',
)
class StaffSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=75, required=True)
email = serializers.CharField(max_length=75, required=True)
order = StaffRoleSerializer(source='school_staff_order')
class Meta:
model = User
What is returned in order for the StaffSerializer is a Manager, instead of the order field from the StaffOrder model related with this User.
A JSON expected response for Staff would be something like this:
[
{
"username": "Denise",
"email": "deniseburton#maximind.com",
"order": 9
},
{
"username": "Ina",
"email": "inaburton#maximind.com",
"order": 4
}
]
I'd love to be able to also write this value from the serializer, but I can do that in the Viewset, but I really need to read this value in the Serializer itself...any idea what I'm missing here?
First you have to understand that one user can have many staff orders. In your models you have defined it that way.
To get the json output you have specified in your question you need to query the StaffOrder objects instead of users.
class StaffOrderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
class Meta:
model = StaffOrder
fields = ('order', )
Use this serializer class in a list view:
class StaffOrderListApi(generics.ListAPIView):
serializer_class = StaffOrderSerializer
def get_queryset(self):
# using distinct because same user can have multiple staff orders
# based on your example it seems you want distinct users
return StaffOrder.objects.all().distinct('user')
This will get you exactly the json you want.

Using reverse relationships with django-rest-framework's serializer

My model looks like this:
class User(TimestampedModel):
name = models.CharField(max_length=30, null=False, blank=False)
device = models.CharField(max_length=255, null=False, blank=False)
class Comment(TimestampedModel):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
And my serializer looks like this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name',)
class CommentListItemSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Comment
fields = ('user', 'contents', 'rating')
And the view:
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all()
It's almost getting the job done ;). The response I'm getting looks like this:
"results": [
{
"user": {
"name": "Ania"
},
"contents": "Very good",
"rating": 6
},
{
"user": {
"name": "Anuk"
},
"contents": "Not very good",
"rating": 1
}
]
There are two problems with that response.
I don't want to have this nested object "user.name". I'd like to receive that as a simple string field, for example "username".
Serializer makes a database query (not a join, but a separate query) for each user, to get his/her name. Since that's unacceptable, how to fix that?
Serializer makes a database query (not a join, but a separate query)
for each user, to get his/her name.
You can use select_related() on the queryset attribute of your view. Then accessing user.name will not result in further database queries.
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all().select_related('user') # use select_related
I don't want to have this nested object "user.name". I'd like to
receive that as a simple string field, for example "username"
You can define a read-only username field in your serializer with source argument. This will return a username field in response.
class CommentListItemSerializer(serializers.ModelSerializer):
# define read-only username field
username = serializers.CharField(source='user.name', read_only=True)
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')
You can add custom functions as fields
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
def username(self):
return self.user.name
class CommentListItemSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')