python-social not saving response to custom model - django

Ive been running into a number of problem in relation to using django's custom model. This one in particular is not raising any errors. For some reason after authenticating via steam and returning to the landing page the database tables for both steamuser_user (custom user) and social_auth_usersocialauth are empty. Nothing is being saved, no errors are being displayed etc.
My custom model which is quite similar to the one on django docs official page is as follows:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
# Create your models here.
class UserManager(BaseUserManager):
def create_user(self, steamid, username, password=None):
if not steamid:
msg = 'User has no Steam ID set'
raise ValueError(msg)
if not username:
msg = 'User has no name set'
raise ValueError(msg)
user = self.model(steamid=steamid,
username=username)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, steamid, username, password):
super_user = self.create_user(steamid=steamid,
username=username,
password=password)
super_user.is_staff = True
super_user.is_admin = True
super_user.is_mod = True
super_user.save(using=self._db)
return super_user
class User(AbstractBaseUser):
steamid = models.CharField(max_length=20, unique=True)
username = models.CharField(max_length=80)
email = models.EmailField(null=True,blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
is_mod = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
reputation = models.IntegerField(max_length=6, default=0)
USERNAME_FIELD = 'steamid'
objects = UserManager()
def __unicode__(self):
return self.username
def get_full_name(self):
return self.steamid
def get_short_name(self):
return self.username
The settings I've used are as follows:
SOCIAL_AUTH_USER_MODEL = 'steamuser.User'
AUTH_USER_MODEL = 'steamuser.User'
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
'social.apps.django_app.context_processors.backends',
'social.apps.django_app.context_processors.login_redirect',
)
AUTHENTICATION_BACKENDS = (
'social.backends.steam.SteamOpenId',
'django.contrib.auth.backends.ModelBackend',
)
#Steam OpenAuth
SOCIAL_AUTH_STEAM_API_KEY = 'B1D7C629D093D4B72577F2F11DE4EBE2'
LOGIN_URL = '/'
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/'
SOCIAL_AUTH_ENABLED_BACKENDS = (
'steam',
)
Any help would be appreciated!
EDIT
Backends steam.py
def get_user_details(self, response):
player = self.get_json(USER_INFO, params={
'key': self.setting('API_KEY'),
'steamids': self._user_id(response)
})
if len(player['response']['players']) > 0:
player = player['response']['players'][0]
details = {'steamid': player.get('steamid'),
'username': player.get('personaname'),
}
else:
details = {}
return details
EDIT 2
Well despite my logical reasoning, I just gave up and created a custom pipeline to create the new steam user as follows:
from django.contrib.auth import get_user_model
def create_steamuser(details, user=None, *args, **kwargs):
if user:
return {'is_new': False}
if not details:
return
try:
steam_user = get_user_model().objects.get(steamid=details['steamid'])
except steam_user.DoesNotExist:
get_user_model().objects.create_user(details['steamid'], details['username'])
return {
'is_new': True,
}
Now I still have the problem where social_user is not being created. I've set the social user model to use my new custom model but there must be something that I am missing.

python-social-auth won't be able to pass the steamid and date_joined parameters to your custom create_user() method in the manager. To make that possible you have three options:
Set =None to those parameters and set some default vaules for them
Override the default create_user pipeline and pass the extra parameters.
Add a custom pipeline function before create_user and fill details with steamid and date_joined, then define SOCIAL_AUTH_STEAM_USER_FIELDS = ('username', 'email', 'steamid', 'date_joined').

Related

Django-invitations, Django-Allauth: pass model field data from custom invitation to (custom) user object

I'm combining django-invitations with django-allauth for user invitation and signup.
I'd like the Administrator (when creating an invitation through the Django Admin) to provide extra data (here a foreign key to Patient object). This is archieved by adding an extra field to the (custom) invitation model:
class PatientInvitation (AbstractBaseInvitation):
email = models.EmailField(unique=True, verbose_name=_('e-mail address'),
max_length=app_settings.EMAIL_MAX_LENGTH)
created = models.DateTimeField(verbose_name=_('created'),
default=timezone.now)
patient = models.ForeignKey(Patient, blank=True, null=True, on_delete=models.CASCADE)
#classmethod
def create(cls, email, inviter=None, patient=None, **kwargs):
key = get_random_string(64).lower()
instance = cls._default_manager.create(
email=email,
key=key,
inviter=inviter,
patient=patient,
**kwargs)
return instance
def key_expired(self):
expiration_date = (
self.sent + datetime.timedelta(
days=app_settings.INVITATION_EXPIRY))
return expiration_date <= timezone.now()
def send_invitation(self, request, **kwargs):
current_site = kwargs.pop('site', Site.objects.get_current())
invite_url = reverse('invitations:accept-invite',
args=[self.key])
invite_url = request.build_absolute_uri(invite_url)
ctx = kwargs
ctx.update({
'invite_url': invite_url,
'site_name': current_site.name,
'email': self.email,
'key': self.key,
'inviter': self.inviter,
})
When the invited user signs up, I would like this data to end up in the Custom user model:
class customUser(AbstractUser):
username_validator = MyValidator()
is_patient = models.BooleanField(default=False)
patient = models.ForeignKey(Patient, null=True, blank=True, on_delete=models.CASCADE)
username = models.CharField(
_('username'),
max_length=150,
unique=True,
)
I've looked into the signals to pass the data, but couldn't find how exactly to do this.
Another option seems to add the PK of the foreign key to a hidden field on the signup form (this seems unsafe though).
I'm a bit stuck on this one, so if anyone could point me in the right direction, It would be greatly appreciated :)
Regards,
Joris
Use allauth's signals instead; you'll have much more options and you can still accomplish exactly what you want.
I would create signals.py in your app directory.
Register your signals file under your apps.py like so:
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'app_name'
def ready(self): ###<-----------
import app_name.signals ###<-----------
Use the user_signed_up signal to update the user with the invitation's data:
signals.py
from allauth.account.signals import user_signed_up
from invitations.utils import get_invitation_model
def user_signed_up(request, user, **kwargs):
try:
Invitation = get_invitation_model() ### Get the Invitation model
invite = Invitation.objects.get(email=user.email) ### Grab the Invitation instance
user.patient = invite.patient ### Pass your invitation's patient to the related user
user.save()
except Invitation.DoesNotExist:
print("this was probably not an invited user.")

Password error when customizing Django authentication

I'm trying to customize the authentication that comes by default in Django with a table of Mysql already made (collaborators).
When I want to register a user (with the command python manage.py createsuperuser) I get the following error:
django.db.utils.OperationalError: (1054, "Unknown column 'collaborators.password' in 'field list'").
That error mentions that the password column does not exist, and indeed I do not have it in the table, is there any way to indicate that the password is obtained from a column called contrasena_general?
I attach my code.
models.py
class MyUserManager(BaseUserManager):
use_in_migrations = True
def create_superuser(self, no_colaborador, nombres_colaborador, apellido_paterno_colaborador, apellido_materno_colaborador,
id_plantel, id_area_colaborador, no_empleado_sup, contrasena_general, empleado_interno):
user = self.model(no_colaborador = no_colaborador, nombres_colaborador = nombres_colaborador,
apellido_paterno_colaborador = apellido_paterno_colaborador, apellido_materno_colaborador = apellido_materno_colaborador,
id_plantel = id_plantel, id_area_colaborador = id_area_colaborador, no_empleado_sup = no_empleado_sup,
contrasena_general = contrasena_general, empleado_interno = empleado_interno,)
user.set_password(contrasena_general)
user.save(using=self._db)
return user
class Colaboradores(AbstractBaseUser):
no_colaborador = models.IntegerField(primary_key=True)
nombres_colaborador = models.CharField(max_length=150)
apellido_paterno_colaborador = models.CharField(max_length=150)
apellido_materno_colaborador = models.CharField(max_length=150)
id_plantel = models.IntegerField()
id_area_colaborador = models.IntegerField()
id_centro_costos = models.IntegerField()
no_empleado_sup = models.IntegerField()
contrasena_general = models.CharField(max_length=100)
empleado_interno = models.CharField(max_length=10)
objects = MyUserManager()
USERNAME_FIELD = "no_colaborador"
class Meta:
managed = False
db_table = 'collaborators'
app_label = "journal"
def __str__ (self):
return self.email
def def_full_name (self):
return self.nombres_colaborador
def has_perm(self, perm, obj=None):
return self.no_colaborador
def has_module_perms(self, app_label):
return self.no_colaborador
# backends.py
class MyAuthBackend(object):
def authenticate(self, no_colaborador, contrasena_general):
try:
user = Colaboradores.objects.get(no_colaborador=no_colaborador)
if user.check_password(contrasena_general):
return user
else:
return None
except Colaboradores.DoesNotExist:
logging.getLogger("error_logger").error("user with login %s does not exists " % login)
return None
except Exception as e:
logging.getLogger("error_logger").error(repr(e))
return None
def get_user(self, no_colaborador):
try:
user = Colaboradores.objects.get(no_colaborador=no_colaborador)
if user.is_active:
return user
return None
except Colaboradores.DoesNotExist:
logging.getLogger("error_logger").error("user with %(no_colaborador)d not found")
return None
# setting.py
...
AUTH_USER_MODEL = 'journal.Colaboradores'
AUTHENTICATION_BACKENDS = ('clientes.backends.MyAuthBackend', 'django.contrib.auth.backends.ModelBackend',)
How could you indicate that the password is obtained from a column in the table that is not called a password?
I think creating your own createsuperuser command would solve this issue.
Ref: Is it possible to make my own createsuperuser command in django?
You can make your based on the the original source code for Django's createsuperuser that can be found on Github.
As you can see from there, there is a constant on line 20 that says:
PASSWORD_FIELD = 'password'
So I guess that since you changed it in the model to contrasena_general, you need to make your own custom command.

Django: authenticate() returns none except one case

So i am stuck at a point in my project. I am trying to create users and I have extended User model using the OneToOnefield method as recommended in Documentation to create a user profile. There is no problem in creation of the user as all the details are correctly stored in both the auth_user and appname_userprofile table.
The problem I get is when I try to login with any user stored in the auth_user table. Except one case( which i created before creating the userProfile model). So in my auth_user Table the pass for this one case is encoded but for the remaining cases it's plain text.
Here is my function handling the view -
def login_view(request):
if request.method == 'POST':
print(request.body)
username = request.POST.get('id_username')
password = request.POST.get('id_password')
print(username, password)
try:
import pdb; pdb.set_trace()
user = authenticate(username=username, password=password)
print('This is ',user)
if user is not None:
login(request,user)
else:
return redirect('fileupload')
print('This is ',user).... some more code
Now when i pass the credentials for this one case and try to authenticate. the login works(except my code gets stuck at some later point because this user doesn't have any profile).
When i pass the credentials for user created after creating the UserProfile model. The
authenticate(username=username, password=password)
function returns
None
I have included this in my settings.py -
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
This is my model -
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 UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
contact_no = models.CharField(max_length=10, null=True)
# email = models.EmailField()
department = models.CharField(max_length=25, null=True)
status = models.IntegerField(null=True)
industry_segment = models.CharField(max_length=50, null=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.IntegerField(null=True)
updated_at = models.DateTimeField(auto_now=True)
updated_by = models.IntegerField(null=True)
#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.userprofile.save()
This is how i am saving the users -
def user_view(request):
print(request.user)
if request.is_ajax():
print(request.user)
data = json.load(request)
u_name = data['txtfirstName'] + " " + data['txtlastName']
user=User(username=u_name,password='test123',email=data['txtemailId'])
user.save()
# userprofile= UserProfile.objects.create(user=request.user)
user.userprofile.contact_no = data['txtcontactNo']
# user.userprofile.email = data['txtemailId']
user.userprofile.department = data['txtdeptvalue']
user.userprofile.status = data['txtstatusvalue']
user.userprofile.industry_segment = 'Software'
user.userprofile.created_at = datetime.datetime.now()
user.userprofile.created_by = request.user.id
user.userprofile.updated_at = datetime.datetime.now()
user.userprofile.updated_by = request.user.id
user.save()
ignore the formatting of this code... but it is working
So can anyone help me with the authentication problem ?
You must not create users like that. The password will not be hashed, so will never match in authenticate. You must always use the create_user manager method.
user = User.objects.create_user(username=u_name, password='test123', email=data['txtemailId'])

Storing additional information in django using absrtactuser

I have created custom Userclass in django(AbstarctUser). Everything works fine but my password is getting stored as plain text in database even after registering in admin.py. I do not have any forms.py explicitly defined.
Also I am using nested serializers following tutorial.
My code is as below
from django.contrib import admin
from .models import BasicUserInfo
from django.contrib.auth.admin import UserAdmin
class BasicUserAdmin(UserAdmin):
pass
admin.site.register(BasicUserInfo, BasicUserAdmin)
Edited to add Models and views
Models.py
class BasicUserInfo(AbstractUser):
email = models.EmailField(primary_key=True, unique=True, db_index=True)
class UserInfo(models.Model):
user = models.ForeignKey(BasicUserInfo, on_delete=models.CASCADE)
Views.py
serializer = AddUserSerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
serializer.create(validated_data=request.data)
Serializers.py
class BasicUserSerializer(serializers.ModelSerializer):
class Meta:
model = BasicUserInfo
fields = ('username', 'password', 'email')
print("hete")
def create(self, validated_data):
retval = BasicUserInfo.objects.create(**validated_data)
password = validated_data.pop('password')
self.password = make_password(password)
# self._password = password
return retval
class AddUserSerializer(serializers.ModelSerializer):
user = BasicUserSerializer(required=True)
class Meta:
model = UserInfo
fields = ('phoneNo')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = BasicUserSerializer.create(BasicUserSerializer(), validated_data=user_data)
user_info, created = UserInfo.objects.update_or_create(user=user, phoneNo=validated_data.pop('phoneNo'))
return user_info
The trick is to use user.set_password(password) -> this internally triggers the password hashing mechanism: Here's the Django code that does this:
def set_password(self, raw_password):
self.password = make_password(raw_password)
self._password = raw_password
def make_password(password, salt=None, hasher='default'):
"""
Turn a plain-text password into a hash for database storage
Same as encode() but generate a new random salt. If password is None then
return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
which disallows logins. Additional random string reduces chances of gaining
access to staff or superuser accounts. See ticket #20079 for more info.
"""
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
return hasher.encode(password, salt)
So the problem is serializers.create(**validated_data) is not performing the make_password operation. The above answer works perfectly fine, except it does two things differently
- It saves the user twice (once in serailizer.create and again during `user.save())
- It does not hande everything within the serializer, part of the work is being split b/w the serializer and the view.
If you want to keep it all within the serializer, you can do the following:
class AddUserSerializer(serializers.ModelSerializer):
class Meta:
model = BasicUserInfo
def validate_password(self, value):
return make_password(value)
Update:
I've made a bunch of edits; and tried to explain why. Please read patiently, and incorporate changes as you see fit.
class BasicUserSerializer(serializers.ModelSerializer):
class Meta:
model = BasicUserInfo
fields = ('username', 'password', 'email')
def validate_password(self, value):
return make_password(value)
class AddUserSerializer(serializers.ModelSerializer):
user = BasicUserSerializer(required=True)
class Meta:
model = UserInfo
fields = ('phoneNo')
def create(self, validated_data):
user_data = validated_data.pop('user')
user_serializer = BasicUserSerializer(data=user_data)
if user_serializer.is_valid(raise_exception=True):
user = user_serializer.save()
validated_data['user'] = user
return UserInfo.objects.create(**validated_data)
You should not use like this:
serializer = AddUserSerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
serializer.create(validated_data=request.data)
if password in validated data
it is better to use like this:
password = request.data.pop('password', '')
if not password:
raise ValidationError('password must not be empty')
serializer = AddUserSerializer(data=request.data)
serializer.is_valid(raise_exception=ValueError):
user = serializer.create(validated_data=request.data)
user.set_password(password)
user.save()

Manager isn't available; User has been swapped for 'pet.Person'

I'm been using the default user model in django for quite a abit and I realize , if I need to further enhance it , I would have to create my own custom User Model in django 1.5 .
I created my custom user model and I have a function which allows users to sign in .
I think my custom user model is incompatible with my function because it wouldn't allow me to do request.user . How can I fix this so I can use request.user again?
views
def LoginRequest(request):
form = LoginForm(request.POST or None)
if request.user.is_authenticated():
username = User.objects.get(username=request.user)
url = reverse('world:Profile', kwargs = {'slug': person.slug})
return HttpResponseRedirect(url)
if request.POST and form.is_valid():
user = form.authenticate_user()
login(request, user)
username= User.objects.get(username=request.user)
person = Person.objects.get(user=request.user)
url = reverse('world:Profile', kwargs = {'slug': person.slug})
return HttpResponseRedirect(url)
return render(request, 'login.html',{'form': form})
models
class PersonManager(BaseUserManager):
def create_user(self, email,date_of_birth, username,password=None,):
if not email:
msg = 'Users must have an email address'
raise ValueError(msg)
if not username:
msg = 'This username is not valid'
raise ValueError(msg)
if not date_of_birth:
msg = 'Please Verify Your DOB'
raise ValueError(msg)
user = self.model(
email=PersonManager.normalize_email(email),username=username,date_of_birth=date_of_birth)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,email,username,password,date_of_birth):
user = self.create_user(email,password=password,username=username,date_of_birth=date_of_birth)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Person(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(verbose_name='email address',max_length=255,unique=True,db_index=True,)
username = models.CharField(max_length=255, unique=True)
date_of_birth = models.DateField()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'date_of_birth',]
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
objects = PersonManager()
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __unicode__(self):
return self.email
The problem is that User refers to django.contrib.auth.models.User and now you have got a Custom User pet.Person assuming you have in the settings.py
AUTH_USER_MODEL = "pet.Person"
you have to define User with the Custom User model and you can do this with get_user_model at the top of the file where you use User
from django.contrib.auth import get_user_model
User = get_user_model()
now you will be able to use Custom User model and the problem has been fixed.
For anyone else who might come across this problem, I also solved it by simply doing this on forms.py:
add this at the top of the forms.py file
from .models import YourCustomUser
and then add this to your forms.py CustomUser form:
class SignUpForm(UserCreationForm):
#profile_year = blaaa blaa blaaa irrelevant.. You have your own stuff here don't worry about it
# here is the important part.. add a class Meta-
class Meta:
model = YourCustomUser #this is the "YourCustomUser" that you imported at the top of the file
fields = ('username', 'password1', 'password2', #etc etc, other fields you want displayed on the form)
BIG NOTES, ATTENTION:
This code worked for my case. I have a view for signing users up, I had a problem here and I solved it, I haven't tried it for logging in users.
The include = () part is required, or you can add exclude = (), but you have to have one
Important caveat to update the above solutions...
If you're facing this kind of problem, you've probably tried various solutions around the web telling you to add AUTH_USER_MODEL = users.CustomUser to settings.py and then to add the following code to views.py forms.py and any other file that calls User:
from django.contrib.auth import get_user_model
User = get_user_model()
And then you scratch your head when you get the error:
Manager isn't available; 'auth.User' has been swapped for 'users.User'
Anytime your code references User such as:
User.objects.get()
Cause you know you already put objects = UserManager() in your custom user class (UserManager being the name of your custom manager that extends BaseUserManager).
Well as it turns out doing:
User = get_user_model() # somewhere at the top of your .py file
# followed by
User.objects.get() # in a function/method of that same file
Is NOT equivalent to:
get_user_model().objects.get() # without the need for User = get_user_model() anywhere
Perhaps not intuitive, but it turns out that that in python, executing User = get_user_model() once at the time of import does not then result in User being defined across subsequent calls (i.e. it does not turn User into a "constant" of sorts which you might expect if you're coming from a C/C++ background; meaning that the execution of User = get_user_model() occurs at the time of imports, but is then de-referenced before subsequent called to class or function/method in that file).
So to sum up, in all files that reference the User class (e.g. calling functions or variables such as User.objects.get() User.objects.all() User.DoesNotExist etc...):
# Add the following import line
from django.contrib.auth import get_user_model
# Replace all references to User with get_user_model() such as...
user = get_user_model().objects.get(pk=uid)
# instead of user = User.objects.get(pk=uid)
# or
queryset = get_user_model().objects.all()
# instead of queryset = User.objects.all()
# etc...
Hope this helps save others some time...
In forms.py
# change
from django.contrib.auth.models import User
# to
from django.contrib.auth import get_user_model
Then add the following code at the top
User = get_user_model()
All the solutions provided above did not work in my case. If you using Django version 3.1 there is another solution for you:
In auth/forms, comment out line 10 and change the model in line 104 & 153 to your defined model.