Here i am simply using User model from django.contrib.auth.models import User and I have a custom userprofile model where user foreign key in that django built in User model , well i have created an email_address field which can manually update by a user, and there is an other email field which is built in inside django User model , I want to update that email field as userprofile email_address field update.
I am simply getting user object which username and try to get email of that user object but getting an error : 'QuerySet' object has no attribute 'email'
models.py
def save(self,*args, **kwargs):
user_obj = User.objects.filter(username=self.user.username)
print(user_obj,'user object')
print(user_obj.email,'email have a user')
email = self.email_address
user_obj.email = self.email_address
print(user_obj.email,'email have a user')
user_obj.save(user_obj.email)
super(UserProfile,self).save(*args, **kwargs)
user_obj is not a User object, it is a QuerySet of User objects.
You can retrieve a single User object with .get(…) [Django-doc]:
def save(self,*args, **kwargs):
user_obj = User.objects.get(username=self.user.username)
user_obj.email = self.email_address
user_obj.save()
super(UserProfile,self).save(*args, **kwargs)
But here you can actually simply use the self.user object:
def save(self,*args, **kwargs):
user = self.user
user.email = self.email_address
user.save()
super(UserProfile,self).save(*args, **kwargs)
That being said, I would advise not to store data twice. So in case the User object has an email address, there is no need to store this in the UserProfile model as well. You can easily access this with:
from django.conf import settings
class UserProfile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
#property
def email_address(self):
return self.user.email
This thus means that you access the email address of the related User object. This avoids data duplication, which will make it hard to keep data in sync. Especially since the .save() method will not run for example for bulk create/updates by the Django ORM.
Related
I know how to override UserCreationForm but it works only on users, not on admin registration.
Here is my case...
I have modified the default user model and it has now the field user_company which cannot be Null:
class User(AbstractUser):
user_company = models.ForeignKey("UserCompany", on_delete=models.CASCADE)
I have overriden the UserCreationForm:
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
class UserRegisterForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = get_user_model()
def save(self, commit=True):
user_company = UserCompany() ## create a new company and assign it to the new user
user_company.save()
user = super(UserRegisterForm, self).save(commit=False)
user.user_company_id = user_company.pk
if commit:
user.save()
return user
All this works fine for normal users. But when I try to python manage.py createsuperuser in the console, after entering the admins username and password, I get an error that
the field user_company cannot be Null
You're not creating a new UserCompany in the database, just an in memory object, replace
user_company = UserCompany() ## create a new company and assign it to the new user
with something like
user_company = UserCompany.objects.create()
I think it is best to move the creation of default UserCompany in the User's save function instead of having it in the form
class User(AbstractUser):
user_company = models.ForeignKey("UserCompany", on_delete=models.CASCADE)
def save(self, *args, **kwargs):
if getattr(self, "user_company", None) is None:
self.user_company = UserCompany.objects.create()
super(User, self).save(*args, **kwargs)
I have used django user model and extended it to add a phone field. I was able to use either username or email to login but cannot access phone number field from extended user model.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phonenumber = models.CharField(max_length=10)
backends.py
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
backend.py is what I used to implement login via username or email but I couldn't do it for phonenumber in extended user model.
There are two ways to solve your problem.
Extend the User model by subclassing it, and changing the default User model. I would recommend you go this route, since the Phone number is used for authentication.
Set related_name on Profile, and query the User model from that name.
For the first one, I recommend you check out the Django documentation on creating custom users.
There is also a pretty extensive tutorial on how to do this here.
If you want to go the easy route, you just need to set a reverse accessor on the Profile's phonenumber field.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phonenumber = models.CharField(max_length=10, related_name='profile')
You can then search users by their profile's phonenumbers:
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) | Q(profile__phonenumber__iexact=username)
if user.check_password(password):
return user
except user.DoesNotExist:
pass
Although this is not within the scope of your question, I think you should be aware of the following issues with your code:
Phone numbers are often more complex than a 10 character string. +31 6 12345678, is a valid phone number, but it's more than 10 characters. Check out this question for pointers on how to store and validate phone numbers.
If you use a field for authentication, make sure it's unique. The phonenumber field should actually be phonenumber = models.CharField(max_length=10, related_name='profile', unique=True)
Your usermodel.objects.get(...) query will return more than one user if there is a user with phone number '0123456789' and a different user with username '0123456789'. You should constrain usernames to not contain phone numbers, which is only possible by extending the default User class.
You can extend the AbstractUser and add the phonenumber on it.
class User(AbstractUser):
phonenumber = models.CharField(max_length=10)
USERNAME_FIELD = 'phonenumber'
Then you should specify the custom model as the default user model in settings.py
AUTH_USER_MODEL = 'app_name.User'
Customizing authentication in Django
I am using the Django REST Framework (DRF) to create an endpoint with which I can register new users. However, when I hit the creation endpoint with a POST, the new user is saved via a serializer, but the password is saved in cleartext in the database. The code for my serializer is as follows:
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['password', 'username', 'first_name', 'last_name', 'email']
read_only_fields = ['is_staff', 'is_superuser']
write_only_fields = ['password']
Please note that I am using the default User model from the Django auth package, and that I am very new to working with DRF! Additionally, I have found this question which provides a solution, but this appears to require two database interactions -- I do not believe that this is efficient, but that might be an incorrect assumption on my part.
The issue is DRF will simply set the field values onto the model. Therefore, the password is set on the password field, and saved in the database. But to properly set a password, you need to call the set_password() method, that will do the hashing.
There are several ways to do this, but the best way on rest framework v3 is to override the update() and create() methods on your Serializer.
class UserSerializer(serializers.ModelSerializer):
# <Your other UserSerializer stuff here>
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 update(self, instance, validated_data):
for attr, value in validated_data.items():
if attr == 'password':
instance.set_password(value)
else:
setattr(instance, attr, value)
instance.save()
return instance
Two things here:
we user self.Meta.model, so if the model is changed on the
serializer, it still works (as long as it has a set_password
method of course).
we iterate on validated_data items and not
the fields, to account for optionally excludeed fields.
Also, this version of create does not save M2M relations. Not needed in your example, but it could be added if required. You would need to pop those from the dict, save the model and set them afterwards.
FWIW, I thereby make all python code in this answer public domain worldwide. It is distributed without any warranty.
This worked for me.
class UserSerializer(serializers.ModelSerializer):
def create(self, *args, **kwargs):
user = super().create(*args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
def update(self, *args, **kwargs):
user = super().update(*args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
class Meta:
model = get_user_model()
fields = "__all__"
just override the create and update methods of the serializer:
def create(self, validated_data):
user = get_user_model(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, instance, validated_data):
for f in UserSerializer.Meta.fields + UserSerializer.Meta.write_only_fields:
set_attr(instance, f, validated_data[f])
instance.set_password(validated_data['password'])
instance.save()
return instance
Here is an alternative to accepted answer.
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(
email=validated_data['email'],
username=validated_data['username'],
password=validated_data['password'],
)
user.save()
return user
create_user function is defined in UserManager and it uses set_password(), we don't need to use it explicitly. I have found many answers and articles which suggest to use set_password but after trying many things I figured the above and it works with CustomUserManager too.
Suppose phone number and password is required to register a user. So our CustomUserManager will look something like this and CreateUserSerializer will handle this too with no changes.
class CustomUserManager(BaseUserManager):
def create_user(self, phone_number, password):
if not phone_number:
raise ValueError('Phone Number must be set')
user = self.model(phone_number=phone_number)
user.set_password(password)
user.save(using=self._db)
return user
I have created an Update Profile page. So the fields are: Email, First Name, Last Name.
In the validation, I'm trying to exclude logged user's email address, and also I'm filtering other user's email addresses. I think if you see the code you will understand what I'm talking about.
I read several questions here but couldn't find something. Some users had the same issue.
So with the code below, the issue I'm getting is:
type object 'User' has no attribute 'email'.
I tried many ways to get the current user's email address(before I save the form) but still nothing.
forms.py
class UpdateProfile(forms.ModelForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(required=False)
last_name = forms.CharField(required=False)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
def clean_email(self):
email = self.cleaned_data.get('email')
current_user_email = User.email
if User.objects.filter(email__iexact=email).exclude(email__iexact=current_user_email).count() > 0:
raise forms.ValidationError('This email address is already in use.'
'Please supply a different email address.')
return email
def save(self, commit=True):
user = super(UpdateProfile, self).save(commit=False)
if commit:
user.save()
return user
I suppose that in line current_user_email = User.email "User" isn't really a actual user instance, it is a model class which you imported for setting model = User in Meta.
If you want to use this form only for editing user data You should do something like this:
urls.py
urlpatterns = patterns('project.apps.app_name.views',
url(r'^edit-user/(?P<pk>\d+)/?$','edit_user',name='edit_user'),
)
app_name/views.py:
def edit_user(request,pk):
user_instance = User.objects.get(pk=pk)
form = UpdateProfile(request.POST, instance=user_instance)
.... more code ....
And in forms.py you should change line from:
current_user_email = User.email
to:
current_user_email = self.instance.email
But the proper way to do that (if you use django >1.6) would be to create custom User model, and set email field attribute unique=True.
I have django custom user model MyUser with one extra field:
# models.py
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
age = models.PositiveIntegerField(_("age"))
# settings.py
AUTH_USER_MODEL = "web.MyUser"
I also have according to these instructions custom all-auth Signup form class:
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = MyUser
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
After submitting SignupForm (field for property MyUser.age is rendered corectly), I get this error:
IntegrityError at /accounts/signup/
(1048, "Column 'age' cannot be null")
What is the proper way to store Custom user model?
django-allauth: 0.12.0; django: 1.5.1; Python 2.7.2
Though it is a bit late but in case it helps someone.
You need to create your own Custom AccountAdapter by subclassing DefaultAccountAdapter and setting the
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=False)
user.age = form.cleaned_data.get('age')
user.save()
and you also need to define the following in settings:
ACCOUNT_ADAPTER = 'api.adapter.UserAccountAdapter'
This is also useful, if you have a custom SignupForm to create other models during user registration and you need to make an atomic transaction that would prevent any data from saving to the database unless all of them succeed.
The DefaultAdapter for django-allauth saves the user, so if you have an error in the save method of your custom SignupForm the user would still be persisted to the database.
So for anyone facing this issue, your CustomAdpater would look like this
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=commit)
user.age = form.cleaned_data.get('age')
# user.save() This would be called later in your custom SignupForm
Then you can decorate your custom SignupForm's with #transaction.atomic
#transaction.atomic
def save(self, request, user):
user.save() #save the user object first so you can use it for relationships
...
Side note
With Django 1.5 custom user model, the best practice is to use the get_user_model function:
from django.contrib.auth import get_user_model
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = get_user_model() # use this function for swapping user model
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
Maybe it's not related, but I thought it would be worth noticing.
i think you should define fields property in class Meta in SignupForm and set list of fields that contains age, like this :
class SignupForm(forms.Form):
...
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'age']
and if it's not worked, look at
this