Django. Python social auth. create profiles at the end of pipeline - python

I want to add a function at the end of the auth pipeline, the function is meant to check if there is a "Profiles" table for that user, if there isn't it will create a table.
The Profiles model is a table where I store some extra information about the user:
class Profiles(models.Model):
user = models.OneToOneField(User, unique=True, null=True)
description = models.CharField(max_length=250, blank=True, null=True)
points = models.SmallIntegerField(default=0)
posts_number = models.SmallIntegerField(default=0)
Each user must have a Profiles table. So, I added a function at the end of the pipeline:
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details',
'app.utils.create_profile' #Custom pipeline
)
#utils.py
def create_profile(strategy, details, response, user, *args, **kwargs):
username = kwargs['details']['username']
user_object = User.objects.get(username=username)
if Profiles.ojects.filter(user=user_object).exists():
pass
else:
new_profile = Profiles(user=user_object)
new_profile.save()
return kwargs
I get the error:
KeyError at /complete/facebook/
'details'
...
utils.py in create_profile
username = kwargs['details']['username']
I'm new to python social auth, and it looks that I'm missing something obvious. Any help will be appreciated.

Ok so I'll answer my own question just in case it is useful for someone in the future. I'm no expert but here it is:
I was following this tutorial, and becouse he does
email = kwargs['details']['email']
I thought I could do
username = kwargs['details']['username']
But it didn't work, it gave my a KeyError.
Then I tried:
username = details['username']
And it worked. But I had a new problem, the username from the details dict was something like u'Firstname Lastname' and when I tried to get the User object
user_object = User.objects.get(username=username)
It was not found, becouse the username in the User model was u'FirstnameLastname' (without the space).
Finally I read the docs again and I found out that I could simply use the user object directly, it gets passed to the function as "user":
def create_profile(strategy, details, response, user, *args, **kwargs):
if Profiles.objects.filter(usuario=user).exists():
pass
else:
new_profile = Profiles(user=user)
new_profile.save()
return kwargs

Related

Is it possible in Django to have 2 different types of users with theirs own login fields in the same app?

I have 2 types of users on my site, one is the store owner, I want to log him in with the usual custom user email and password, the other is the buyer, I want to login the buyer using just a pin number only. Is it possible to have both types of login users in the same django app. Thanks in advance.
class Store(models.Model):
store_name = models.CharField(max_length=200)
store_status = models.BooleanField()
store_details = models.CharField(max_length=300, blank = True)
store_balance = models.IntegerField(default=0)
user = models.OneToOneField(User, on_delete=models.CASCADE)
college = models.ForeignKey(Institute, on_delete=models.CASCADE )
def __str__(self):
return str(self.store_name)+" "+ str(self.store_status)
class Customer(models.Model):
name = models.CharField(max_length=200)
branch = models.CharField(max_length=200, choices=BRANCHES)
sem = models.CharField(max_length=200, choices=SEMESTERS)
reg_no = models.IntegerField(default=0)
balance = models.IntegerField(default=0)
pin_no = models.IntegerField()
college = models.ForeignKey(Institute, on_delete=models.CASCADE )
To make a custom authentication you need to add an Authentication Backend. Firstly your customer model is not related to your user model try adding a OnetoOne field in that. After that try adding code like this in one of your apps:-
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
try:
customer = Customer.objects.get(pin_no=token)
user = customer.user
except Customer.DoesNotExist:
return None
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Refer the documentation on Customizing authentication in Django for more information.
Now after making an AuthenticationBackend you need to make it so that Django uses it, you do this by adding it to AUTHENTICATION_BACKENDS in your settings.py, since you want the default username and password to remain something like this would work:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'path.to.MyBackend']
You have several ways for this. One is making an index place where they choose the option "owner" or "buyer" and after ask for login auth. Other approach is to make url specifying those options already. If you want to do it in the same "form" you could add specification under the hood for manage this input data provided also could be done by several ways, or make a checkbox like so it changes form input. Choose the one that suits you. Does this help you?

Exception in django: Query set have no attribute user

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.

Django auto increment model

I design Django Model as shown below :
`
class User(models.Model):
id=models.AutoField(primary_key=True)
username=models.CharField(max_length=25,verbose_name="username")
name=models.CharField(max_length=20,default="noName",verbose_name="name")
password=models.CharField(max_length=20,verbose_name="password")
email=models.EmailField()
userCode=models.CharField(default=" ",max_length=100,verbose_name="User Code")
def __str__(self):
return self.username
class Meta:
ordering = ['-username']`
Then I create an object in view.py. Although id is auto incremented, python want me to define an id. View.py is shown below.
def register(request,id):
if request.method=='POST':
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
name=form.cleaned_data["name"]
password = form.cleaned_data["password"]
email=form.cleaned_data.get("email")
newUser = User(1,username,name,password,email)
newUser.save()
return redirect('mainPage')
else:
form=RegisterForm()
context = {
"form" : form,
"id":id
}
return render(request,"SignupLogin.html",context)
User(1,username,name,password,email) in that line, 1 is the id number. When I delete it, error which is about missing variable id, is thrown. How can I get rid of 1?
Just introduce your parameters to model init function. That might help :)
newUser = User(username=username, name=name, password=password, email=email)
and if you have nullable fields (null=True) you must describe it on model.
You do not need to declare an autoincrementing primary key field by hand – Django implicitly adds an id field for you if there is none.
Your problem is likely not having set editable=False on that manually created field of yours.

DRF: Serializer validation while submitting a POST request to detail route

DRF newbie here. I have the following model:
class User(models.Model):
first_name = models.CharField(max_length=30, null=True, blank=True)
last_name = models.CharField(max_length=30, null=True, blank=True)
email = models.EmailField(max_length=254, null=False, blank=False, unique=True)
password = models.CharField(max_length=128, null=False, blank=False)
I've managed to implement POST /users/ successfully. I'm able to validate password and email fields in the request body against pre-defined constraints.(e.g. password cannot be entirely numeric) For this purpose, I override field validators such as validate_password and validate_email.
Now I'm trying to implement an endpoint POST /users/pk/password/ through which users will be able to update their password resource. To achieve this I've used detail_route. Below you can find the corresponding implementation:
# Custom method to update the password resource of a given user.
#detail_route(methods=['post'])
def password(self, request, pk=None):
try:
user = User.objects.get(pk=pk)
except User.DoesNotExist:
# returns error
else:
password = request.data.get('password',None)
new_password = request.data.get('new_password',None)
if password and new_password:
if check_password(password,user.password):
# Use Django built-in to hash password.
password_hash = make_password(new_password)
user.password = password_hash
user.save()
serializer_data = UserSerializer(user).data
return Response(serializer_data)
else:
# return error reponse
else:
# return error response
By using this approach, I'm able to update the password field of a user but validate_password is not effective anymore when POST /users/pk/password/ is called so that users can update their password with an entirely numeric one.
I'm aware of the fact that I can try to implement validate_password inside the detail route implementation, but it does not feel like the best way to go.
My question is what is the best way to do this without duplicating code and without moving validation logic into views.py?
PS: I cannot use nor extend Django User model for various reasons.
Thanks!
This the problem, you are getting the data straight off the raw post, when you are supposed to be using the serializer
password = request.data.get('password',None)
new_password = request.data.get('new_password',None)
Now assuming your serializer with the validation code is called MySerializer, the above lines need to be replaced with something like
serial = MySerializer(data=request.data)
if serial.is_valid():
password = serial.validated_data['password']
new_password = serial.validated_data['new_password']
Now your validation code will be executed.

Django, Update Profile, exclude current user email

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.

Categories

Resources