I am trying to build an application that allows users to register and meet other users with similar interests. I am extending the user model using a OneToOne field but I run into a problem when I try to register some users: The profile does not save. The user data saves, but the profile data does not.
I do not understand what I am doing wrong, as I followed a tutorial to write the program.
This is my Models.py file:
class Profile(models.Model):
GENDERS = (
('M', 'Male'),
('F', 'Female'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
email = models.EmailField(max_length=254, blank=True)
gender = models.CharField(choices=GENDERS, max_length=1, null=True, default='')
dob = models.DateField(auto_now=False, auto_now_add=False, blank=True, null=True)
hobby = models.ManyToManyField(Hobby)
def __str__(self):
return self.user.username
#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()
post_save.connect(create_user_profile, sender=User)
This is my forms.py file:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'password', 'first_name', 'last_name')
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('email', 'gender', 'dob', 'hobby')
This is my view function:
def register(request):
if request.method =="POST":
userForm = UserForm(request.POST)
profileForm = ProfileForm(request.POST)
if userForm.is_valid() and profileForm.is_valid():
userForm.save()
profileForm.save()
return redirect('/')
else:
return render(request, 'QMLove/register.html', {'userForm': userForm, 'profileForm': profileForm})
else:
userForm = UserForm()
profileForm = ProfileForm()
return render(request, 'QMLove/register.html',{'userForm': userForm, 'profileForm': profileForm})
Thank you in advance!
You haven't done anything to associate the Profile you're creating with the User you've created. I would expect either two profiles two be created - one empty and one with data but not associated with the user - or for the profile form save to fail with an integrityerror because you didn't supply the user.
You should remove those signal receivers because they won't help with what you want to do and will probably create conflicts. Instead, pass the created user when you save the profile:
user = userForm.save()
profile = profileForm.save(commit=False)
profile.user = user
profile.save()
Related
I am using django-allauth for taking car of accounts, login, logout, signup, but I need that on create the user must create a profile and I am using the model UserProfile as it can be seen on the code. The problem is that when I created the custom signup form, now it creates a user with [username, email, first_name, last_name, password] but it does not create a UserProfile. And I have three questions:
How to create a User and a UserProfile on signup?
How can add styling to the forms that come with django-allauth, i.e. at /accounts /login/
How can I modify so that when the user logs in, it will redirect him to /profiles/ instead of /accounts/profiles or is it better in terms of REST principles to have it /accounts/profiles/ if yes, then is it possible to modify the profiles app so that it can use django-allauth views?
My custom signup form:
# django_project/profiles/forms.py
from django import forms
from allauth.account.forms import SignupForm
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='First Name')
last_name = forms.CharField(max_length=30, label='Last Name')
bio = forms.CharField(max_length=255, label='Bio')
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.bio = self.cleaned_data['bio']
user.save()
return user
Settings:
# django_project/django_project/settings.py
ACCOUNT_FORMS = {
'signup': 'profiles.forms.CustomSignupForm',
}
And main url patterns:
# django_project/django_project/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('profiles/', include('profiles.urls')),
path('accounts/', include('allauth.urls')),
]
Url patterns in profiles app:
# django_project/profiles/urls.py
app_name = 'profiles'
urlpatterns = [
path('<str:user>/', ProfileView.as_view(), name='profile-detail'),
]
And this is my ProfileView:
class ProfileView(LoginRequiredMixin, View):
def get(self, request, user, *args, **kwargs):
profile = UserProfile.objects.get(user=user)
my_user = profile.user
context = {
'user': my_user,
'profile': profile,
}
return render(request, 'profile/profile.html', context)
I have a different user profile than the User model that comes with django user model:
User = settings.AUTH_USER_MODEL
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, verbose_name='user',
related_name='profile', on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, blank=True, null=True)
last_name = models.CharField(max_length=30, blank=True, null=True)
email = models.CharField(max_length=30, blank=True, null=True)
bio = models.TextField(max_length=500, blank=True, null=True)
The Signals of user creation:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
How to create a User and a UserProfile on signup?
You can create a UserProfile at the same time you save the CustomSignupForm
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.bio = self.cleaned_data['bio']
user.save()
# Create your user profile
UserProfile.objects.create(user=user, first_name=self.cleaned_data['first_name'], last_name=self.cleaned_data['last_name'], email=self.cleaned_data['email'], bio=self.cleaned_data['bio'])
Another elegant way is to use Django signals to perform some actions after that an event occur like user creation.
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import UserProfile
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
first_name = instance.first_name
last_name = instance.last_name
email = instance.email
# The bio field is not set because the User instance has not bio attribute by default.
# But you can still update this attribute with the profile detail form.
UserProfile.objects.create(user=instance, first_name=first_name, last_name=last_name, email=email)
If you want to update the profile each time an user is updated, then remove the if created in the signal body.
apps.py
class AppNameConfig(AppConfig):
# some code here
# import your signal in the ready function
def ready(self):
import app_name.signals
How to create a User and a UserProfile on signup?
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='First Name')
last_name = forms.CharField(max_length=30, label='Last Name')
bio = forms.CharField(max_length=255, label='Bio')
def save(self, request):
# create user the create profile
user = super(CustomSignupForm, self).save(request)
### now save your profile
profile = UserProfile.objects.get_or_create(user=user)
profile.first_name = self.cleaned_data['first_name']
profile.last_name = self.cleaned_data['last_name']
profile.bio = self.cleaned_data['bio']
profile.save()
return user
How can add styling to the forms that come with django-allauth, i.e. at
make a new directory in your templates call it /account/login.html and render your form there and add styles as follow
this can be done in many ways
using https://pypi.org/project/django-bootstrap4/
https://pypi.org/project/django-widget-tweaks/
manually render fields https://simpleisbetterthancomplex.com/article/2017/08/19/how-to-render-django-form-manually.html
How can I modify so that when the user logs in, it will redirect him to /profiles/ instead of /accounts/profiles or is it better in terms of REST principles to have it /accounts/profiles/ if yes, then is it possible to modify the profiles app so that it can use django-allauth views?
go to your setting files and add the following
LOGIN_REDIRECT_URL = "/profiles"
you can check more settings here
https://django-allauth.readthedocs.io/en/latest/configuration.html
Im still learning Django and I am stuck at user registration / profile creation.
My goal
So, the purpose of this is to save the new user and at the same time save the profile of the new user with de data from the form. I use 2 forms u_form and p_form.
What I have done so far:
Created model Profile with OneToOneField to User
Created 2 forms for User (u_form) and Profile (p_form)
Created signals.py to create new Profile when new User is created
In the view I have create function with u_form.save()
Problem
This works, but the new Profile is completely empty.. When I put p_form.save() in my view it gives me this error:
NOT NULL constraint failed
The code
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
voorletter = models.CharField(max_length=10)
voorvoegsel = models.CharField(max_length=10)
achternaam = models.CharField(max_length=200)
depers = models.CharField(max_length=25)
depersoud = models.CharField(max_length=25)
telefoonnummer = models.CharField(max_length=25)
class Meta:
verbose_name = "Collega"
verbose_name_plural = "Collega's"
def __str__(self):
return self.depers
signals.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
views.py
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from gebruikers.forms import UserRegisterForm, ProfileRegisterForm
def gebruiker_create(request):
if request.method == "POST":
u_form = UserRegisterForm(request.POST)
p_form = ProfileRegisterForm(request.POST)
if u_form.is_valid() and p_form.is_valid():
u_form.save()
messages.success(request, f'Account is aangemaakt.')
return redirect('login')
else:
u_form = UserRegisterForm()
p_form = ProfileRegisterForm()
context = {
'u_form': u_form,
'p_form': p_form
}
return render(request, 'users/register.html', context)
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import Profile
class UserRegisterForm(UserCreationForm):
email = forms.EmailField(label = "Email")
password1 = forms.CharField(widget=forms.TextInput(attrs={'type':'password'}), label="Wachtwoord", help_text=None)
password2 = forms.CharField(widget=forms.TextInput(attrs={'type':'password'}), label="Wachtwoord herhalen", help_text=None)
class Meta:
model = User
fields= ['username', 'email', 'password1', 'password2']
class ProfileRegisterForm(forms.ModelForm):
voorletters = forms.CharField(label="Voorletters", max_length=10)
voorvoegsel = forms.CharField(label="Voorvoegsel", max_length=50)
achternaam = forms.CharField(label='Achternaam', max_length=100)
depers = forms.CharField(label='Depers', max_length=8)
depersoud = forms.CharField(label='Oude Depers', max_length=50)
telnummer = forms.CharField(label="Telefoonnummer", max_length=20)
class Meta:
model = Profile
fields = ['voorletters', 'voorvoegsel', 'achternaam', 'depers', 'depersoud', 'telnummer']
If you want Profile fields to be empty initially just add blank=True in every model fields except for user .
voorletter = models.CharField(max_length=10, blank=True)
.....
.....
.....
telefoonnummer = models.CharField(max_length=25, blank=True)
Then run makemigrations and migrate command to successfully make the changes in the database.
it will solve NOT NULL constraint failed error
I am using the authtools plugin to manage the user and its profile in django, but when I create the user, it does not create its profile, I have to go to the admin part of the site and create it manually.
I separated the applications into account and profile.
This is the profiles model:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True)
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
email_verified = models.BooleanField("Email verified", default=True)
This is the signal.py, that is inside of the profiles application:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
profile = models.Profile(user=instance)
profile.save()
logger.info('New user profile for {} created'.format(instance))
This is the admin.py of the account app:
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline]
list_display = ('is_active', 'email', 'name', 'permalink',
'is_superuser', 'is_staff',)
# 'View on site' didn't work since the original User model needs to
# have get_absolute_url defined. So showing on the list display
# was a workaround.
def permalink(self, obj):
url = reverse("profiles:show",
kwargs={"slug": obj.profile.slug})
# Unicode hex b6 is the Pilcrow sign
return format_html('{}'.format(url, '\xb6'))
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin.site.register(Profile)
When I signup a user, both the user and the profile objects are created, only they are not linked. Why is that?
Thank you
Use this below your profile model in models.py. I hope you are generating slug by another slugify signal.
def user_created_receiver(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.get_or_create(user = instance)
post_save.connect(user_created_receiver, sender = User)
I have two models Profile and Company
models.py
class Profile(models.Model):
user = models.OneToOneField(User)
company = models.ForeignKey('company.Company', null=True)
phone = models.CharField(max_length=10, blank=True)
#receiver(post_save, sender=User)
#receiver(post_save, sender=Company)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
Profile.objects.create(company=instance)
instance.profile.save()
As you can see Profile is an user_model extension. I have got that working when sending only one instance.
models.py
class Company(models.Model):
user = models.OneToOneField(User)
name = models.CharField(max_length=10, blank=True, unique=True)
phone = models.CharField(max_length=10, blank=True)
Company is created successfully. I want to save the name field in the Company to The Profile when a company is created.
views.py
def form_valid(self, form):
company = form.save(commit=False)
user = self.request.user
name=form.cleaned_data['name']
phone=form.cleaned_data['phone']
company.name = name
company.phone = phone
company.user = user
company.save()
Profile.refresh_from_db()
Profile.company = name
Profile.save()
return super(CompanyCreateView, self).form_valid(form)
According to your model architecture, following should be the code for the signal based approach.
#receiver(post_save, sender=User)
#receiver(post_save, sender=Company)
def update_user_profile(sender, instance, created, **kwargs):
if created:
if sender.__name__ == 'User':
Profile.objects.create(user=instance)
# Company
else:
profile = Profile.objects.get(user=instance.user)
profile.company = instance
profile.save()
What a want to do: When a user is logged in, and he or she makes a post, the name of that user should automatically be assigned in my database posts.
What it's doing: It's not adding a user automatically, but i am able to assign a user manually, so I'm accessing the user database, and seeing whom i can attach to a newly made post.
My question is then, how can i get this process done automatically?
Here is my code from the model.py in the posts app:
from __future__ import unicode_literals
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.contrib.auth.models import User
User = settings.AUTH_USER_MODEL
class Post(models.Model):
title = models.CharField(max_length=120)
content = models.TextField()
#email = models.EmailField(null=True, blank=True, default=None)
user = models.ForeignKey(User, null=True,)
#upload = models.FileField(null=True, blank=True, default=None)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=True, auto_now_add=False)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:detail", kwargs={"id":self.id})
class Meta:
ordering = ["-timestamp", "-updated"]
I am getting the user class via User = settings.AUTH_USER_MODEL and the AUTH_USER_MODEL is referring in settings.py to a class called MyUser in another models.py who originates from an app called accounts.
here is the code from that class:
class MyUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
is_admin = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
USERNAME_FIELD = 'email'
Here is the code from views.py in the posts app:
def post_create(request):
form = PostForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
# Message succes
messages.success(request, "Succesfully Created ")
return HttpResponseRedirect(instance.get_absolute_url())
else:
messages.error(request, "Not Succesfully created")
context = {
'form': form,
}
return render(request, app_name+"/post_form.html", context)
Here is the forms.py in the posts app:
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = {
"title",
"content",
"user",
#"email",
#"upload",
}
Here are two pictures to illustrate my problem:
The post create site
The django administration
Let me now if any more code is needed, appreciate any feedback as well.
I don't have a lot of rep on stack overflow so please let me know if this is poorly explained, and i shall re right it.
Simply change:
instance = form.save(commit=False)
instance.save()
to
instance = form.save(commit=False)
if request.user.is_authenticated():
instance.user = request.user
instance.save()
If user is logged in, i think the Combobox should not
appear, so you can do that on forms.py
forms.py
class PostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(PostForm, self).__init__(*args, **kwargs)
if not self.user.is_authenticated():
self.fields['user'] = forms.ModelChoiceField(
required=True,
queryset=User.objects.all())
class Meta:
model = Post
fields = {
"title",
"content",
# "user",
#"email",
#"upload",
}
on views.py
def post_create(request):
form = PostForm(request.POST or None, user = request.user)
if form.is_valid():
if request.user.is_authenticated():
form.instance.user = request.user
form.save()
...
return render(request, app_name+"/post_form.html", context)
If you want the Combobox has selected with the user logged in, you can pass initial data on views.py, like this:
def post_create(request):
if request.method == 'GET':
form = PostForm(initial = {'user' : request.user})