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.
Related
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.
I have a model with a one-to-one relationship with a main model:
class User(models.Model):
id = models.BigIntegerField(primary_key=True)
username = models.CharField(max_length=100, blank=True)
class AggregatedStats(models.Model):
user_id = models.ForeignKey('User', on_delete=models.DO_NOTHING, unique=True)
followers_30d = models.BigIntegerField(blank=True)
I have written the following serializers:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'followers']
class AggregatedStatsSerializer(serializers.HyperlinkedModelSerializer):
username = UserSerializer(source='user.username')
class Meta:
model = AggregatedStats
fields = ['followers_30d', 'username']
I am trying to return the username from the User model, but whatever I try to get it, the best I can do is get the hyperlinked related field from user, but not the actual "username" attribute. How would you return this?
You can simply create a field and return it:
class AggregatedStatsSerializer(serializers.HyperlinkedModelSerializer):
username = SerializerMethodField()
class Meta:
model = AggregatedStats
fields = ['followers_30d', 'username']
def get_username(self, obj):
return obj.user_id.username
I am trying to have an api for my answers app.
When a post is created, the user is always set to null.
I am not sure of why that is.
How do I auto-add some fields to the saving object, like the authenticated user, user IP address?
Here is my serializer:
User = get_user_model()
class UserPublicSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'is_staff',
'photo_url'
]
class AnswerCreateUpdateSerializer(ModelSerializer):
user = UserPublicSerializer(read_only=True)
class Meta:
model = Answer
fields = [
'object_pk',
'content_type',
'answer',
'user'
]
read_only_fields = ['user']
And the api create view:
class AnswerCreateAPIView(CreateAPIView):
queryset = Answer.objects.all()
serializer_class = AnswerCreateUpdateSerializer
And the model is:
class AnswerAbstractModel(BaseAnswerAbstractModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_answers",
on_delete=models.SET_NULL)
answer = FroalaField()
submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)
class Answer(AnswerAbstractModel):
class Meta(AnswerAbstractModel.Meta):
db_table = "answers"
I beg your pardon for my ignorance of the django world. I am trying hard to figure it out though.
That said, what should be the approach here?
You have to override the perform_create() method of the AnswerCreateAPIView as below,
class AnswerCreateAPIView(CreateAPIView):
queryset = Answer.objects.all()
serializer_class = AnswerCreateUpdateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Update-1
Why do you need to override?
Case-1
You set user as read_only_field in your AnswerCreateUpdateSerializer. By doing so DRF serialzier won't fetch any input data from JSON payload even if the data present in it
Case-2
If you unset the read_only property, DRF will ask you to provide a dict like object for the user since you have a Nested serializer configuration.
The DRF way
If you want to save the logged-in user to the DB in DRF, you should set the user field as read_only and override appropriate methods and fetch the logged-in user from request object.
I'm trying to send the following HTTP Post API request to create a new EventInterest object. How can I accomplish this in a smallest payload instead of sending the entire object? I'm attempting an extra layer of security-through-obfuscation and instead of using the default integer pk, how can I use uuid for Event and username for User? .... Or do the extra SQL lookups negate the benefits of simplifying the payload and I should just use pk?
models.py
class Event(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(max_length=500)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True, blank=True)
class EventInterest(models.Model):
event = models.ForeignKey(Event)
sender = models.ForeignKey(settings.AUTH_USER_MODEL) # from User
api.py
class EventInterestViewSet(mixins.CreateModelMixin, GenericViewSet):
queryset = models.EventInterest.objects.all()
serializer_class = serializers.EventInterestSerializer
lookup_field = 'uuid'
serializer.py
class EventInterestSerializer(serializers.ModelSerializer):
# event = serializers.SlugRelatedField(read_only=True, slug_field='uuid')
# recipient = serializers.SlugRelatedField(read_only=True, slug_field='username')
# sender = serializers.SlugRelatedField(read_only=True, slug_field='username')
class Meta:
model = models.EventInterest
fields = (
'event', #works with pk, want uuid
'sender', # works with pk, want username
)
HTTP Post:
{
"event": "da9290c6-f6f8-4d27-bfe0-d388ed911fe8",
"sender":"eX8gkxJNDREv" //this is the username field
}
You need to make your UUIDField as primary key. Just like this:
class Event(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
....
As you defined uuid in Event model, you have to define uuid in User model too. In order to do that, you have to extend the default user model. Then you have to override the create() method of EventInterestSerializer to do a lookup on respective UUID field instead of pk
models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
class Event(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(max_length=500)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
class EventInterest(models.Model):
event = models.ForeignKey(Event)
sender = models.ForeignKey(User)
serializer.py
class EventInterestSerializer(serializers.ModelSerializer):
class Meta:
model = EventInterest
fields = ('event', 'sender',)
def create(self, validated_data):
try:
return EventInterest.objects.get(event__uuid=validated_data['event'],
sender__uuid=validated_data['sender'])
except EventInterest.DoesNotExist:
raise serializers.ValidationError("No matching data found")
Rest Framework Django - Disable field to accept null values
How can I configure for the serialized Model to accept blank fields?
Warning
{"Operacao": ["This field can not be blank."]}
Model
class SEF(models.Model):
operacao = models.CharField(max_length=10, null=True)
documento = models.CharField(max_length=50, null=True)
user = models.ForeignKey(User)
Serializer
class SEFCreateSerializer(serializers.ModelSerializer):
class Meta:
model = SEF
fields = ('operacao', 'documento', 'user')
View
sef_create = SEFCreateSerializer(data=data, many=True)
if sef_create.is_valid():
sef_create.save()
salvo = HTTP_200_OK
else:
salvo = sef_create.errors
Include allow_blank=True in the field definition as this:
operacao = serializers.CharField(allow_blank=True, allow_null=True)
class SEFCreateSerializer(ModelSerializer):
class Meta:
model = SEF
fields = ('operacao', 'documento', 'user')
extra_kwargs = {'operacao': {'required': False}}
You can set default value like
operacao = models.CharField(max_length=10, default=0)
if you specify a field in model like this it will take default value as 0 if nothing is present.
or in serializer
operacao = serializers.CharField(allow_null = True)