Create User and UserProfile on user signup with django-allauth - python

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

Related

'User' object has no attribute 'user_type' - Django Custom User model issue

I've created a custom user abstract model and profile model to collect additional information once the user registers.
I am collecting "User type: Employer/employee" at the time of registration but this doesn't seem to be recognized in the profile view. Despite the user being correctly added into the DB (I checked via Admin).
For example, I created user: asus23910 (employer user type). But when I login and redirect to http://127.0.0.1:8000/employer_profile/asus23910/, I get following error:
'User' object has no attribute 'user_type'C:\Users\ASUS\PycharmProjects\Content\content\content\views.py, line 112, in employer_profile_view
1. Here's my employer_profile_view.py code:
def employer_profile_view(request, username):
user = User.objects.get(username=username)
if user.user_type != User.EMPLOYER:
# Redirect to the correct profile page if the user type is not employer
return redirect('employee_profile', username=request.user.username)
if request.method == 'POST':
form = EmployerProfileForm(request.POST, instance=user.employerprofile)
if form.is_valid():
employer_profile = form.save(commit=False)
employer_profile.user = user
employer_profile.save()
return redirect('employer_profile', username=request.user.username)
else:
form = EmployerProfileForm(instance=user.employerprofile)
context = {
'form': form,
'username': username,
}
return render(request, 'employer_profile.html', context)
2. Employer Profile model and connector
class EmployerProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_type = models.CharField(
max_length=10,
choices=User.USER_TYPE_CHOICES,
default=User.EMPLOYER
)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
title = models.CharField(max_length=255)
company_name = models.CharField(max_length=255)
company_logo = models.ImageField(upload_to='company_logos/')
company_location = models.CharField(max_length=255)
company_website = models.URLField()
company_twitter = models.URLField()
#one-2-one connector
#receiver(post_save, sender=User)
def create_employer_profile(sender, instance, created, **kwargs):
if created:
EmployerProfile.objects.create(user=instance, user_type=instance.user_type)
print('Employer Profile created')
#receiver(post_save, sender=User)
def save_employer_profile(sender, instance, **kwargs):
instance.employerprofile.user_type = instance.user_type
instance.employerprofile.save()
print('Employer Profile saved')
3. User model
#model one to store the user into db
class User(AbstractUser):
EMPLOYER = "employer"
EMPLOYEE = "employee"
USER_TYPE_CHOICES = [
(EMPLOYER, "Employer"),
(EMPLOYEE, "Employee"),
]
user_type = models.CharField(
max_length=10,
choices=USER_TYPE_CHOICES,
default=EMPLOYEE
)
email = models.EmailField(default='example#example.com')
username = models.CharField(max_length=150, default='example_user')
password = models.CharField(max_length=128, default='!')
groups = models.ManyToManyField(
Group,
blank=True,
related_name='content_groups'
)
user_permissions = models.ManyToManyField(
Permission,
blank=True,
related_name='content_user_permissions'
)
`
**
What I've tried:**
Flushing and starting new DB (as I used in-built Django user model before and some old users weren't fairing well with the new user-type field).
Adding the user type with default employer option to employer view and fetching the usertype from user model.
**
What I expect:**
The profile view to connect with the custom user model and allow the user to add additional information to their user profile. And ofcourse the profile page to have the user-type attribute as initially stored from user class.
You probably import User not from your models file, but from django.
Anyway I highly recommend (if you overwrote AUTH_USER_MODEL) using built-in get_user_model() method from Django e.g.:
from django.contrib.auth import get_user_model
def employer_profile_view(request, username):
user = get_user_model().objects.get(username=username)
if user.user_type != User.EMPLOYER:
...
And don't use User name for model, I prefer to use CustomUser by myself but you can name it differently, just to avoid mistakes.

i want to add custom field in django-allauth SignupForm

i wanted to add custom field with django-allauth SingupForm and adding new field like phone number. i already managed to add this field in Postgresql on my own(without migrations,but by my hands).
this is my postgresql screen
In my signup page i have these fields already but i can't managed to add "phone" to my database, i really want to make it! please someone help me.
forms.py
from allauth.account.forms import SignupForm
from django import forms
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='Voornaam')
last_name = forms.CharField(max_length=30, label='Achternaam')
phone = forms.CharField(max_length=30, label='phone')
def __init__(self, *args, **kwargs):
super(CustomSignupForm, self).__init__(*args, **kwargs)
self.fields['first_name'] = forms.CharField(required=True)
self.fields['last_name'] = forms.CharField(required=True)
self.fields['phone'] = forms.CharField(required=True)
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.phone = self.cleaned_data.get('phone')
user.save()
return user
def signup(self,request,user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
return user
settings.py
ACCOUNT_FORMS = {'signup': 'registration.forms.CustomSignupForm'}
What you need is a profile model which is attached to a user so that you can add extra fields for information you might want.
If you're starting a project from the very beginning you can also consider a custom user model so that all data is on one object.
When I do this, I create an accounts app which I put my overrides in for allauth and my model starts something like this (modified to add the receiver function which I don't have at the moment because I'm not using Profile objects);
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import ugettext_lazy as _
User = get_user_model()
class Profile(models.Model):
"""
Profile model
"""
class Meta:
"""
Metadata
"""
app_label = 'accounts'
verbose_name = _('User profile')
verbose_name_plural = _('User profiles')
user = models.OneToOneField(
verbose_name=_('User'),
to=User,
related_name='profile',
on_delete=models.CASCADE
)
# Add your fields here like `phone`
def __str__(self):
"""
String representation
"""
return f'User Profile for: {self.user}'
#receiver(post_save, sender=User)
def create_profile(sender, instance, **kwargs):
"""
Setup a profile as a user is created
"""
Profile.objects.create(user=instance) # Using `create` also saves the object
Your signup form then does something like
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.profile.phone = self.cleaned_data.get('phone')
user.profile.save()
return user
If you've already got users you'll also need a migration which creates profiles for them;
# Generated by Django 2.2.12 on 2020-05-01 22:03
from django.db import migrations
def create_profiles(apps, schema_editor):
User = apps.get_model('authentication', 'User') # this should match the User model you are using
Profile = apps.get_model('accounts', 'Profile')
for user in User.objects.all():
profile = Profile.objects.create(user=user)
profile.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RunPython(create_profiles, migrations.RunPython.noop)
]

Extending the Django User Model OneToOne - User profile not saving

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()

Django: authtools do create the user profile when the user is created

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)

Creating user accounts for pre-registered users in Django

I have a website that has a registration system and a blog with some registered users. Yesterday, I added a new app that creates dedicated profile pages for each one of those users.
The issue being, the profile pages aren't getting created for the users that have already registered. This is, I guess because the user profile creation logic allows for profile creations only after the user has registered.
Below is the code in my models.py
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def create_account(sender, instance, created, *args, **kwargs):
if created:
profile, new = UserAccount.objects.get_or_create(user=instance)
post_save.connect(create_account, sender=settings.AUTH_USER_MODEL)
So, what can I do to create those profile pages?
I tried the following:
1. Manually creating a profile page against each registered users' username. (But this isn't the way I want to lean on. This is just a temporary arrangement)
Issue with this, when the superuser who creates these profile pages calls for his private profile page [at /u/], the code looks towards all the users created by the superuser instead of his own.
This is the error that's shown:
MultipleObjectsReturned at /u/
get() returned more than one UserAccount -- it returned 2!
.
.
.
instance = get_object_or_404(UserAccount)
So, what's the issue in code here? [my views.py]
# public user profile
def user_account(request, username):
instance = get_object_or_404(UserAccount, user__username=username)
context = {
'instance' : instance,
'title' : "User Account",
}
template = "user_accounts/public_account.html"
return render(request, template, context)
# private user profile.
#login_required
def self_user_account(request):
if not request.user.is_authenticated:
raise Http404
instance = get_object_or_404(UserAccount)
if not request.user == instance.user:
raise Http404
context = {
'instance' : instance,
'title' : 'Your Account',
}
template = "user_accounts/self_account.html"
return render(request, template, context)
# ability to update the user profile
#login_required
def update_user_account(request):
if not request.user.is_authenticated:
raise Http404
instance = get_object_or_404(UserAccount)
if not request.user == instance.user:
raise Http404
if request.method == 'POST':
form = UserAccountForm(request.POST or None, request.FILES or None, instance=instance)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
messages.success(request, "Account Updated.")
return HttpResponseRedirect("/u/")
else:
messages.error(request, "Something went wrong. Profile not created.")
else:
form = UserAccountForm(instance=instance)
context = {
'title': 'Update Your Account',
'form' : form,
}
template = "user_accounts/update.html"
return render(request, template, context)
Below is my urls.py These are populated after /u/ from the main urls.py
urlpatterns = [
# for updating
url(r'^update/$', views.update_user_account, name="update_user_account"),
# for outside world
url(r'^(?P<username>[\w.#+-]+)/$', views.user_account, name="public_user_account"),
# for the user himself
url(r'^$', views.self_user_account, name="self_user_account"),
]
Below is my models.py script
# uploading profile photos
def upload_location(instance, filename):
return "account_photos/%s/%s" %(instance.user, filename)
# Create your models here.
class UserAccount(models.Model):
user = models.OneToOneField(User)
photo = models.ImageField(
upload_to=upload_location, # there needs to be a upload location tho
# most probably a cdn server
blank=True,
null=True,
width_field="width_field",
height_field="height_field")
width_field = models.IntegerField(default=0, null=True)
height_field = models.IntegerField(default=0, null=True)
bio = models.TextField(max_length=60, null=True, blank=True, verbose_name="You in 60 words.")
phone = PhoneNumberField(blank=True, verbose_name="Contact Number")
status = models.CharField(max_length=128, default="Student")
totos = models.IntegerField(default=0, verbose_name="Contribution")
user_since = models.DateTimeField(auto_now=True)
# social links
email = models.EmailField(verbose_name="email address",
max_length=255,
unique=True,
null=True, blank=True)
custom_link = models.URLField(null=True, blank=True)
facebook_link = models.URLField(null=True, blank=True)
twitter_link = models.URLField(null=True, blank=True)
linkedin_link = models.URLField(null=True, blank=True)
github_link = models.URLField(null=True, blank=True)
reddit_link = models.URLField(null=True, blank=True)
def __str__(self):
return self.user.username
def get_absolute_url(self):
return self.reverse("accounts:public_user_account", kwargs={"username":self.user__username})
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User) # User is coming from the user=models.OneToOneField(<User>)
def create_account(sender, instance, created, *args, **kwargs):
if created:
profile, new = UserAccount.objects.get_or_create(user=instance)
post_save.connect(create_account, sender=settings.AUTH_USER_MODEL)
Using get_object_or_404(UserAccount) uses all user accounts, so it will give a MultipleObjectsReturned error if more than one user account exists.
You need to filter the queryset so that it only returns one object. You are already using the username to do this. This should work as long as username is unique and there is only one user account per user.
get_object_or_404(UserAccount, user__username=username)
If you want to filter on the logged in user, you can do:
get_object_or_404(UserAccount, user=request.user)
Or, depending on your models, you might be able to follow the one-to-one field backwards, for example:
instance = request.user.useraccount

Categories

Resources