Prepopulate custom form with social data in django-allauth - python

How can i prepopulate the sign up form with extra data in django-allauth after I've been connected with Facebook during registration?
In my settings I have the following
settings.py
SOCIALACCOUNT_AUTO_SIGNUP = False
Let's say I have a UserProfile model with some data related to the user.
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
gender = models.CharField(max_length=1)
If I use the following form, a non-registered user can connect with Facebook and receives a registration form to fill (an instance of UserSignupForm), where the first name and the last name are already prepopulated. How can I fill automagically the gender using the data collected from Facebook?
In other words, I would like to use the gender taken from facebook extra data as initial data for the sign up form.
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'UserSignupForm'
forms.py
class UserSignupForm(forms.ModelForm):
first_name = forms.CharField(label=_('First name'), max_length=30)
last_name = forms.CharField(label=_('Last name'), max_length=30)
class Meta:
model = UserProfile
fields = ['first_name', 'last_name', 'gender']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
self.instance.user = user
self.instance.user.save()
self.instance.save()
It seems to me that I should change the adapter.
adapter.py
class UserProfileSocialAccountAdapter(DefaultSocialAccountAdapter):
def populate_user(self, request, sociallogin, data):
user = super(UserProfileSocialAccountAdapter, self).populate_user(request, sociallogin, data)
# Take gender from social data
# The following line is wrong for many reasons
# (user is not saved in the database, userprofile does not exist)
# but should give the idea
# user.userprofile.gender = 'M'
return user

I'm using a solution that doesn't use the adapter, but overrides the initialization of the signup form.
forms.py
class UserSignupForm(forms.ModelForm):
first_name = forms.CharField(label=_('First name'), max_length=30)
last_name = forms.CharField(label=_('Last name'), max_length=30)
class Meta:
model = UserProfile
fields = ['first_name', 'last_name', 'gender']
def __init__(self, *args, **kwargs):
super(UserSignupForm, self).__init__(*args, **kwargs)
if hasattr(self, 'sociallogin'):
if 'gender' in self.sociallogin.account.extra_data:
if self.sociallogin.account.extra_data['gender'] == 'male':
self.initial['gender'] = 'M'
elif self.sociallogin.account.extra_data['gender'] == 'female':
self.initial['gender'] = 'F'

Related

How create a user and a profile with a single form in Django?

I have created a Clients model in models.py that is intended to be a Client (user) Profile.
class Clients(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, verbose_name="Primeiro Nome")
last_name = models.CharField(max_length=30, verbose_name="Apelido")
address = models.CharField(max_length=200, verbose_name="Morada")
nif = models.CharField(max_length=9, verbose_name="NIF", validators=[RegexValidator(r'^\d{1,10}$')], primary_key=True)
mobile = models.CharField(max_length=9, verbose_name="Telemóvel", validators=[RegexValidator(r'^\d{1,10}$')])
email = models.CharField(max_length=200, null=True, verbose_name="Email")
avatar = models.ImageField(null=True)
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
class Meta:
verbose_name_plural = "Clientes"
#receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Clients.objects.create(user=instance)
instance.profile.save()
This model is connected to the Django Users through a OneToOneField called user.
I created a form that is capable of adding data to the Clients model in forms.py:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.validators import RegexValidator
class SignUpForm(UserCreationForm):
first_name = forms.CharField(max_length=30, label="Primeiro Nome")
last_name = forms.CharField(max_length=30, label="Apelido")
address = forms.CharField(max_length=200, label="Morada")
nif = forms.CharField(max_length=9, label="NIF", validators=[RegexValidator(r'^\d{1,10}$')])
mobile = forms.CharField(max_length=9, label="Telemóvel", validators=[RegexValidator(r'^\d{1,10}$')])
email = forms.CharField(max_length=200, label="Email")
class Meta:
model = User
fields = ('username', 'password1', 'password2', 'first_name', 'last_name', 'address', 'nif', 'mobile', 'email')
How can I, through this single form, add a username and password field so that, through the OneToOneField, it creates an user connected to this profile?
EDIT
The new version of the files above. Now, it creates the user, but all other fields for the Clients get passed empty.
My views.py
def signup(request):
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save()
user.refresh_from_db() # load the profile instance created by the signal
user.first_name = form.cleaned_data.get('first_name')
user.last_name = form.cleaned_data.get('last_name')
user.address = form.cleaned_data.get('address')
user.nif = form.cleaned_data.get('nif')
user.mobile = form.cleaned_data.get('mobile')
user.email = form.cleaned_data.get('email')
user.save()
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=user.username, password=raw_password)
login(request, user)
return redirect('clientes')
else:
form = SignUpForm()
return render(request, 'backend/new_client.html', {'form': form})
The fields you added are just regular form fields, django doesn't know anything about where to save these values. So you need to override the form's save() method or save them in your view. Here's how to save them in your view, since this is what you started to do:
if form.is_valid():
user = form.save() # this creates the user with first_name, email and last_name as well!
user.refresh_from_db() # load the profile instance created by the signal
user.clients.address = form.cleaned_data.get('address')
user.clients.nif = form.cleaned_data.get('nif')
user.clients.mobile = form.cleaned_data.get('mobile')
user.clients.save()
login(request, user)
return redirect('clientes')
Note: I don't do anything with first_name, last_name and email in the view, they are fields of the User model so they will already be saved automatically when you do form.save(). You should remove them from your Clients model.
Note 2: Renaming your Clients model to Client would make your code more readable. You should always use singular for your models. This way you can do user.client.address which makes more sense than user.clients.address since it's a one-to-one field.
Alternatively, you can override the form's save() method, which is a method I would prefer as I don't think the view should care about how to save the user's profile:
# in SignupForm(UserCreationForm):
def save(self, commit=True):
user = super().save(commit) # this creates the new user
if commit:
user.refresh_from_db() # not sure if this is needed
user.clients.nib = self.cleaned_data.get('nib')
user.clients.address = self.cleaned_data.get('address')
user.clients.mobile = self.cleaned_data.get('mobile')
user.clients.save()
return user

Get inputs from user into profile model of Django

I have a registration form which is an extension of UserCreationForm and I have a UserProfileForm. I am rendering both the forms to the same html during user registration.
The problem is, inputs are getting saved to the inbuilt users model but not to the Profile Model. No data is showing up in Profile Model.
I have tried many ways and looked for many solution but unable to find the mistake.
My forms.py looks like this -
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
username = forms.CharField(required=True)
class Meta:
model = User
fields = ['username',
'first_name',
'last_name',
'email',
'password1',
'password2'
]
def save(self,commit):
user = super(RegistrationForm,self).save(commit=False)
if commit:
user.save()
return user
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ['user']
def save(self,commit):
user = super(RegistrationForm,self).save(commit=False)
USN =self.cleaned_data['USN']
year = self.cleaned_data['year']
sem = super(RegistrationForm,self).save(commit=False)
if commit:
user.save()
return user
Have created UserProfile model in models.py
class UserProfile(models.Model):
alphanumeric = RegexValidator(r'^[0-9A-Z]*$', 'Only alphanumeric
characters are allowed.')
user = models.OneToOneField(User, related_name =
'profile',on_delete=models.CASCADE)
USN = models.CharField(max_length=50, blank=True, null=True, validators=
[alphanumeric])
year = models.IntegerField(default=0)
sem = models.IntegerField(default=0)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
if kwargs['created']:
objects=models.Manager()
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender = User)
I have used both the forms in Views.py -
def register(request):
if request.method == 'POST':
form_1 = RegistrationForm(request.POST)
form_2 = UserProfileForm(request.POST)
if form_1.is_valid() and form_2.is_valid():
save_1 = form_1.save(commit = False)
save_2 = form_1.save(commit = False)
save_1.save()
save_2.save()
return render(request,'main/home.html')
else:
form_1 = RegistrationForm()
form_2 = UserProfileForm()
args = {'form_1':form_1,'form_2':form_2}
return render(request, 'account/register.html',args)
The username in the profile model object shows correctly, but other fields are not getting updated. I want other fields also getting updated in the profile object.

Django - Creating two user types, where one type can be both

We're required to have two separate forms for two different types of users. Call them Client and Provider. Client would be the parent, base user, while Provider is a sort of extension. At any point a Client could become a Provider as well, while still maintaining status and information as a Client. So a Provider has both permissions as a Client and as a Provider.
I'm new to Django. All we're trying to do is register either user type, but have a one to one relation between Provider and Client tables if a user registers as a Provider straight away.
The issue we're having is in the adapter, we think. A provider registers fine, but ends up in the users_user table with no entry in the generated users_provider table. Is it the way we're trying to save and relate these two entities in the database, or something else?
We're trying to utilize allauth for authentication and registration.
Our code:
models.py:
class User(AbstractUser):
name = models.CharField(_('Name of User'), blank=True, max_length=255)
def __str__(self):
return self.username
def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username})
SEX = (
("M","MALE"),
("F","FEMALE"),
)
birthdate = models.DateField(_('Birth Date'), default=django.utils.timezone.now, blank=False)
sex = models.CharField(_('Sex'), choices=SEX, max_length=1, default="M")
isProvider = models.BooleanField(_('Provider'), default=False)
#Using User, not models.Model
class Provider(User):
HAS_BUSINESS = (
('YES','YES'),
('NO','NO'),
)
#Resolving asociation 1:1 to User
#NOTE: AUTH_USER_MODEL = users.User in setting
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
has_business = models.CharField(_('Do you have your own business?'),max_length=2, choices=HAS_BUSINESS, default='NO')
isProvider = True
our forms.py
class ProviderForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
Provider.isProvider = True
class Meta:
model = Provider
fields = '__all__'
exclude = GENERAL_EXCLUSIONS + [
'owner',
]
class ClientForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
class Meta:
model = User
fields = "__all__"
exclude = GENERAL_EXCLUSIONS
def is_active(self):
return False
def __init__(self, *args, **kwargs):
super(ClientForm, self).__init__(*args, **kwargs)
views.py:
class ProviderRegisterView(SignupView):
template_name = 'account/form_provider.html'
form_class = ProviderForm
redirect_field_name = 'next'
view_name = 'registerprovider'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ProviderRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerprovider = ProviderRegisterView.as_view()
#View para el formulario de registro de usuarios clientes
class ClientRegisterView(SignupView):
template_name = 'account/form_client.html'
form_class = ClientForm
redirect_field_name = 'next'
view_name = 'registerclient'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ClienteRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerclient = ClienteRegisterView.as_view()
finally, our adapter.py:
#Per allauth documentation, settings changed:
#ACCOUNT_ADAPTER = 'projectname.users.adapters.RegisterUserAdapter'
class RegisterUserAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
data = form.cleaned_data
user.first_name = data['name']
user.last_name = data['lastname']
#Saving Client info
user.sex = data['sex']
user.birthdate = data['birthdate']
#Normal allauth saves
user.username = data['username']
user.email = data['email']
if user.isProvider:
p = Provider()
p.owner = user
p.has_business = data['has_business']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
#Save user
user.save()
#If it's also a Provider, save the Provider
if user.isProvider:
p.save()
return user
Any help or tips would be greatly appreciated. If I left something out, please let me know. I'm not sure if the problem is in the model itself, the way we represent the form, or the adapter. The way it stands, it doesn't matter what form we use, it's always saved as the base User table (our Client) and the Provider table never gets information saved to it.
With Django's new custom user model, only one user model can be set as settings.AUTH_USER_MODEL. In your example, you can set this to your User model.
Then for the optional provider data, create a separate model that is referenced by OneToOneField from your User model.
class User(AbstractUser):
...
provider = models.OneToOneField(Provider, null=True)
class Provider(models.Model):
...
This is the easiest way to work with multiple user types in Django, given the AUTH_USER_MODEL constraint.
Also, it's best to only subclass abstract models, otherwise you get multitable inheritance which results in hidden implied JOINs, degrading performance.
Finally, you can create the Provider object in your custom form's form.is_valid() method and assign user.provider = provider.

How to create a UserProfile field which is alias for a User field?

I want access User model's first_name, last_name and email fields from UserProfile as if they were UserProfile's own fields.
class Person(models.Model):
user = models.OneToOneField(User)
name = #ModelField which refers to user.first_name
surname = #ModelField which refers to user.last_name
email = #ModelField which refers to user.email
birth_date = models.DateField(null=True, blank=True)
#and other fields
I could use independent name, surname, email, fields but it would have caused data duplication with User fields.
upd
I want name, surname and email to be of some Field type (as birth_date) and modification of these fields to equal modification of corresponding User fields (property behavior shown below). I need this because I want these three fields to be edible in admin interface or to be prcocessed equally with "native" fields in ModelForm for example.
upd solution. Not very fancy, but here how it worked for me:
class Person(models.Model):
user = models.OneToOneField(User)
#and also some char, text fields
#property
def name(self):
return self.user.first_name
#name.setter
def name(self, value):
self.user.first_name = value
#and by analogy surname and email properties
class PersonForm(ModelForm):
class Meta:
model = Person
exclude = ('user',)
name = forms.CharField(max_length=100, required=False)
surname = forms.CharField(max_length=100, required=False)
email = forms.EmailField(required=False)
#no make fields filled with User data on form load
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
instance = kwargs['instance']
initial = kwargs.get('initial', {})
initial['name'] = instance.name
initial['surname'] = instance.surname
initial['email'] = instance.email
kwargs['initial'] = initial
super(PersonForm, self).__init__(*args, **kwargs)
# to save form data to User model when commit
def save(self, commit=True):
instance = super(PersonForm, self).save(commit)
user = instance.user
user.first_name = self.cleaned_data['name']
user.last_name = self.cleaned_data['surname']
user.email = self.cleaned_data['email']
user.save()
return instance
class PersonAdmin(admin.ModelAdmin):
fields = ['name', 'surname', 'email', 'and_others']
form = PersonForm
admin.site.register(Person, PersonAdmin)
If want you want is to access user.first_name, user.last_name, etc... directly as attributes of Person, you can add a name, surname, etc... properties to Person model, instead of fields:
class Person(models.Model):
user = models.OneToOneField(User)
birth_date = models.DateField(null=True, blank=True)
#and other fields
#property
def name(self):
return self.user.first_name
#property
def surname(self):
return self.user.last_name
....
class Person(models.Model):
user = models.OneToOneField(User)
#property
def name(self):
return self.user.first_name
#property
def surname(self):
return self.user.last_name

Django extending user model and displaying form

I am writing website and i`d like to implement profile managment. Basic thing
would be to edit some of user details by themself, like first and last name
etc. Now, i had to extend User model to add my own stuff, and email address.
I am having troubles with displaying form. Example will describe better what i
would like achieve.
This is mine extended user model.
class UserExtended(models.Model):
user = models.ForeignKey(User, unique=True)
kod_pocztowy = models.CharField(max_length=6,blank=True)
email = models.EmailField()
This is how my form looks like.
class UserCreationFormExtended(UserCreationForm):
def __init__(self, *args, **kwargs):
super(UserCreationFormExtended, self).__init__(*args, **kwargs)
self.fields['email'].required = True
self.fields['first_name'].required = False
self.fields['last_name'].required = False
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
It works fine when registering, as i need allow users to put username and email but when it goes to editing profile it displays too many fields. I would not like them to be able to edit username and email. How could i disable fields in form?
Thanks for help.
You should create another form, that excludes the fields you don't want (or simply don't specify them in the fields list). Then pass the 2 different forms to the registration and edit-profile views.
Try removing 'username' and 'email' from fields in Meta:
class Meta:
model = User
fields = ('first_name', 'last_name')
What i did is i created new form and used it and it worked. It allows to edit fields from User model not only UserExtended. Thanks for help.
class UserProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
try:
self.fields['first_name'].initial = self.instance.user.first_name
self.fields['last_name'].initial = self.instance.user.last_name
self.fields['email'].initial = self.instance.user.email
except models.User.DoesNotExist:
pass
email = forms.EmailField(label = "Główny adres email",
help_text="",
required=True)
first_name = forms.CharField(label = "Imię",
required=False)
last_name = forms.CharField(label = "Nazwisko",
required=False)
kod_pocztowy = forms.RegexField('\d{2}-\d{3}',
required = False,
label="Kod pocztowy",
error_messages={"invalid":'Poprawna wartość to np: 41-200'})
class Meta:
model = UserExtended
exclude = ('user')
def save(self, *args, **kwargs):
u = self.instance.user
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
u.kod_pocztowy = self.cleaned_data['kod_pocztowy']
u.save()
profile = super(UserProfileForm, self).save(*args, **kwargs)
return profile

Categories

Resources