How does Django rest framework verify the secondary password? - python

I'm not using auth, I've added a re_password field to my serializer, I think it only does a consistency check with the password field when a POST request comes in.
But the problem is that if re_password and password are write_only, then PUT and PATCH requests must also pass in these 2 fields.
I guess the consistency validation of re_password and password is reasonable for user registration, but it is not so necessary for updating user information.
What can I do to make re_password and password only required for POST requests?
POST: i need password and re_password field register new user account
PUT/PATCH: i don't need password and re_password as they are not suitable for updating user info
class UserSerializer(serializers.ModelSerializer):
re_password = serializers.CharField(write_only=True, min_length=6, max_length=20, error_messages={
"min_length": "Password at least 6 digits",
"max_length": "Password up to 20 characters",
})
class Meta:
exclude = ("is_delete",)
model = models.User
extra_kwargs = {**CommonSerializer.extra_kwargs, **{
"password": {
"write_only": True,
"min_length": 6,
"max_length": 20,
"error_messages": {
"min_length": "Password at least 6 digits",
"max_length": "Password up to 20 characters",
}
},
}}
def validate_password(self, data):
return hashlib.md5(data.encode("utf-8")).hexdigest()
def validate_re_password(self, data):
return hashlib.md5(data.encode("utf-8")).hexdigest()
def validate(self, validate_data):
if validate_data['password'] != validate_data.pop('re_password'):
raise exceptions.AuthenticationFailed("password not match")
return validate_data
def create(self, validate_data):
instance = models.User.objects.create(**validate_data)
return instance
def update(self, instance, validate_data):
password = validate_data.get("password")
validate_data["password"] = hashlib.md5(
password.encode("utf-8")).hexdigest()
for key, val in validate_data.items():
setattr(instance, key, val)
instance.save()
return instance

Related

I want to assign value of logged in user's user_company to the user_company field of the newly created user

When a user creates a user record for a client, the new client should should have the current logged in user's User.user_company value.
In the problem here, I want to assign the value of the logged in user's User.user_company into the new user's clientuser.user_company when save() is called in the view.
here is the serializer below with the clientuser object.
class ClientSerializers(serializers.ModelSerializer):
password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True)
client_name = serializers.CharField(style={'input_type' : 'text'}, required=True)
class Meta:
model = User
fields = ['email', 'username', 'password', 'password2', 'user_type', 'client_name']
extra_kwargs = {
'password': {'write_only': True}, #dont want anyone to see the password
'user_type': {'read_only': True},
}
def save(self):
clientuser = User(
#creating a user record. it will record company fk
email=self.validated_data['email'],
username=self.validated_data['username'],
user_type = 3,
first_name = self.validated_data['client_name'])
#validating the password
password = self.validated_data['password']
password2 = self.validated_data['password2']
if password != password2: #trying to match passwords.
raise serializers.ValidationError({'password': 'Passwords must match.'})
clientuser.set_password(password) #setting the password
clientuser.save() #saving the user
return clientuser
I've tried using
cur_usr = User()
param = 'user_company'
usr_comp = getattr(u, param)
print(f'usr_comp is {usr_comp})
print statement prints usr_comp is None in the terminal
I've also tried using
curr_User_Company = User.user_company
user.user_company = curr_User_Company
it returns the following line in the terminal
raise ValueError(
ValueError: Cannot assign "<django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x04B05F28>": "User.user_company" must be a "Company" instance.
Here is my user model
class User(AbstractUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
user_type_data = ((1,"sysAdmin"),(2,"CompanyAdmin"), (3,"Client"), (4,"Employee"))
user_type = models.IntegerField(choices=user_type_data, default=2)
user_company = models.ForeignKey('Company', on_delete=models.CASCADE, null=True, blank=True)
#if user is CompAdmin then company is the company he belongs to
#if user is Client then company is the company he is serviced by
#if user is Employee then company is the company he works for
#if user is sysAdmin then company is null
Here is my view
#csrf_exempt
#permission_classes([IsAuthenticated])
def ClientApi(request):
if request.method == 'POST':
data = JSONParser().parse(request)
serializer = ClientSerializers(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
I'm not sure if this is necessary, but if it is, here is a sample of the data i tried to pass through Postman
{
"email":"Maximillian#amgracing.com",
"username":"Maximillian",
"password":"AmgRacingBetterThanRedBull",
"password2":"AmgRacingBetterThanRedBull",
"client_name" : "MaximillianRacing"
}
Maybe you should used this:
from django.contrib.auth import get_user
and this:
clientuser = User(
#creating a user record. it will record company fk
email=self.validated_data['email'],
username=self.validated_data['username'],
///
user_company = get_user(self.request).user_company /// this line get you user_company from logged user
Finally figured it out. I added current_user parameter to the save() function and assigned the user_company to the value of current_user.user_company
def save(self, current_user):
usr_comp = current_user.user_company
clientUser = User(///
user_company = usr_comp)
In the ClientApi view, here's what I did
serializer.save(current_user = request.user)

Django rest framework how to change: "This field may not be blank." error message

When I am trying to create a new user model, I am getting the following validation error response:
HTTP 400 Bad Request
Allow: POST, OPTIONS
Content-Type: application/JSON
Vary: Accept
{
"phone": [
"This field may not be blank."
],
"name": [
"This field may not be blank."
]
}
What I want to do is how to change the error message to a custom one.
Here is my user serializer code:
from rest_framework import serializers
from django.contrib.auth import get_user_model
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = '__all__'
read_only_fields = ('last_login',)
I already override the validate method but that didn't work neither using this solution:
extra_kwargs = {"phone": {"error_messages": {"required": "A valid phone number is required"}}}
views.py:
class UserList(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
(Custom user) User.py:
class User(AbstractBaseUser):
"""
Custom user model based on phone number as the only identifier
"""
phone = models.CharField(max_length=10, unique=True)
name = models.CharField(max_length=50, blank=False)
password = None
USERNAME_FIELD = 'phone'
objects = UserManager()
def __str__(self):
return self.name
User manager.py:
class UserManager(BaseUserManager):
"""
Custom user model manager where the phone is the unique identifier
"""
def create_user(self, phone, name, **extra_fields):
"""
Create and save a User using the given phone number and country
"""
if not phone:
raise ValueError(_('A valid phone must be provided.'))
if not name:
raise ValueError(_('Your name is required'))
user = self.model(phone=phone, name=name, **extra_fields)
user.set_unusable_password()
user.save(using=self._db)
def create_superuser(self, phone, name, **extra_fields):
"""
Create and save a SuperUser with the given phone and country.
"""
extra_fields.setdefault('is_verified', True)
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(phone, name, **extra_fields)
I know it's a silly problem but I couldn't solve it.
Thanks for your appreciated help.
blank is the key for the error messages dict that is applicable for blank fields so:
extra_kwargs = {'phone': {'error_messages': {'blank': 'New blank error message'}}}
You can validate empty fields in your views.py file
def post(self, request):
phone = request.data.get('phone', False)
name = request.data.get('name', False)
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({
'status': True,
'message': 'User Register Successfully',
'data': serializer.data,
}, status = status.HTTP_201_CREATED)
elif phone == '':
return Response({
'status': False,
'message': 'phone field is not defined!',
}, status = status.HTTP_400_BAD_REQUEST)
elif name == '':
return Response({
'status': False,
'message': 'name field is not defined!',
}, status = status.HTTP_400_BAD_REQUEST)
else :
return Response({
'status': False,
'message': 'Error! Something went wrong!',
}, status = status.HTTP_400_BAD_REQUEST)

UNIQUE constraint failed on Posting form

I am trying to post a simle form to create a user. but whenever i try to save the form data it always gives me UNIQUE constraint failed error even if i pass the new mobile number that does not exist on database.
ERROR IS: UNIQUE constraint failed: core_user.mobile_no
models.py
Manager Class is:
class UserManager(BaseUserManager):
def create_user(self, username, password=None, **extra_fields):
"""Creates and saves a new user"""
if not password:
raise ValueError("User must have a password")
if not username:
raise ValueError("User must have an username")
user = self.model(username=username, **extra_fields)
user.set_password(password)
user.save(using=self.db)
return user
def create_staff_user(self, username, password=None, **kwargs):
user = self.create_user(username, password, is_staff=True, **kwargs)
return user
def create_super_user(self, username, password=None):
user = self.create_user(self, username=username, password=password, is_staff=True, is_super_user=True)
return user
Model class is:
class User(AbstractBaseUser):
user_types = (
("staff", "Staff"),
("super_user", "Super User"),
)
first_name = models.CharField(max_length=100)
middle_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
username = models.CharField(max_length=100, unique=True)
email = models.EmailField()
mobile_no = models.CharField(max_length=10, unique=True)
is_active = models.BooleanField(default=True) # can login
is_staff = models.BooleanField(default=False) # staff user
is_super_user = models.BooleanField(default=False) # super user
created_date = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'username'
objects = UserManager()
# USERNAME_FIELD and password are required by default
REQUIRED_FIELDS = [] # e.g full_name
def __str__(self):
return self.username
Views.py
class UserCreationView(CreateView):
template_name = "form.html"
form_class = UserCreationForm
success_url = "/"
def form_valid(self, form):
username = form.cleaned_data['username']
password = form.cleaned_data['password']
first_name = form.cleaned_data['first_name']
middle_name = form.cleaned_data['middle_name']
last_name = form.cleaned_data['last_name']
mobile_no = form.cleaned_data['mobile_no']
email = form.cleaned_data['email']
user_type = form.cleaned_data['user_type']
user_data = {
"first_name": first_name,
"middle_name": middle_name,
"last_name": last_name,
"mobile_no": mobile_no,
"email": email
}
if user_type == 'super-user':
user = User.objects.create_super_user(username, password, **user_data)
else:
user = User.objects.create_staff_user(username, password, **user_data)
form.instance.user = user
form.instance.is_active = True
form.save()
return super().form_valid(form)
Questions are:-
As far as i tried to debug, it could be the reason that create_staff_user and create_super_user functions have already created a row in database and now form.save() is also trying to insert the row again. (not sure)
do i need to do form.save() as i found that super().form_valid(form) also have implemented form saving function within it ?
In your view, you should only call save once, but you are calling it twice
form.save() # this line saves it to the database
super().form_valid(form) # and this line does that too
so after calling form.save() return response.
update, your code to
class UserCreationView(CreateView):
template_name = "form.html"
form_class = UserCreationForm
success_url = "/"
def form_valid(self, form):
username = form.cleaned_data['username']
password = form.cleaned_data['password']
first_name = form.cleaned_data['first_name']
middle_name = form.cleaned_data['middle_name']
last_name = form.cleaned_data['last_name']
mobile_no = form.cleaned_data['mobile_no']
email = form.cleaned_data['email']
user_type = form.cleaned_data['user_type']
user_data = {
"first_name": first_name,
"middle_name": middle_name,
"last_name": last_name,
"mobile_no": mobile_no,
"email": email
}
if user_type == 'super-user':
user = User.objects.create_super_user(username, password, **user_data)
else:
user = User.objects.create_staff_user(username, password, **user_data)
form.instance.user = user
form.instance.is_active = True
form.save()
return HttpResponseRedirect(self.get_success_url())
mobile number should be unique for every user
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('is_staff', 'is_superuser',)
def clean_mobile_no(self):
mobile_number = self.cleaned_data.get('mobile_no')
user = User.objects.filter(mobile_no=mobile_number)
if user:
raise forms.ValidationError(
"mobile no is taken"
)
return mobile_number
Well, as for your first question, it could be the problem. I guess before you created a custom User model, you makes migrations and pushed them to your database. So, that is the part where Django also creates its own User model, with all the available columns and attributes. I would suggest to DELETE your currently Users table, and run the makemigrations and migrate again.
As for your second question, the best practice and advice that I could give you is to first add an if clause, to check if the form data are valid, and then save the form and post the data. Although my advice does not really relate to your question, the point is to always validate the form's data and after you retrieve them, save the form (post the data).
In the documentation it says that the FormView class on success will redirect the user and on error, it will redisplay the form. However, the CreateView will only display the errors and save the object, it will not redirect to anything. Although with the CreateView you can automatically save the form and its data, it will not redirect the user. I suggest you using the FormView class that will show if there any errors and will redirect the user on success, but be careful and save the form data at the end of the POST function.
I hope that helps! Please let me know if there is anything else I can help you with.

DRF: Custom field error message

While creating simple login api using DRF, I encountered a problem. Two field email and password are required to login. If the fields are left blank following json message is shown:
{
"email": [
"This field may not be blank."
],
"password": [
"This field may not be blank."
]
}
But I would like to customise the error message, say to something like,
{
"email": [
"Email field may not be blank."
],
"password": [
"Password field may not be blank."
]
}
I tried the something like the following in validate() in serializers.py :
if email is None:
raise serializers.ValidationError(
'An email address is required to log in.'
)
But it is not getting override, I'm not sure about the reason.
Edit
I implemented with #dima answer it still not work. What am I doing wrong?, now my serializer looks like:
class LoginSerializer(serializers.Serializer):
email = serializers.CharField(max_length=255, required=True, error_messages={"required": "Email field may not be blank."})
username = serializers.CharField(max_length=255, read_only=True)
password = serializers.CharField(max_length=128, write_only=True, required=True,
error_messages={"required": "Password field may not be blank."})
token = serializers.CharField(max_length=255, read_only=True)
def validate(self, data):
# The `validate` method is where we make sure that the current
# instance of `LoginSerializer` has "valid". In the case of logging a
# user in, this means validating that they've provided an email
# and password and that this combination matches one of the users in
# our database.
email = data.get('email', None)
password = data.get('password', None)
user = authenticate(username=email, password=password)
# If no user was found matching this email/password combination then
# `authenticate` will return `None`. Raise an exception in this case.
if user is None:
raise serializers.ValidationError(
'A user with this email and password was not found.'
)
# Django provides a flag on our `User` model called `is_active`. The
# purpose of this flag is to tell us whether the user has been banned
# or deactivated. This will almost never be the case, but
# it is worth checking. Raise an exception in this case.
if not user.is_active:
raise serializers.ValidationError(
'This user has been deactivated.'
)
# The `validate` method should return a dictionary of validated data.
# This is the data that is passed to the `create` and `update` methods
# that we will see later on.
return {
'email': user.email,
'username': user.username,
'token': user.token
}
views.py
class AuthLogin(APIView):
''' Manual implementation of login method '''
permission_classes = (AllowAny,)
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = LoginSerializer(data=data)
if serializer.is_valid(raise_exception=True):
new_data = serializer.data
return Response(new_data)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
You can set error_messages attribute for fields you want to override message. In your case:
class LoginSerializer(serializers.Serializer):
email = serializers.CharField(max_length=255, required=True, error_messages={"required": "Email field may not be blank."})
username = serializers.CharField(max_length=255, read_only=True)
password = serializers.CharField(max_length=128, write_only=True, required=True, error_messages={"required": "Password field may not be blank."})
token = serializers.CharField(max_length=255, read_only=True)
For ModelSerializers you can do this using extra_kwargs property in Meta class.
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ('email', 'password')
extra_kwargs = {
'password': {"error_messages": {"required": "Password field may not be blank."}},
'email': {"error_messages": {"required": "Email field may not be blank."}},
}
you need field-level-validation, try it:
def validate_email(self, value):
# ^^^^^^
if not value:
raise serializers.ValidationError(
'An email address is required to log in.'
)
return value

user authentication by email and password from database in django

i am a beginner in django. I am working on a project in which customer and companies have their own accounts the models.py is:
class Company_SignUp(models.Model):
comp_name = models.CharField(_('Company Name'), max_length=30)
email = models.EmailField(_('E-mail'), unique=True)
raise forms.ValidationError("This email address already exists.")
password1 = models.CharField(_('Password'), max_length=128)
password2 = models.CharField(_('Confirm Password'), max_length=30)
def __unicode__(self):
return smart_unicode(self.comp_name)
class Customer_SignUp(models.Model):
cust_name = models.CharField(_('Customer Name'), max_length=30)
email = models.EmailField(_('E-mail'), unique=True)
password1 = models.CharField(_('Password'), max_length=128)
password2 = models.CharField(_('Confirm Password'), max_length=30)
def __unicode__(self):
return smart_unicode(self.cust_name)
my forms.py is:
class Company(forms.ModelForm):
class Meta:
model = Company_SignUp
widgets = {
'password1': forms.PasswordInput(),
'password2': forms.PasswordInput(),
}
fields = ('email','password1','password2','comp_name')
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields did not match."))
elif len(self.cleaned_data['password1']) < 8:
raise forms.ValidationError(_("The password must be 8 characters long."))
return self.cleaned_data
class Customer(forms.ModelForm):
class Meta:
model = Customer_SignUp
widgets = {
'password1': forms.PasswordInput(),
'password2': forms.PasswordInput(),
}
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields did not match."))
elif len(self.cleaned_data['password1']) < 8:
raise forms.ValidationError(_("The password must be 8 characters long."))
return self.cleaned_data
how will i authenticate a company or a customer using their email and passwords.
i tried authenticate() but it doesn't work.
also how will i check during registration , the email address given already exists
ok now i created a backend which is:
from django.contrib.auth.models import User
from prmanager.models import Company_SignUp, Customer_SignUp
class EmailBackend(object):
def authenticate(self, username=None, password=None):
try:
o = Company_SignUp.objects.get(email=username, password1=password)
except Company_SignUp.DoesNotExist:
try:
o = Customer_SignUp.objects.get(email=username, password1=password)
except Customer_SignUp.DoesNotExist:
return None
return User.objects.get(email=o.email)
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
But now i cannot login to admin page using superuser credentials. what should i do
Models
Consider extending the User model from django.contrib.auth.models like so. If you don't want to do this, skip to the next section (Authentication).
from django.contrib.auth.models import User
class Customer(User):
# extra fields
The User model has common fields such as username,first_name,last_name,email, etc. You only need to specify any extra attributes your model may have.
The Django docs suggest extending AbstractBaseUser, which may work for you too.
Read more here: https://docs.djangoproject.com/en/1.7/topics/auth/customizing/#extending-the-existing-user-model
Authentication
For email-based authentication, you need to write your own authentication backend: https://docs.djangoproject.com/en/1.7/topics/auth/customizing/#writing-an-authentication-backend
Once you have that in place, you need to accept email / password and authenticate using authenticate and login.
from django.contrib.auth import authenticate, login
def my_view(request):
email = request.POST['email']
password = request.POST['password']
user = authenticate(email=email, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
else:
# Return a 'disabled account' error message
else:
# Return an 'invalid login' error message.
The above snippet is from the docs and I have modified it to fit your use-case.
More about authentication in Django: https://docs.djangoproject.com/en/1.7/topics/auth/default/#how-to-log-a-user-in

Categories

Resources