How to make log-in with Django rest api - django

hello i learning about Django rest api, I am learning through someone else's code, but I don't know how to make Login.
my code :
model:
class User(models.Model):
class Meta:
db_table = "users"
created_at = models.DateTimeField(default = timezone.now)
updated_ay = models.DateTimeField(auto_now= True)
email = models.CharField(max_length = 128, unique= True)
password = models.CharField(max_length = 255)
active = models.BooleanField(default=False)
token = models.CharField(max_length= 255, null = True)
nickname = models.CharField(max_length = 255, null = True)
serializer:
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
class Meta:
model = User
fields = '__all__'
def to_internal_value(self, data):
ret = super(UserSerializer, self).to_internal_value(data)
# cipher = AESSipher()
# ret['password'] = cipher.encrypt_str(ret['password'])
return ret
def to_representation(self, obj):
ret = super(UserSerializer, self).to_representation(obj)
print(ret)
return ret
def validate_email(self, value):
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("Email already exists")
return value
def validate_password(self, value):
if len(value) < 8:
raise serializers.ValidationError("The password must be at least %s characters long. " % 8)
return value
def create(self, validate_data):
user = User.objects.create(
email = validate_data['email'],
password = validate_data['password'],
)
user.active = False
user.save()
message = render_to_string('user/account_activate_email.html', {
'user': user,
'domain' : 'localhost:8000',
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user)
})
mail_subject = 'sign up mail.'
to_email = 'mymail#gmail.com'
email = EmailMessage(mail_subject, message, to=[to_email])
email.send()
return validate_data
views:
class SignUp(APIView):
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class UserActivate(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk = uid)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
try:
if user is not None and account_activation_token.check_token(user, token):
user.active = True
user.token = token
user.save()
return Response(user.email + 'email active', status=status.HTTP_200_OK)
else:
return Response('Expired Link', status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
print(traceback.format_exc())
Since I want to handle log-in status in the Mobile Client section,
I want to get only user model information when I log-in.
When Enter Email and Password in Client and I want to get user information.
How to make log-in? Do you know anyone?

Here is the link to the article: custom user authentication; it should help you
https://wsvincent.com/django-rest-framework-user-authentication-tutorial/

Hi you can use Django packages for this. for example you can use rest-auth package this package have all the things that you need for log in log out and reset password and all you need is that follow the rest auth documentation :
. https://pypi.org/project/django-rest-auth/
. https://django-rest-auth.readthedocs.io/en/latest/

Related

What is the proper way to change custom user password?

I am working on my Django (DRF) application. A have a CustomUser model
class CustomAccountManager(BaseUserManager):
def create_superuser(self, email, user_name, password, **other_fields):
...
def create_user(self, email, user_name, password, **other_fields):
if not email:
raise ValueError(_('You must provide an email address'))
email = self.normalize_email(email)
user = self.model(email=email, user_name=user_name, **other_fields)
user.set_password(password)
user.save()
return user
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
user_name = models.CharField(max_length=150, unique=True) # Full name
phone_number = models.CharField(max_length=20, unique=True)
...
I have created a custom way to change password.
I am sending current_password, new_password_verify and new_password_verify in body parameter.
My solution is working, but looks bulky
What is the proper way to implement password change in Django?
class CustomUserViewSet(viewsets.ModelViewSet):
def update(self, request: Request, *args, **kwargs):
instance: CustomUser = self.get_object()
serializer = self.get_serializer(
instance, data=request.data, partial=True
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, "_prefetched_objects_cache", None):
instance._prefetched_objects_cache = {}
return Response({
"is_password_updated": self.update_password(request, instance), # <-------UPDATE
"result": serializer.data
})
def update_password(self, request, instance):
"""update password if 'new_password_verify' and 'new_password' are in request"""
if "current_password" in request.data and instance.check_password(request.data["current_password"]) and \
"new_password" in request.data and "new_password_verify" in request.data and \
request.data["new_password"] == request.data["new_password_verify"]:
instance.set_password(request.data["new_password"])
instance.save()
return True
return False
I think you can extract the validating logic from the view to make it more modularized by defining the serializer.
class ChangePasswordSerializer(serializers.Serializer):
current_password = serializers.CharField(trim_whitespace = False, validators=[validate_password]),
new_password = serializers.CharField(trim_whitespace = False, validators=[validate_password])
new_password_verify = serializers.CharField(trim_whitespace = False)
def validate(self, attrs):
if attrs.get('new_password') != attrs.get('new_password_verify'):
serializers.ValidationError('Password and confirm password do not match')
return attrs
def validate_password(value):
# you can set your own validating logic here if you want to
# for example, like validations for length or regex
pass
Of course, you don't need to upload new_password_verify data, and check that part in the frontend then the new_password_verify field and validate method is not necessary and the code will be simpler.
class CustomUserViewSet(viewsets.ModelViewSet):
def update(self, request: Request, *args, **kwargs):
...
return Response({
"is_password_updated": self.update_password(request, instance)
"result": serializer.data
})
def update_password(self, request, instance):
serializer = ChangePasswordSerializer(data = request.data)
if serializer.is_valid():
input_data = serializer.validated_data
cur_password = input_data.get('current_password')
new_password = input_data.get('new_password')
if instance.check_password(cur_password):
instance.set_password(new_password)
instance.save()
return True
return False

how to add more attributes to what an api returns

I am trying to write an API using django rest framework in which, you give a username and a password and in return you get an AuthToken or in other words you login. now I want this API to also return some fields like the email of the user along with the AuthToken. so if the authentication was successful, the get an authToken and the user's email. Can anyone help me on how I could be able to do this by adding or changing a bit of my code?
These are my models:
class UserManager(BaseUserManager):
def createUser(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Email Not Found!!!')
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def createSuperUser(self, email, password):
user = self.createUser(email, password)
user.isAdmin = True
user.isSuperUser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
isSuspended = models.BooleanField(default=False)
isAdmin = models.BooleanField(default=False)
emailActivation = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
objects = UserManager()
USERNAME_FIELD = 'username'
These are my serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('username','email', 'password', 'name')
extra_kwargs = {'password': {'write_only': True, 'min_length': 8}}
def create(self, validated_data):
return get_user_model().objects.createUser(**validated_data)
def update(self, instance, validated_data):
password = validated_data.pop('password', None)
user = super().update(instance, validated_data)
if password:
user.set_password(password)
user.save()
return user
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(trim_whitespace=False)
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
user = authenticate(
request=self.context.get('request'),
username= username,
password= password
)
if not user:
msg = 'Authentication Failed.'
raise serializers.ValidationError(msg, code='authentication')
attrs['user'] = user
return attrs
And finally, these are my views:
class CreateUserView(generics.CreateAPIView):
serializer_class = UserSerializer
class CreateTokenView(ObtainAuthToken):
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
class ManageUserView(generics.RetrieveAPIView):
serializer_class = UserSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
return self.request.user
create a new serializer inside serializer.py
from rest_framework.authtoken.models import Token as DefaultTokenModel
class TokenSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = DefaultTokenModel
fields = ('key', 'user',)
add this function in views.py
def get_token_response(user):
serializer_class = TokenSerializer
token, _ = DefaultTokenModel.objects.get_or_create(user=user)
serializer = serializer_class(instance=token)
return Response(serializer.data, status=status.HTTP_200_OK)
now override post method of CreateTokenView
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
return get_token_response(user)
For what i understand you just want to return the toekn and the email of the user right? I used this class based view to login users using token authentication.
from rest_framework.authtoken.views import ObtainAuthToken
class UserLoginView(ObtainAuthToken):
def post(self, request, **kwargs):
serializer = self.serializer_class(data=request.data,
context={
'request':request
})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response(
{
'token':token.key,
'email':user.email,
}
)

Django - MultiValueKeyError in form when running test

I have a CustomUser model, using django's auth for authentication, and a custom signup view. In the signup form I have some validation to check that the email_suffix (domain of the email) matches with the district that they select in the form. I also check that the email is unique.
When running a test on this, I get an error on the form:
raise MultiValueDictKeyError(key)
django.utils.datastructures.MultiValueDictKeyError: 'district'
Model
class CustomUser(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
is_student = models.BooleanField('student status', default=False)
is_teacher = models.BooleanField('teacher status', default=False)
SD23 = 'SD23'
SD39 = 'SD39'
SD67 = 'SD67'
SDISTRICT = [
(SD23, 'Kelowna SD23'),
(SD39, 'Vancouver SD39'),
(SD67, 'Summerland SD67'),
]
district = models.CharField(
max_length=4, choices=SDISTRICT, blank=True, default='SD39')
paper = models.BooleanField(default=False)
def __str__(self):
return self.username
View
def signup(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
to_email = form.cleaned_data.get('email')
# make the username the same as the email
user.username = str(to_email)
user.is_teacher = True
user.is_staff = True
user.is_active = False
user.save()
group = Group.objects.get(name='teacher')
user.groups.add(group)
current_site = get_current_site(request)
print(urlsafe_base64_encode(force_bytes(user.pk)))
sendgrid_client = SendGridAPIClient(
api_key=os.environ.get('SENDGRID_API_KEY'))
from_email = From("doug#smartmark.ca")
to_email = To(to_email)
subject = "Activate your SmartMark Account"
active_link = render_to_string('account/acc_active_email_link.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
html_text = f'Hello {user}<br/><p>Please click on the link below to confirm</p>{active_link}'
html_content = HtmlContent(html_text)
mail = Mail(from_email, to_email, subject,
html_content)
response = sendgrid_client.send(message=mail)
return redirect(reverse('accounts:account_activation_sent'))
else:
form = CustomUserCreationForm()
return render(request, 'account/signup.html', {'form': form})
Form
class CustomUserCreationForm(UserCreationForm):
""" form from class based view """
paper = forms.BooleanField(
label='I agree that keeping a backup paper gradebook is best practice')
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
class Meta:
model = get_user_model()
fields = ('email', 'first_name', 'last_name', 'district', 'paper')
def signup(self, request, user):
user.district = self.cleaned_data['district']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.paper = self.cleaned_data['paper']
user.email = self.cleaned_data['email']
def check_suffix(self, e, d):
email_domain = e.split("#", 2)[1]
t_suffix = email_creation(d)[0] # method which takes the 'district' and creates an email suffix
print(email_domain)
print(t_suffix)
if email_domain == t_suffix:
return True
else:
return False
def clean_email(self):
value = self.cleaned_data["email"]
value_district = self.data["district"]
if not value:
raise forms.ValidationError('An Email address is required.')
check_users = CustomUser.objects.filter(email__iexact=value)
if check_users:
raise forms.ValidationError('This email is already in use.')
if value and not check_users:
if not self.check_suffix(value, value_district):
self.add_error(
"email", "Your email address does not match your school district.")
return value
email_creation method
def email_creation(school_district):
if school_district == "SD23":
teacher_suffix = "sd23.bc.ca"
student_suffix = "learn.sd23.bc.ca"
email_prefix = True
elif school_district == "SD39":
teacher_suffix = "vsb.bc.ca"
student_suffix = "learn.vsb.bc.ca"
email_prefix = True
elif school_district == "SD67":
teacher_suffix = "sd67.bc.ca"
student_suffix = teacher_suffix
email_prefix = True
return(teacher_suffix, student_suffix, email_prefix)
Test
def test_signup_endpoint(self):
email = "foo#vsb.bc.ca"
result = self.client.post('/accounts/signup/', {'email': email})
self.assertEqual(result.status_code, 200)
created_user = get_user_model().objects.get(email=email)
self.assertTrue(created_user.is_active ==
False, created_user.to_dict())
The check_suffix might seem a bit convoluted but the MultiValueDictKeyError is happening before this method is called. The problem seems to be the `self.data['district'], it doesn't like the choice? Do I need to set/define this choice/field in my test? I tried that by adding the following in the test, but it didn't change the error.
email = "foo#vsb.bc.ca"
district = "SD39"

Object of type data is not JSON serializable error in Django

I have a registration page that allows a user to sign up. After doing so, I want to call an API and then, save the data to my model (not saving it to a form though). I tried doing this:
models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE, primary_key=True, related_name = 'profile')
address = models.TextField()
birthday = models.DateField()
def __str__(self):
return str(self.user)
views.py:
def signup(request):
if request.method == 'POST':
user_form = UserForm(request.POST)
register_form = RegisterForm(request.POST)
if user_form.is_valid() and register_form.is_valid():
username = user_form.cleaned_data.get('username'),
first_name = user_form.cleaned_data.get('first_name'),
last_name=user_form.cleaned_data.get('last_name'),
email=user_form.cleaned_data.get('email'),
password=user_form.cleaned_data.get('password2'),
birthday = register_form.cleaned_data.get('dob'),
address=register_form.cleaned_data.get('address'),
payload = {'username': username,'first_name': first_name,'last_name': last_name,'email':email,'password':password,'register' : {'birthday': birthday,'address': address}}
response = requests.post('http://127.0.0.1:8000/my_api/',json=payload)
return redirect("home") #re-direct if login is successful
else:
user_form = UserForm()
register_form = RegisterForm()
return render(request, 'users/register.html', {'user_form': user_form, 'register_form': register_form})
class RegisterAPI(APIView):
permission_classes = [AllowAny]
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
content = {'status': 'You are registered'}
return Response(content, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py:
from users.models import Profile
from django.contrib.auth.models import User
class ProfileSerializer(serializers.ModelSerializer):
birthday = serializers.DateField(format="%Y-%m-%d")
class Meta:
model = Profile
fields = ('birthday','address')
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username','first_name','last_name','email', 'password', 'profile')
def create(self, request, validated_data, *args, **kwargs):
register_data = validated_data.pop('profile')
password = validated_data.pop('password', None)
user = User.objects.create(**validated_data)
if password is not None:
user.set_password(password)
user.save()
Profile.objects.create(user = user, **register_data)
return validated_data
However, I am getting this error:
Object of type data is not JSON serializable error in Django
It seems that it's got to do with the birthday. On my template, a user can display the date of birth as 'YYYY-MM-DD'. How can I fix this error?
The create method in your UserSerializer should return a User instance instead of validated_data.
def create(self, request, validated_data, *args, **kwargs):
register_data = validated_data.pop('profile')
password = validated_data.pop('password', None)
user = User.objects.create(**validated_data)
if password is not None:
user.set_password(password)
user.save()
Profile.objects.create(user = user, **register_data)
return user

How to prevent JWT Django (all-auth) from changing token when its username is updated?

I'm using all-auth / django-rest-auth for Authorization. When the User changes his/her username, Django (Django REST Framework) changes user's token and this makes user log-out from app; I set the app to logout if its user's token is invalid.
What I want to do is that even if user changes username, email, or any field in User, it keeps token.
Here is settings.py
REST_USE_JWT = True
AUTHENTICATION_BACKENDS = (
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth` specific authentication methods, such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
# Facebook OAuth2
'social_core.backends.facebook.FacebookAppOAuth2',
'social_core.backends.facebook.FacebookOAuth2',
# django-rest-framework-social-oauth2
'rest_framework_social_oauth2.backends.DjangoOAuth2',
)
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),
}
...
Thanks!
Suggestion:
1) Make username as some hash object (uuid) and create new field called username_custom,
(change your login and register views)
2) Prepare your mode as:
USERNAME_FIELD = 'username' so it is default username in Django
3) Never update default field username and your token will never change.
Example
1) Use package for user model : https://github.com/jcugat/django-custom-user
2) Model example
Model
class UserModel(AbstractEmailUser):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
username = models.CharField(max_length=255, db_index=True, unique=True)
email = models.EmailField(unique=False, db_index=True)
username_custom = models.CharField(max_length=255, db_index=True, unique=True)
USERNAME_FIELD = 'username'
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.first_name = self.first_name.capitalize()
self.last_name = self.last_name.capitalize()
self.full_name = '{} {}'.format(self.first_name.capitalize(), self.last_name.capitalize())
super(UserModel, self).save(force_insert, force_update)
def clean(self):
if UserModel.objects.filter(email=self.email, username_custom=self.username_custom).exists():
raise ValidationError('User already exists!')
You can see there is new field username_custom.
Now rest api create user.
class UserRegister(APIView):
permission_classes = (AllowAny,)
authentication_classes = ()
def post(self, request):
data = request.data
# or you can check username_custom
if UserModel.objects.filter(email=data['email']).exists():
return Response(data={'success': False, 'msg': 'User with email already exists.'},
status=status.HTTP_403_FORBIDDEN)
data['username'] = '%s' % uuid.uuid4()
serializer = UserRegisterSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
token_data = UserModel.objects.get(email=serializer.data['email'])
payload = jwt_payload_handler(token_data)
token = jwt_encode_handler(payload)
return Response(data={'success': True, 'user': serializer.data,
'token': token_prefix + token}, status=status.HTTP_201_CREATED)
return Response(data={'success': False, 'msg': serializer.errors},
status=status.HTTP_400_BAD_REQUEST)
Serializer
class UserRegisterSerializer(serializers.ModelSerializer):
username = serializers.UUIDField()
class Meta:
model = UserModel
fields = ('password', 'email', 'user_type', 'username', 'first_name', 'last_name', 'username_custom',)
def create(self, validated_data):
user = UserModel.objects.create_user(**validated_data)
return user
You must preprare UPDATE method to check unique email field so you dont get 2 email in your DB, or username_custom whatever you want to be authorization.
Login API
class UserLoginView(APIView):
permission_classes = (AllowAny,)
def get(self, request, email, password):
# or you can check username_custom
if not UserModel.objects.filter(email=email.lower()).exists():
return Response(data={'success': False, 'msg': 'Email or password wrong!'},
status=status.HTTP_404_NOT_FOUND)
qv = UserModel.objects.get(email=email.lower())
user = authenticate(username=qv.username, password=password)
if user:
if user.is_active:
token_data = UserModel.objects.get(id=user.id)
payload = jwt_payload_handler(token_data)
token = jwt_encode_handler(payload)
return Response(data={'success': True,
'token': token_prefix + token}, status=status.HTTP_200_OK)
return Response(data={'success': False, 'msg': 'Email or password wrong!'},
status=status.HTTP_400_BAD_REQUEST)