I am trying to set up a custom login serializer in Django and want a custom response but the default one always show:
{
"username":[
"This field is required."
],
"password":[
"This field is required."
]
}
I tried to set up my serializer like so:
class MyLoginSerializer(serializers.Serializer):
username = serializers.CharField(required=True, allow_blank=True)
email = serializers.EmailField(required=False, allow_blank=True)
password = serializers.CharField(style={'input_type': 'password'})
def authenticate(self, **kwargs):
return authenticate(self.context['request'], **kwargs)
def _validate_email(self, email, password):
user = None
if email and password:
user = self.authenticate(email=email, password=password)
else:
msg = _('Must include "email" and "password".')
raise serializers.ValidationError(msg)
return user
def _validate_username(self, username, password):
print("in username")
user = None
if username and password:
print("in username 2")
try:
user = self.authenticate(username=username, password=password)
except Exception:
raise serializers.ValidationError("Wrong")
else:
print("in username 3")
msg = _('Must include "username" and "password".')
raise serializers.ValidationError(msg)
return user
def _validate_username_email(self, username, email, password):
user = None
if email and password:
user = self.authenticate(email=email, password=password)
elif username and password:
user = self.authenticate(username=username, password=password)
else:
msg = _(
'Must include either "username" or "email" and "password".'
)
raise serializers.ValidationError(msg)
return user
def validate(self, attrs):
username = attrs.get('username')
email = attrs.get('email')
password = attrs.get('password')
user = None
if 'allauth' in settings.INSTALLED_APPS:
from allauth.account import app_settings
# Authentication through email
if (app_settings.AUTHENTICATION_METHOD ==
app_settings.AuthenticationMethod.EMAIL):
user = self._validate_email(email, password)
# Authentication through username
elif (app_settings.AUTHENTICATION_METHOD ==
app_settings.AuthenticationMethod.USERNAME):
user = self._validate_username(username, password)
# Authentication through either username or email
else:
user = self._validate_username_email(username, email, password)
else:
# Authentication without using allauth
if email:
try:
username = GameUser.objects\
.get(email__iexact=email)\
.get_username()
except UserModel.DoesNotExist:
pass
if username:
user = self._validate_username_email(username, '', password)
# Did we get back an active user?
if user:
if not user.is_active:
msg = ('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = ('Wrong login information.')
raise exceptions.ValidationError(msg)
# If required, is the email verified?
if 'rest_auth.registration' in settings.INSTALLED_APPS:
from allauth.account import app_settings
if app_settings.EMAIL_VERIFICATION == app_settings\
.EmailVerificationMethod\
.MANDATORY:
email_address = user.emailaddress_set.get(email=user.email)
if not email_address.verified:
raise serializers.ValidationError((
'E-mail is not verified.'
))
attrs['user'] = user
return attrs
And I have this set as my login serializer in my settings.py:
REST_AUTH_SERIALIZERS ={
'LOGIN_SERIALIZER': 'api.serializer.MyLoginSerializer'
}
And here is my custom login view:
class CustomLoginView(LoginView):
permission_classes = (AllowAny,)
serializer_class = MyLoginSerializer
def get_response(self):
original_response = super().get_response()
print("ORIGINAL REESPONSE:")
print(str(self.user))
mydata = {"username": str(self.user), "status": "success"}
original_response.data.update(mydata)
return original_response
How would I get it to show the custom 'Must include "email" and "password".'
instead of the default message?
Thanks!
Short answer, use the serializer to validate the data then customize the responses based off of the serializers validator. You could even go so far as to use cases.
The basic structure for this is implemented in your view/viewset like:
class MyViewSet(viewsets.ModelViewSet):
"""
My View Set
"""
queryset = #
serializer_class = #
# take which ever method you want to add the custom formatting to
# and modify it, or create a new method with its own special endpoint
def view_set_method_to_modify(self, *other_args):
# Do some stuff to keep the desired functions of the method you are hacking
serializer = serializers.MySerializer(data=request.data, context={"request": request})
if serializer.is_valid():
# Do some stuff with serializer.validated_data['my_var'])
return Response({'message': 'Yay the data is valid!!!'},
status=status.HTTP_200_OK)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Here is an example that I have working to update user passwords. Instead of hacking an existing method, I ended up adding a new method with its own special endpoint.
views.py
class UserViewSet(viewsets.ModelViewSet):
"""
User View Set
"""
queryset = User.objects.all()
serializer_class = serializers.UserSerializer
@action(methods=['post'], detail=True,
url_path='change-password', url_name='change_password')
def set_password(self, request, pk=None):
user = self.get_object()
serializer = serializers.PasswordSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
user.set_password(serializer.validated_data['new_password'])
user.save()
return Response({'message': 'password set'},
status=status.HTTP_200_OK)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
serializers.py
class PasswordSerializer(Serializer):
old_password = CharField(required=True)
new_password = CharField(required=True)
def validate(self, data):
request = self.context['request']
user = request.user
new_password = request.data['new_password']
old_password = request.data['old_password']
validate_passwords(old=old_password, new=new_password, user=user)
return data