Multiple Authentication Models in Django - python

I am working on a project where I need to have 3 types of Users.
Staff (for admin,superadmin like stuff)
Dealers
Customers
i have created 3 models using AbstractBaseUser class . I also created separate model managers for each .
models.py
class Staff(AbstractBaseUser):
USERNAME_FIELD = 'email'
# fields
class Customer(common_modelfields):
USERNAME_FIELD = 'email'
# customer specific fields
class Dealer(common_modelfields):
USERNAME_FIELD = 'email'
# dealer specific fields
after getting through this Django Multiple Auth Models
i made a custom authentication backend authentication.py as follows:
from .models import Staff,Customer,Dealer
var_model = ""
class authentication_models_backends(object):
def authenticate(self, request, email=None, password=None):
global var_model
if "admin" in request.path:
var_model = Staff
elif "customer" in request.path:
var_model = Customer
elif "dealer" in request.path:
var_model = Dealer
else :
return "no_model_found"
# authenticate user based on request object
try:
validated_email = email.lower()
if var_model.objects.filter(email=validated_email).exists():
user = var_model.objects.get(email = validated_email)
if user.check_password(password):
return user
else:
return "invalid password"
else:
return "email not found"
except var_model.DoesNotExist:
return None
def get_user(self, user_id):
# logic to get user
try:
return var_model.objects.get(pk=user_id)
except var_model.DoesNotExist:
return None
in settings.py
AUTH_USER_MODEL = 'account.Staff'
AUTHENTICATION_BACKENDS = ['account.authentication.authentication_models_backends',]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',#(user not found error)
)}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',}
api URL which i hit with GET request by postman
path("customerprofileview/", customer_profile_view.as_view(),name='customerprofileview'),
path("dealerprofileview/", dealer_profile_view.as_view(),name='dealerprofileview'),
here is a View with Serializer :
class customer_profile_view(APIView):
renderer_classes = [apirenderer]
permission_classes = [IsAuthenticated]
def get(self,request,format=None):
try:
serializer_object = customer_profile_serializer(request.user)
except:
pass
return Response(serializer_object.data,status=status.HTTP_200_OK)
class customer_profile_serializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ['id','full_name','email','unique_id','full_number','house_no','locality','state']
however django is looking only for the model mentioned in AUTH_USER_MODEL,if user is not an instance of AUTH_USER_MODEL's value then it throws user_not_found error . Hence, Not searching in AUTHENTICATION_BACKENDS.
i am assigning token to user on login via simple-jwt RefreshToken method and i want to get the logged in user details from models according to request - as you can see the logic in authentication.py .
i got stuck at this point . please help me to proceed further
EDIT : i forgot to mention earlier that i am not using django.contrib.auth's authenticate method to authenticate users.

I'm not sure why you need three models for user authentication. You can use one model with roles defined.
class User(AbstractBaseUser):
...
...
...
CUSTOMER = 'customer'
STAFF = 'staff'
DEALER = 'dealer'
USER_ROLES= [
(CUSTOMER, 'customer'),
(STAFF, 'staff'),
(DEALER, 'dealer'),
]
user = models.CharField(max_length=10, choices=USER_ROLES, default="your-default")

Related

How to add a custom field to the Django admin login page

To authenticate, 3 fields are needed:
user_id
password
account_id
Each account_id defines an isolated space and the user_id is unique only inside its respective account_id.
I've created a custom backend authentication and I can authenticate successfully. The problem is that the login Django Admin form has user_id and password fields by default.
I would like to add the account_id field in the original Django form template by only subclassing AuthenticationForm, is it possible?
There's a similar question here but he's using a custom login template:
Add custom field to Django Admin authentication form
Existing code..
Model:
class HeliosUser(AbstractUser, DateModelBase):
username = None
user_id = models.CharField(max_length=255)
USERNAME_FIELD = "user_id"
REQUIRED_FIELDS = ["account_id", "role", "email"]
objects = HeliosUserManager()
email = models.EmailField()
account_id = models.ForeignKey(
"agent.Agent",
on_delete=models.RESTRICT,
)
...
Backend:
class SettingsBackend(BaseBackend):
def authenticate(
self, request, user_id=None, account_id=None, password=None, **kwargs
):
try:
user = HeliosUser.objects.get(
user_id=user_id, account_id=account_id
)
except HeliosUser.DoesNotExist:
return None
if user.check_password(password):
return user
return None
def get_user(self, user_id, account_id):
try:
return HeliosUser.objects.get(
user_id=user_id, account_id=account_id
)
except HeliosUser.DoesNotExist:
return None
Admin site:
class HeliosUserAdmin(admin.AdminSite):
site_title = "Helios Administration"
site_header = "Helios Administration"
index_title = "Helios Administration"
login_form = HeliosAuthenticationForm
admin_site = HeliosUserAdmin()
Authentication form:
class HeliosAuthenticationForm(AuthenticationForm):
account_id = forms.ModelChoiceField(queryset=Agent.objects.all())
Thank's in advance!

Python Django {'user': [ErrorDetail(string='Incorrect type. Expected pk value, received BoundField.', code='incorrect_type')]}

I'm developing my backend on Django. I wrote a code for user registration under RegistrationView class, and it worked fine before. I had to incorporate email confirmation in it, which also worked fine on its own. However, after all of that was done, I added some conditions to ensure proper working, but I get the "string='Incorrect type. Expected pk value, received BoundField.'" and "Forbidden: /api/v1.0/user/register-patient/" error.
Also, to clarify the workflow. Each user is assigned a JWT token while signing up. This token is different from the token sent to verify email. I named the latter "verification_token".
So a user signs up, their 'is_active' field is set to False until they click on the link received in their email. Sendgrid email API was used to send the email. The email is only sent if the patient_serializer data is stored in the db.
When the user clicks on the link received through email, their 'is_active' field is set to True, and they can now log in.
Also, the actual sendgrid API key is a part of the code, I removed it here.
My models.py file:
from django.db import models
from django.contrib.auth.models import User
class VerificatonToken(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='token')
verification_token = models.CharField(
max_length=256, blank=True, null=True)
My serializers.py file:
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework.validators import UniqueValidator
from rest_framework_jwt.settings import api_settings
from .models import VerificatonToken
class TokenSerializer(serializers.ModelSerializer):
class Meta:
model = VerificatonToken
fields = ('user', 'verification_token',)
class PatientSerializer(serializers.ModelSerializer):
token = serializers.SerializerMethodField()
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
username = serializers.CharField(
required=True,
max_length=32,
validators=[UniqueValidator(queryset=User.objects.all())]
)
first_name = serializers.CharField(
required=True,
max_length=32
)
last_name = serializers.CharField(
required=True,
max_length=32
)
# DOB = serializers.DateField(
# required=True
# )
# gender = serializers.CharField(
# required=True
# )
# address = serializers.CharField(
# required=True,
# max_length=60
# )
# contactNo = serializers.IntegerField(
# required=True,
# max_length=11
# )
password = serializers.CharField(
required=True,
min_length=8,
write_only=True
)
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
def get_token(self, obj):
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(obj)
token = jwt_encode_handler(payload)
return token
class Meta:
model = User
fields = (
'token',
'username',
'password',
'first_name',
'last_name',
'email',
'is_active',
# 'DOB',
# 'address',
# 'contactNo',
'id'
)
My views.py file:
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import PatientSerializer, TokenSerializer
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
import uuid
import hashlib
from .models import VerificatonToken
from rest_framework.permissions import AllowAny
class TestView(APIView):
def get(self, request, format=None):
print("API called")
return Response("You did it!", status=200)
# Registering User & Sending Email Confirmation
class RegisterView(APIView):
permission_classes = [AllowAny]
def post(self, request, format=None):
print("Registering User")
user_data = request.data
user_data['is_active'] = False
print(request.data)
patient_serializer = PatientSerializer(data=user_data)
if patient_serializer.is_valid(raise_exception=False):
patient_serializer.save()
salt = uuid.uuid4().hex
hash_object = hashlib.sha256(
salt.encode() + str(patient_serializer.data['id']).encode())
verification_token = hash_object.hexdigest() + ':' + salt
verification_token_serializer = TokenSerializer(
data={'user': patient_serializer['id'], 'token': verification_token})
if verification_token_serializer.is_valid(raise_exception=False):
verification_token_serializer.save()
message = Mail(
from_email='allinonehealthapp22#gmail.com',
to_emails=user_data['email'],
subject='Please Confirm Your Email Address',
html_content=f"Hi {user_data['username']}!\
<br><br>\
Welcome to All-in-one Health App. To finish signing up, please click \
<a href='http://localhost:8000/api/v1.0/user/verify-patient/{verification_token}'>HERE</a>\
")
try:
sg = SendGridAPIClient(
'<Sendgrid API KEY>')
response = sg.send(message)
print(response)
return Response(patient_serializer.data, status=200)
except Exception as e:
print('ERROR', e)
else:
print(patient_serializer.errors,
verification_token_serializer.errors)
return Response({'msg': 'error'}, status=403)
# RegisterPatientView() provides customized link to VerifyPatientView() in email to patient
# compares the token provided to verify the email
class PatientVerificationView(APIView):
def get(self, request, pk, format=None):
print("Verifying Patient", pk)
tokenObj = VerificatonToken.objects.filter(token=pk).first()
user = User.objects.filter(id=tokenObj.user.id).first()
if user:
patient_serializer = PatientSerializer(
user, data={'is_active': True})
if patient_serializer.is_valid(raise_exception=False):
patient_serializer.save()
return Response('Patient email verified', status=200)
return Response(status=404)
class PatientLoginView(APIView):
# convert user token to user data
def get(self, request, format=None):
if request.user.is_authenticated == False or request.user.is_active == False:
return Response('Invalid credentials', status=403)
user = PatientSerializer(request.user)
print(user.data)
return Response("Testing", status=200)
# authenticate user data
def post(self, request, format=None):
print("Login class")
# Looking for user's email or username
user_obj = User.objects.filter(email=request.data['username']).first(
) or User.objects.filter(username=request.data['username']).first()
# if user is valid, returns user data in credentials
if user_obj is not None:
credentials = {
'username': user_obj.username,
'password': request.data['password']
}
user = authenticate(**credentials)
# if user is valid and active, logs user in
if user and user.is_active:
user_serializer = PatientSerializer(user)
print("Login successful")
return Response(user_serializer.data, status=200)
return Response("Invalid credentials", status=403)
Postman request:
Command line:
EDIT: My TokenSerializer is basically the one giving an error. The Patiet_Serializer is working fine because the user info goes into the database with the 'in_active' field set to false. I can't figure out how to fix it.

Django Rest Framework - Post more info User from Foreign Key

I am new to Django Rest Framework and checked some tutorials. Now I am trying to create my own user profile with more fields like: company name, phone, ....I created OneToOneField (one-to-one relationship) table with more info for my extend user. Now i want to create new user with all fields in post method, but i got error. How can i fix this?
models.py
class MoreInfo(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
compName = models.CharField(max_length=100)
title = models.CharField(null=True,max_length=128)
birthday = models.DateField(null=True, blank=True)
phone = models.CharField(max_length=20,blank=True)
api/serializer.py
class MoreInforSerializer(serializers.ModelSerializer):
class Meta:
model = MoreInfo
fields = '__all__'
class CreateUserSerializer(serializers.ModelSerializer):
moreInfoUser = MoreInforSerializer()
class Meta:
model = User
fields = '__all__'
extra_kwargs = {'password':{'write_only':True}}
def create(self,validated_data):
user = User.objects.create(
email=validated_data['email'],
username = validated_data['username'],
password = make_password(validated_data['password'])
)
info_data = validated_data.pop('moreInfoUser')
moreInfo = MoreInfo.objects.create(
user = user,
compName = info_data['compName'],
title = info_data['title'],
birthday = info_data['birthday'],
phone = info_data['phone']
)
# user.save()
return user
views.py
class ListCreateUser(ListCreateAPIView):
serializer_class = CreateUserSerializer
def post(self, request, *args, **kwargs):
serializer = CreateUserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return JsonResponse({
'message': 'Create a new Info successful!'
}, status=status.HTTP_201_CREATED)
return JsonResponse({
'message': 'Create a new Info unsuccessful!'
}, status=status.HTTP_400_BAD_REQUEST)
urls.py
path('createUser',views.ListCreateUser.as_view()),
POST:
{
"username":"user5",
"password":"12345aA#",
"email":"user5#gmail.com",
"compName":"A",
"title":"test",
"birthday":"1997-05-04",
"phone":"01234567"
}
Table for create User
enter image description here
Errors:
Can't create new User
Bad Request: /createUser
"POST /createUser HTTP/1.1" 400 46
You have to upload moreInfoUser also because you set that in the serializer.
{
"username":"user5",
"password":"12345aA#",
"email":"user5#gmail.com",
"compName":"A",
"title":"test",
"birthday":"1997-05-04",
"phone":"01234567",
"moreInfoUser": {
"compName": "...",
"title": "...",
"birthday": "...",
"phone": "..."
}
}

Use email as authentication field and add email verification on django custom user model

I have this custom user model on my Django project. I want to make the email as authentication field instead of the username. Also, I want to perform an email verification.
models.py
class es_user(models.Model):
user = models.OneToOneField(User,related_name='es_user', on_delete=models.CASCADE),
is_activated = models.BooleanField(default=False)
def get_absolute_url(self):
return reverse('user_detail', kwargs={'id': self.pk })
view.py
def signup(request):
signup_form_instance = SignUpForm()
if request.method == "POST":
signup_form_instance = SignUpForm(request.POST)
if signup_form_instance.is_valid():
signup_form_instance2 = signup_form_instance.save(commit = False)
username = signup_form_instance2.username
password = signup_form_instance2.password
signup_form_instance2.password = make_password(signup_form_instance.cleaned_data['password'])
signup_form_instance2.save()
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request,user)
active_user = request.user
es_user_instance = es_user.objects.create(user= active_user)
# return index(request)
return redirect('index')
# return user_detail(request)#successful signup redirect or return
# return redirect('user_detail',id = [str(request.user.id) ])#kwargs={'id': request.user.id })
else:
print("SIGN UP FORM INVALID")
return render(request,'signup.html',{'signup_form':signup_form_instance})
forms.py
class SignUpForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'email', 'password')
# for adding bootstrap classes & placeholders
widgets = {
'username': TextInput(attrs={
'class':'form-control',
'placeholder': 'Username *'}),
'email': EmailInput(attrs={
'class':'form-control',
'placeholder': 'Your Email *'}),
'password': PasswordInput(attrs={
'class':'form-control',
'placeholder': 'Your Password *'}),
}
help_texts = {
'username': None,
}
# to remove labels in form
labels = {
'username': (''),
'email':(''),
'password':(''),
}
My project is near completion so I cannot change my user model anymore or even change its name. So is there a way I can add email verification and using email instead of username for authentication without changing my user model.
I've seen a solution for a similar problem in this post . But I cannot use it since I use my custom user model es_user. is there a way in which I can edit it for my problem
To use Email as authentication, you have to use make new python file Backend.py and inside it write
class AuthenticationBackend(backends.ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
usermodel = get_user_model()
print(usermodel)
try:
user = usermodel.objects.get(Q(username__iexact=username) | Q(
email__iexact=username))
if user.check_password(password):
return user
except user.DoesNotExist:
pass
and add this AuthenticationBackend in settings.py as
AUTHENTICATION_BACKENDS = (
'users.backend.AuthenticationBackend',
'django.contrib.auth.backends.ModelBackend',
)
This will let you authenticate user with both username and password.
For second part of your question,Please follow:
Django Custom User Email Account Verification

django form validation -validation error custom message not work

This is a Django rest project
I'm creating a user registration form .. I wrote a validation for username like below but my custom validation error is not showing and it shows a default message like below... how can I fix this?
My other question is: Whats the difference between def validate_username and def clean_username
Thank You
This is the default Django message:
"{
"username": [
"A user with that username already exists."
]
}
"
This is my view:
class UserRegisterApiView(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserRegisterSerializer
queryset = User.objects.all()
This is my serilizer:
class UserRegisterSerializer(ModelSerializer):
#email2 = EmailField(label='confirm email')
email = EmailField(label='email', )
class Meta:
model = User
fields = [
'username',
'first_name',
'email',
'password',
]
extra_kwargs = {
"password": {
"write_only": True
}
}
# check if the user name is taken
def validate_username(self, value):
username = value
qs = User.objects.filter(username=username)
if qs.exists():
raise ValidationError("این نام کاربری آزاد نمیباشد")
return value
The uniquire message is come from models as:
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
In your case,you need to rewrite your username's UniqueValidator in serializer.
class UserRegisterSerializer(ModelSerializer):
def __init__(self, *args, **kwargs):
super(UserRegisterSerializer, self).__init__(*args, **kwargs)
for validator in self.fields['username'].validators:
from rest_framework.validators import UniqueValidator
if type(validator) == UniqueValidator:
validator.message = 'این نام کاربری آزاد نمیباشد'

Categories