I'm trying to create a user update endpoint that only updates select fields of a user. My currently implementation is to save the model with ModelForm's save method but I've also tried with the base model save method.
My request body doesn't contain a password field and I don't have a password field in my UserForm, however I'm unable to maintain the current session or logout and log back in with the current user after executing an update. I'm nearly positive this is because Django is changing my password somewhere during the update process.
Other pertinent information: I'm using Django (not Django Rest Framework). I have a custom user model. I know I can solve this issue by using serializers with DRF but it seems hacky to use that for this issue alone.
My endpoint looks like this:
def saveAccountData (request):
resp = {}
if request.method == 'POST':
form = UserForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
resp['result'] = 'User updated'
return HttpResponse(
json.dumps(resp),
status=200
)
else:
return HttpResponse(
json.dumps(form.errors),
status=422,
)
My Form looks like this:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['email', 'name', 'is_active', 'is_admin']
def save(self, commit=True):
user = super(UserForm, self).save(commit=False)
user.name = self.cleaned_data['name']
if commit:
user.save()
return user
User model:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.conf import settings
import logging
class UserManager(BaseUserManager):
def create_user(self, email, name, password=None):
# executes if email is zero (false) or an empty container (equivalent to None)
logger = logging.getLogger(__name__)
logger.warning('Create user: ' + password)
if not email:
raise ValueError("Please provide an email address")
if not password:
raise ValueError("Please enter a password")
if not name:
raise ValueError("Please enter your name")
# normalize email address (convert to lower case so all email addresses are standardized)
user = self.model(
email = self.normalize_email(email),
name=name,
)
# the set_password method encrypts the password
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, name):
user = self.create_user(
email,
password=password,
name=name)
user.is_superuser = True
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=50, unique=True)
password = models.CharField(max_length=100)
name = models.CharField(max_length=100)
phone = models.CharField(max_length=20)
city = models.CharField(max_length=50)
state = models.CharField(max_length=20)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = UserManager()
# we must specify a username field
USERNAME_FIELD = 'email'
# required fields are used for create_superuser
REQUIRED_FIELDS = ['name']
def get_full_name(self):
return self.name
def get_short_name(self):
return self.name
def __str__(self):
# django uses this method when it needs to convert an object to a string
return self.email
I created a UpdateAccountForm(forms.ModelForm),
and overwrite save() like:
```
class Meta:
model = User
fields = ['username', 'groups', 'is_active']
def save(self, commit=True):
┆ user = super(UpdateAccountForm, self).save(commit=False)
┆ password = self.cleaned_data["password"]
┆ if password:
┆ ┆ user.set_password(password)
┆ if commit:
┆ ┆ user.save()
┆ return user
```
Related
I am using DRF and I have these pieces of code as models, register view and serializer
But anytime I signup a user the password does not hashed and I can't see to figure out why.
models.py
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
if not email:
raise ValueError("Users must have an email")
email = self.normalize_email(email).lower()
user = self.model(email=email, **kwargs)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
if not password:
raise ValueError("Password is required")
user = self.create_user(email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
role = models.CharField(max_length=255)
department = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_verified = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["first_name", "last_name", "role", "department"]
def __str__(self):
return self.email
serializers.py
class RegisterSerializer(serializers.ModelSerializer):
email = serializers.CharField(max_length=255)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField(max_length=255)
last_name = serializers.CharField(max_length=255)
role = serializers.CharField(max_length=255)
department = serializers.CharField(max_length=255)
class Meta:
model = User
fields = ["email", "password", "first_name", "last_name", "role", "department"]
def create(self, validated_data):
return User.objects.create(**validated_data)
def validate_email(self, value):
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("This email already exists!")
return value
views.py
class RegisterView(APIView):
serializer_class = RegisterSerializer
def post(self, request, *args):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
user = User.objects.get(email=user_data.get("email"))
return Response(user_data, status=status.HTTP_201_CREATED)
for some reason, which I don't know anytime a user is created the password is save in clear text. It does not hash the passwords. The superuser's password is however hashed because I created it with the command line but the api doesn't hash the password. I some help to fix this.
Instead of passing plain password you should use make_password method provided by django.
from django.contrib.auth.hashers import make_password
make_password(password, salt=None, hasher='default')
Creates a hashed password in the format used by this application. It takes one mandatory argument: the password in plain-text (string or bytes). Optionally, you can provide a salt and a hashing algorithm to use, if you don’t want to use the defaults (first entry of PASSWORD_HASHERS setting). See Included hashers for the algorithm name of each hasher. If the password argument is None, an unusable password is returned (one that will never be accepted by check_password()).
You can try something like this
hashed_pass = make_password(your_password_here, salt=None, hasher='default')
user = self.create_user(email, hashed_pass)
Source: https://docs.djangoproject.com/en/4.1/topics/auth/passwords/
Try this:
def create(self, validated_data):
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
return user
I found why it wasn't working. In the create method in the RegisterSerializer I did a return User.objects.create(**validated_data)
which saves on the request data in the database, including the password without hashing it first. The correct way is to be calling the create_user method instead, like so return User.objects.create_user(**validated_data) since that method has the set_password mechanism to hash the password.
I have a problem with simple user model.I wrote a test for email normalize function and in response i've got a " django.db.utils.IntegrityError: duplicate key value violates unique constraint "core_user_username_key"
DETAIL: Key (username)=() already exists. " When I added a "user.delete()"after "self.assertEqual(user.email, expected)" all test passed.What is wrong ? Is that test creates users with the same username field ?
Models.py
class UserManager(BaseUserManager):
"""Manager for user"""
def create_user(self, email, password=None, **extra_fields):
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
class User(AbstractUser, PermissionsMixin):
"""User model"""
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
REQUIRED_FIELDS = []
USERNAME_FIELD = 'email'
test.py
class ModelTest(TestCase):
def test_create_user_with_email(self):
"""Testing creating user with email address"""
email = 'testaddress#example.com'
password = 'testpassword1234'
user = get_user_model().objects.create_user(
email=email,
password=password,
)
self.assertEqual(user.email, email)
self.assertTrue(user.check_password(password))
def test_user_email_normalized(self):
"""Testing email normalize function"""
test_emails = [
['test1#EXAMPLE.Com', 'test1#example.com'],
['TesT2#exaMple.com', 'TesT2#example.com'],
]
for email, expected in test_emails:
user = get_user_model().objects.create_user(email, 'password123')
self.assertEqual(user.email, expected)
user.delete()
It creates a user with the same username. You inherited this from the parent model, and did not remove the username field. You should specify this as None, so:
class User(AbstractUser, PermissionsMixin):
"""User model"""
username = None
# …
In the tests you thus do not have to delete the user: the system should be capable to register multiple users with each a different email.
I have a wishlist app with a custom user model. I added the user as the foreign key to each wishlist item. However, I am only able to add the foreign key by specifying a default user, even after deleting the existing db files and running makemigrations and migrate. The default user is associated with the wishlist item regardless of which user is logged in, and if I don't include a default user I get the "NOT NULL constraint failed: wishlist_wish.user_id" error.
How can I update the code so that the wishlist item is associated with the logged-in user creating the item?
A related question is whether I need to include a user id in my custom user model? I tried doing this as well and ran into the same problem. Thanks in advance.
Wish Model:
class Wish(models.Model):
name = models.TextField()
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.name
Wish View:
def add_wish(request):
user = request.user
wishes = Wish.objects.filter(user=request.user)
if request.method == 'POST':
form = WishForm(request.POST)
if form.is_valid():
form.save()
return redirect('home')
else:
form = WishForm()
context = {'form' : form, 'wishes' : wishes}
return render(request, 'wishlist/add_wish.html', context)
Wish Form:
class WishForm(ModelForm):
class Meta:
model = Wish
fields = ('name', )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class' : 'textarea', 'placeholder' : 'Enter wishlist item'})
Custom User (Account) Model:
class MyAccountManager(BaseUserManager):
def create_user(self, email, username, first_name, last_name, password=None):
if not email:
raise ValueError("Users must provide an email to create an account.")
if not first_name:
raise ValueError("Users must provide full name to create an account.")
if not last_name:
raise ValueError("Users must provide full name to create an account.")
user = self.model(
email = self.normalize_email(email),
username = username,
first_name = first_name,
last_name = last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, first_name, last_name, password):
user = self.create_user(
email = self.normalize_email(email),
username = username,
first_name = first_name,
last_name = last_name,
password = password
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Account(AbstractBaseUser):
email = models.EmailField(max_length=100, unique=True)
username = models.CharField(max_length=100, unique=False, blank=True, null=True)
date_joined = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
objects = MyAccountManager()
def __str__(self):
return self.first_name + " " + self.last_name
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
You simply need to provide a value for the .user for the Wish you are creating:
from django.contrib.auth.decorators import login_required
#login_required
def add_wish(request):
wishes = Wish.objects.filter(user=request.user)
if request.method == 'POST':
form = WishForm(request.POST)
if form.is_valid():
form.instance.user = requst.user
form.save()
return redirect('home')
else:
form = WishForm()
context = {'form' : form, 'wishes' : wishes}
return render(request, 'wishlist/add_wish.html', context)
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
I have this problem for a month.
I'm using abstractbasemodel and basemanagerto to create a login and signup API using rest framework.
However, when I create a user password, it is saved as raw data since I use set_password() method and custom model manager confuses me...
This is my code :
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id' ,'email' ,'name' ,'password')
extra_kwargs = {
'password':{
'write_only':'True',
'style': {'input_type': 'password'}
}
}
def create(self, validated_data):
user = UserProfile.people.create_user(
email = validated_data['email'],
name = validated_data['name'],
password = validated_data['password']
)
class UserProfileViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserProfileSerializer
queryset = models.UserProfile.people.all()
authentication_classes = (TokenAuthentication, )
permission_classes = (UpdateOwnProfile, )
filter_backends = (SearchFilter, )
search_fields = ('name', 'email')
class UserLoginApiView(ObtainAuthToken):
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
class UserProfileManager(BaseUserManager):
def create_user(self, email, name, password=None):
print("user model manager")
if not email:
raise ValueError('User Must Have an Email Address')
email = self.normalize_email(email)
user = self.model(email=email, name=name )
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, name, password):
user = self.create_user(email, name, password)
user.is_superuser = True
user.is_staff = True
user.save(using=self._db)
return user
class UserProfile(AbstractBaseUser,PermissionsMixin):
email = models.EmailField(max_length=255,unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
people = UserProfileManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name']
def get_full_name(self):
return self.name
def get_short_name(self):
return self.name
def __str__(self):
return self.email
class Profile(models.Model):
user = models.OneToOneField(UserProfile,on_delete=models.CASCADE,relat ed_name="Profile")
location = models.CharField(max_length=100,blank=True,null=True)
bio = models.CharField(max_length=100,blank=True,null=True)
creationDate = models.DateTimeField(auto_now_add=True)
follower = models.ManyToManyField(UserProfile,related_name="Following",blank=True)
class Meta:
verbose_name='Profile'
verbose_name_plural='Profiles'
I also defined auth user model in settings :
AUTH_USER_MODEL='profiles.UserProfile'
to make sure Django uses my custom user model.
I don't know whats wrong as there is no error and only superusers that are created in terminal using manage.py are saved with hashed password.
Users which are created with my viewsets are saved with raw password.
First, I named the model manager "objects" and now, its people but the create user method wont run at all.
You can use django's built in hasher to create hashed password. It can be applied in .create method. First import from django.contrib.auth.hashers import make_password and then modify .create() method,
def create(self, validated_data):
user = UserProfile.people.create_user(
email = validated_data['email'],
name = validated_data['name'],
password = make_password(validated_data['password']) # here
)
return user
Or
if you don't override the .create() method then add the following validate_password method in serializer,
The validate_password is ran, everytime a new object has to be created
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id' ,'email' ,'name' ,'password')
extra_kwargs = {
'password':{
'write_only':'True',
'style': {'input_type': 'password'}
}
}
def validate_password(self, value: str) -> str:
return make_password(value)
I'm using Django 1.7 and am trying to authenticate a user with email instead of the provided Django auth user.
This is my models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
class MyUserManager(BaseUserManager):
def create_user(self, email, password=None):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=MyUserManager.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
user = self.create_user(email,
password=password,
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
"""
Custom user class.
"""
email = models.EmailField('email address', unique=True, db_index=True)
joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
def __unicode__(self):
return self.email
and this is a snippet from my views.py
def auth_view(request):
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request, user)
return HttpResponseRedirect('/')
else:
return HttpResponseRedirect('/invalid/')
def register_user(request):
if request.method == 'POST':
form = MyRegistrationForm(request.POST)
if form.is_valid():
print "Form is valid"
form.save()
return HttpResponseRedirect('/register_success/')
args = {}
args.update(csrf(request))
args['form'] = MyRegistrationForm()
return render_to_response('register.html', args, context_instance=RequestContext(request))
and finally, my forms.py
from django import forms
from django.contrib.auth.models import User
class MyRegistrationForm(forms.ModelForm):
"""
Form for registering a new account.
"""
email = forms.EmailField(widget=forms.EmailInput,label="Email")
password1 = forms.CharField(widget=forms.PasswordInput,
label="Password")
password2 = forms.CharField(widget=forms.PasswordInput,
label="Password (again)")
class Meta:
model = User
fields = ['email', 'password1', 'password2']
def clean(self):
"""
Verifies that the values entered into the password fields match
NOTE: Errors here will appear in ``non_field_errors()`` because it applies to more than one field.
"""
cleaned_data = super(MyRegistrationForm, self).clean()
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("Passwords don't match. Please enter both fields again.")
return self.cleaned_data
def save(self, commit=True):
user = super(MyRegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
Whenever I try to register an account, I get an error 'NoneType' object has no attribute '_insert' from forms.py calling user.save and views.py calling form.save. I don't really know how to write the user.save, but I'd imagine that would fix both errors.
Can anyone help me?
look at forms.py imports
from django.contrib.auth.models import User
must import MyUser instead of that
same in
class Meta:
model = User
fields = ['email', 'password1', 'password2']
and add to MyUser class
objects = MyUserManage()
change to
class Meta:
model = MyUser
fields = ['email', 'password1', 'password2']
and settings.py must set:
AUTH_USER_MODEL = '<apppath>.MyUser'