I am trying to save a model by using User model as a ForeignKey, but when I try to save the model I get a RecursionError: maximum recursion depth exceeded.
My code is:
from django.db import models
from django.contrib.auth.models import (User, Group)
from phonenumber_field.modelfields import PhoneNumberField
class AuthenticatorModel(models.Model):
id = models.IntegerField(auto_created=True, default=1, primary_key=True, serialize=False)
user_id = models.ForeignKey(User, related_name='auth_user', null=True)
first_name = models.CharField(default='First Name', max_length=50)
last_name = models.CharField(default='Last Name', max_length=50)
phone_number = PhoneNumberField()
email_id = models.CharField(default='email ID', help_text='Enter the email ID used to register the Django user',
max_length=50)
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
try:
obj = User.objects.get(id=self.id)
obj2 = AuthenticatorModel.objects.get_or_create(id=self.id)
except User.DoesNotExist:
return
obj.first_name = self.first_name
obj.last_name = self.last_name
obj2.phone_number = self.phone_number
obj.email = self.email_id
obj.save()
return super(AuthenticatorModel, self).save()
I am not sure whats wrong in this. How would I create content for AuthenticatorModel and update first_name, last_name and email?
You are manually calling get_or_create() in the save() method, but get_or_create() itself is also calling save(). There is no need to create obj2, since you are calling the super method at the end.
You could just do this:
def save(...):
try:
obj = User.objects.get(id=self.id)
except User.DoesNotExist:
return
obj.first_name = self.first_name
obj.last_name = self.last_name
obj.email = self.email_id
obj.save()
return super(AuthenticatorModel, self).save()
Related
I have two models staff and UserAddress and I'm trying to add the staff through models. But I get this error:
AttributeError at /accounts/signup/staff/
'NoneType' object has no attribute 'add'
What should I do now, Could some one help me?
I wanted to register a staff but this thing happened.
models.py:
class UserAddress(models.Model):
city = models.CharField(max_length=100)
address = models.CharField(max_length=200)
zip_code = models.CharField(max_length=15, blank=True)
def __str__(self):
return str(self.id)
class CustomUser(AbstractUser):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
user_address = models.ForeignKey(UserAddress, on_delete=models.CASCADE, null=True)
class Staff(CustomUser):
def save(self, *args, **kwargs):
if not self.id:
self.is_staff = True
self.is_superuser = False
return super(Staff, self).save(*args, **kwargs)
class Meta:
proxy = True
forms.py:
class StaffCreationForm(UserCreationForm):
first_name = forms.CharField(max_length=100, required=True)
last_name = forms.CharField(max_length=100, required=True)
email = forms.EmailField(required=True)
city = forms.CharField(max_length=100)
address = forms.CharField(max_length=100)
zip_code = forms.CharField(max_length=15, required=True)
class Meta(UserCreationForm.Meta):
model = CustomUser
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.first_name = self.cleaned_data.get("first_name")
user.last_name = self.cleaned_data.get("last_name")
user.email = self.cleaned_data.get("email")
user.is_staff = True
user.save()
address = UserAddress.objects.create(city=self.cleaned_data.get("city"),
address=self.cleaned_data.get("address"),
zip_code=self.cleaned_data.get("zip_code"))
user.user_address.add(address)
return user
views.py:
class StaffSignUpView(CreateView):
model = CustomUser
form_class = StaffCreationForm
template_name = "accounts/signup_user.html"
def get_context_data(self, **kwargs):
kwargs["user_type"] = "staff"
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = form.save()
login(self.request, user)
return redirect("home")
Your user.user_address object does not contain any value that is why you're getting this error 'NoneType' object has no attribute 'add' because default it's value is None and None datatype does not contain add() method so try this
user.user_address = address
user.save() # don't forget to save user
this will assign address object to user.user_address
I extended the Django AbstratUser so that users can use email to sign in and signup, these work perfectly. The problem I am facing, however, is that the extra information on the extended model is not storing the information in the database, even though the user gets created. Once I hit the submit button, the user and extended model get created, and while the user model stores the information, the extended model is always empty.
I have tried using both signals and #transaction_atomic, yet, I have not been able to figure it out. Maybe I am missing out something, I do not know.
Models.py
class Company(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
name= models.CharField(_('Company name'), max_length=250)
...
#more information
...
class Meta:
verbose_name = _('Company')
verbose_name_plural = _('Companies')
def __str__(self):
return self.name
forms.py
class CompanySignUpForm(CustomUserCreationForm):
name = forms.CharField(widget=TextInput(attrs={'placeholder': 'Company name'}))
...
#more fields
...
class Meta(CustomUserCreationForm.Meta):
model = User
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.is_company = True
user.save()
company = Company.objects.create(user=user)
company.name = self.cleaned_data.get('name')
...
#more information
...
return user
Views.py
def company_signup(request):
if request.method == 'POST':
form = CompanySignUpForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'accounts/templates/company_success.html')
else:
form = CompanySignUpForm()
return render(request, 'accounts/templates/company_signup.html', context={
'title': _('Create a Company Account'),
'form': form,
})
Edit:
Thanks to #Mandrup, I was able to extend his solution to fit my need.
forms.py
class CompanySignUpForm(CustomUserCreationForm):
name = forms.CharField(widget=TextInput(attrs={'placeholder': 'Company name'}))
number_of_employees = forms.CharField(widget=NumberInput(attrs={'placeholder': 'Number of employees'}))
phone = forms.CharField(widget=TextInput(attrs={'placeholder': 'Contact Number'}))
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=True, empty_label="Country")
class Meta(CustomUserCreationForm.Meta):
model = User
#transaction.atomic
def save(self, commit=True):
user = super(CompanySignUpForm, self).save(commit=False)
if commit:
user.is_company = True
user.save()
name = self.cleaned_data.get('name')
number_of_employees = self.cleaned_data.get('number_of_employees')
phone = self.cleaned_data.get('phone')
country = self.cleaned_data.get('country')
company = Company(user=user, name=name, number_of_employees=number_of_employees, phone=phone, country=country)
company.save()
return user
Edit:
This worked for me when i tried to create an extended user profile. I changed it to fit your needs.
Model:
class Company(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
name= models.CharField(max_length=250)
...
#more information
...
def __str__(self):
return self.name
Form:
class RegisterUserForm(UserCreationForm):
class Meta:
model = User
fields = ["username", "email", "password1", "password2"]#add whatever fields you want to here
def save(self, commit=True):
user = super(RegisterUserForm, self).save(commit=False)
if commit:
user.save()
company = Company(user=user, name='Company name')
company.save()
return user
I have the model "Account" below:
class Account(models.Model):
email=models.EmailField(verbose_name="email", max_length=60, unique=True)
username=models.CharField(max_length=30, unique=True)
data_inscricao=models.DateTimeField(verbose_name='Data de Inscrição', auto_now_add=True)
ultimo_login=models.DateTimeField(verbose_name='ùltimo Login', auto_now_add=True)
def __str__(self):
return self.username
and this other "PersonalData"...
class PersonalData(models.Model):
id_user=models.OneToOneField(Account, on_delete=models.CASCADE)
nome_completo=models.CharField(max_length=56, unique=True, null=True)
email=models.EmailField(max_length=60, verbose_name="Email", unique=True, null=True)
cpf=models.CharField(max_length=14, unique=True, null=True, verbose_name="CPF")
rg=models.CharField(max_length=12, unique=True, null=True, verbose_name="RG")
idade=models.IntegerField(null=True)
data_nascimento=models.DateField(verbose_name="Data de Nascimento", null=True)
genero=models.CharField(max_length=8, choices=GENERO, null=True)
estado_civil=models.CharField(max_length=13, null=True, choices=ESTADO_CIVIL, verbose_name="Estado Civil")
def __str__(self):
return self.nome_completo
views.py
def cadastro_curriculo(request):
form = InsereDadosPessoais(request.POST or None, request.FILES or None)
if form.is_valid():
form.save()
return redirect("vagas")
return render(request, "personal/curriculo.html", {'form': form,})
forms.py
class InsereDadosPessoais(forms.ModelForm):
class Meta:
model = PersonalData
fields = '__all__'
I'ld like PersonalData.id_user use by default the Account.username of the loged user and I don't know how do that.
Just to exemplify, In Django Admin, my PersonalData model allows me to choose the user, but I want it to happen automatically and not manually.
Can someone help me?
at admin.py add the admin class as per the docs
assign exclude as per the docs
exclude = ('user',)
and at save_model
def save_model(self, request, obj, form, change):
obj.user = request.user
super().save_model(request, obj, form, change)
Check docs here
If you want to add this to normal views
forms.py
class XXForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(XXForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(XXForm, self).save(commit=False)
obj.user = self.user
if commit:
obj.save()
return obj
views.py
def xxview(request, *args, **kwargs): # your args
if request.POST:
#your stuff
form = XXForm(request.POST......., user=request.user)
else:
# your stuff
Check views here
I think you are Brazilian too :) Welcome! I learned this from this site.
models.py
from django.contrib.auth import User
class PersonalData(models.Model):
id_user = models.OneToOneField(User, on_delete=models.PROTECT, null=True, blank=True)
cpf = models.CharField(max_length=14, unique=True, null=True, verbose_name="CPF")
rg = models.CharField(max_length=12, unique=True, null=True, verbose_name="RG")
idade = models.IntegerField(null=True)
data_nascimento = models.DateField(verbose_name="Data de Nascimento", null=True)
genero = models.CharField(max_length=8, choices=GENERO, null=True)
estado_civil = models.CharField(max_length=13, null=True, choices=ESTADO_CIVIL, verbose_name="Estado Civil")
data_inscricao = models.DateTimeField(verbose_name='Data de Inscrição', auto_now_add=True)
ultimo_login = models.DateTimeField(verbose_name='ùltimo Login', auto_now_add=True)
def __str__(self):
return '{} {}'.format(id_user.first_name, id_user.last_name)
def save_model(self, request, obj, form, change):
if not obj.id_user:
# Only set added_by during the first save.
obj.id_user = request.user
super().save_model(request, obj, form, change)
admin.py
from .models import PersonalData
class PersonalDataAdmin(admin.ModelAdmin):
readonly_fields = ('id_user',)
admin.site.register(PersonalData, PersonalDataAdmin)
Important notes:
You don't have to create a field that saves the full name, Django itself can do it for you.
If you are going to use Django-Admin you can work with permissions within it by own User models. Click here and read a little about.
I recommend you use the built-in User model, it would look like this. Feel free to access my profile and get my contact information, I would be happy to help you with your project!
I'd suppose you have to make id_user field a foreign key to Account identifier which you should add to your model. In your described scenario id_user will "use" Account.username which is not obligated to be unique among all the users registered in the system. So, I suggest you to create an id field for Account and utilize it as a numeral identifier for that model.
Also, if I do not mistake, Django has an intrinsic User model which is intended for usage in such cases. Of course, you can extend that model for adding specific attributes.
I am using jwt auth system When I fill the field it returns jwt token easily but if i want to create a profile with this token it says
CustomUser has no attribute 'objects'
my users/models.py
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=40, unique=True)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
date_joined = models.DateTimeField(default=timezone.now)
object = UserManager()
USERNAME_FIELD = 'email'
def save(self, *args, **kwargs):
super(CustomUser, self).save(*args, **kwargs)
return self
and user/views.py
class CreateUserAPIView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
user = request.data
serializer = UserSerializer(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
try:
email = request.data['email']
password = request.data['password']
user = CustomUser.object.get(email=email, password=password)
if user:
try:
payload = jwt_payload_handler(user)
token = jwt.encode(payload, settings.SECRET_KEY)
user_details = {}
user_details['id'] = "%s" % (user.id)
user_details['token'] = token
user_logged_in.send(sender=user.__class__,
request=request, user=user)
return Response(user_details, status=status.HTTP_200_OK)
except Exception as e:
raise e
else:
return Response(status=status.HTTP_403_FORBIDDEN)
except KeyError:
res = {'error': 'please provide a email and a password'}
return Response(res)
with this code I am getting a token but with this token I cannot create a profile
profile/models.py
class Profile(TimeModel):
user = models.OneToOneField('user.CustomUser', on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
birthdate = models.DateTimeField(null=True)
image = models.ImageField(upload_to='path', null=True)
def __str__(self):
return self.user.email
here what i have tried so far. I tried to look for solution but could not get any and none of them solved my problems. How can I solve this problems. Any help would be appreciated! Thank you!
P.S
In the serializers file there is no additional overridden functions it just includes model name and fields of Profile model
You're using a custom manager and you assign that to object instead of objects in your CustomUser model.
class CustomUser(AbstractBaseUser, PermissionsMixin):
...
objects = UserManager()
...
Also, your queryset should use CustomUser.objects.get(...) Another thing I've realized is you're overriding the save method inside your CustomUser but you're not doing anything specific there, so there's no need to override that method.
I think you've a typo in your code - it should be objects:
user = CustomUser.objects.get(email=email, password=password)
I have a django project with the django-allauth app. I need to collect additional data from the user at signup. I came across a similar question here but unfortunately, no one answered the profile customization part.
Per the documentation provided for django-allauth:
ACCOUNT_SIGNUP_FORM_CLASS (=None)
A string pointing to a custom form class (e.g. ‘myapp.forms.SignupForm’) that is used during signup to ask the user for additional input (e.g. newsletter signup, birth date). This class should implement a ‘save’ method, accepting the newly signed up user as its only parameter.
I am new to django and am struggling with this. Can someone provide an example of such a custom form class? Do I need to add a model class as well with a link to the user object like this ?
Suppose you want to ask the user for his first/last name during signup. You'll need to put these fields in your own form, like so:
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30, label='Voornaam')
last_name = forms.CharField(max_length=30, label='Achternaam')
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
Then, in your settings point to this form:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourproject.yourapp.forms.SignupForm'
Note that SignupForm cannot be defined in the same file as form overrides through ACCOUNT_FORMS or SOCIALACCOUNT_FORMS, because that would lead to a circular import error.
That's all.
Using the solution suggested by pennersr I was getting a DeprecationWarning:
DeprecationWarning: The custom signup form must offer a def signup(self, request, user) method DeprecationWarning)
This is because as of version 0.15 the save method has been deprecated in favour of a def signup(request, user) method.
So to solve this, the code of the example should be like this:
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30, label='Voornaam')
last_name = forms.CharField(max_length=30, label='Achternaam')
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
Here's what worked for me combining a few of the other answers (none of them are 100% complete and DRY).
In yourapp/forms.py:
from django.contrib.auth import get_user_model
from django import forms
class SignupForm(forms.ModelForm):
class Meta:
model = get_user_model()
fields = ['first_name', 'last_name']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
And in settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourapp.forms.SignupForm'
This way it uses the model forms so that it's DRY, and uses the new def signup. I tried putting 'myproject.myapp.forms.SignupForm' but that resulted in a error somehow.
#Shreyas: The below solution may not be the cleanest, but it works. Please let me know if you have any suggestions to clean it up any further.
To add information that does not belong to the default user profile, first create a model in yourapp/models.py. Read the general django docs to learn more about it, but basicly:
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
organisation = models.CharField(organisation, max_length=100, blank=True)
Then create a form in yourapp/forms.py:
from django import forms
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30, label='Voornaam')
last_name = forms.CharField(max_length=30, label='Achternaam')
organisation = forms.CharField(max_length=20, label='organisation')
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
# Replace 'profile' below with the related_name on the OneToOneField linking back to the User model
up = user.profile
up.organisation = self.cleaned_data['organisation']
user.save()
up.save()
In your users/forms.py you put:
from django.contrib.auth import get_user_model
class SignupForm(forms.ModelForm):
class Meta:
model = get_user_model()
fields = ['first_name', 'last_name']
def save(self, user):
user.save()
In settings.py you put:
ACCOUNT_SIGNUP_FORM_CLASS = 'users.forms.SignupForm'
In this way you don't break DRY principle by multiplicity User models fields definition.
I've tried many different tutorials and all of them is missing something, repeating unnecessary code or doing weird things, bellow follows my solution that joins all the options that I've found, it's working, I have already put it in production BUT it still not convincing me because I would expect to receive first_name and last_name inside the functions that I attached to Users create to avoid creating a profile inside the form but I couldn't, by the away I think it will help you.
Models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
nationality = models.CharField(max_length=2, choices=COUNTRIES)
gender = models.CharField(max_length=1, choices=GENDERS)
def __str__(self):
return self.user.first_name
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
Forms.py
class SignupForm(forms.ModelForm):
first_name = forms.CharField(max_length=100)
last_name = forms.CharField(max_length=100)
class Meta:
model = Profile
fields = ('first_name', 'last_name', 'nationality', 'gender')
def signup(self, request, user):
# Save your user
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
user.profile.nationality = self.cleaned_data['nationality']
user.profile.gender = self.cleaned_data['gender']
user.profile.save()
Settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'apps.profile.forms.SignupForm'
#models.py
from django.conf import settings
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(default='users/default.png', upload_to='users')
fields = models.ForeignKey('Field' ,null=True ,on_delete=models.SET_NULL)
category = models.ForeignKey('Category' ,null=True ,on_delete=models.SET_NULL)
description = models.TextField()
interests = models.ManyToManyField('Interests')
...
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
...
def userprofile_receiver(sender, instance, created, *args, **kwargs):
if created:
userprofile = UserProfile.objects.create(user=instance)
else:
instance.userprofile.save()
post_save.connect(userprofile_receiver, sender=settings.AUTH_USER_MODEL)
#forms.py
class SignupForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SignupForm, self).__init__(*args, **kwargs)
self.fields['first_name'].widget = forms.TextInput(attrs={'placeholder': 'Enter first name'})
self.fields['last_name'].widget = forms.TextInput(attrs={'placeholder': 'Enter last name'})
first_name = forms.CharField(max_length=100)
last_name = forms.CharField(max_length=100)
interests = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, help_text="Choose your interests", queryset=Interests.objects.all())
image = forms.ImageField(help_text="Upload profile image ")
fields = forms.ChoiceField(help_text="Choose your fields ")
category = forms.ChoiceField(help_text="Choose your category")
class Meta:
model = UserProfile
fields = ('first_name', 'last_name', 'name', 'image', 'fields', 'category', 'description', 'phone', 'facebook', 'twitter', 'skype', 'site', 'address', 'interests' ,'biography')
widgets = {
...
'description': forms.TextInput(attrs={'placeholder': 'Your description'}),
'address': forms.TextInput(attrs={'placeholder': 'Enter address'}),
'biography': forms.TextInput(attrs={'placeholder': 'Enter biography'}),
....
}
def signup(self, request, user):
# Save your user
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
user.userprofile.image = self.cleaned_data.get('image')
user.userprofile.fields = self.cleaned_data['fields']
user.userprofile.category = self.cleaned_data['category']
user.userprofile.description = self.cleaned_data['description']
interests = self.cleaned_data['interests']
user.userprofile.interests.set(interests)
user.userprofile.save()
# settings.py or base.py
ACCOUNT_SIGNUP_FORM_CLASS = 'nameApp.forms.SignupForm'
That is it. (:
Create a Profile Model with user as OneToOneField
class Profile(models.Model):
user = models.OneToOneField(User, verbose_name=_('user'), related_name='profiles')
first_name=models.CharField(_("First Name"), max_length=150)
last_name=models.CharField(_("Last Name"), max_length=150)
mugshot = ImageField(_('mugshot'), upload_to = upload_to, blank=True)
phone= models.CharField(_("Phone Number"), max_length=100)
security_question = models.ForeignKey(SecurityQuestion, related_name='security_question')
answer=models.CharField(_("Answer"), max_length=200)
recovery_number= models.CharField(_("Recovery Mobile Number"), max_length=100)
city=models.ForeignKey(City,related_name='city', blank=True, null=True, help_text=_('Select your City'))
location=models.ForeignKey(Country,related_name='location', blank=True, null=True, help_text=_('Select your Location'))