Cliff
Cliff

Reputation: 11238

Django-Social-Auth Errors

Update I tried replacing everything in my custom manager with the following:

def create_user(self, username, email):
        return self.model._default_manager.create(username=username)

And that throws an error. I then tried returning the User from my custom user manager and I get 'Cannot assign "": "UserSocialAuth.user" must be a "Barbuser" instance.' thrown from associate_user. It comes from the bowels of django.db.models.fields.related.py. Basically, I'm stuck with knowing how to correctly create users from my custom model mgr. I was going directly off of the docs which lead me to copying everything from django's built in ModelManager. Help? Update

I'm having trouble configuring django-social-auth. I've been at this for 3-4 days and I'm getting ready to throw in the towel. I have a working existing user registration app installed and I then installed and followed along with the docs on django-social-auth github site. I added the following to my settings.py

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'polls',
    'barbuser',
    'social_auth',
)

AUTHENTICATION_BACKENDS = (
    'social_auth.backends.facebook.FacebookBackend',
    'django.contrib.auth.backends.ModelBackend',
)

FACEBOOK_APP_ID              = os.environ.get('FACEBOOK_APP_ID')
FACEBOOK_API_SECRET          = os.environ.get('FACEBOOK_SECRET')

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'social_auth.context_processors.social_auth_by_type_backends',
)

#SOCIAL_AUTH_ENABLED_BACKENDS = ('facebook',)
SOCIAL_AUTH_DEFAULT_USERNAME = 'new_social_auth_user'

LOGIN_URL          = '/login/'
LOGIN_REDIRECT_URL = '/profile/'
LOGIN_ERROR_URL    = '/login-error/'

SOCIAL_AUTH_USER_MODEL = 'barbuser.Barbuser'

My models.py looks like:

from datetime import date
from django.db import models
from django.contrib.auth.models import User
from django.db.models import BooleanField
from django.db.models.fields import DateTimeField
from django.utils import timezone
from django.utils.crypto import get_random_string

class BarbuserManager(models.Manager):
    @classmethod
    def normalize_email(cls, email):
        """
        Normalize the address by converting the domain part of the email address to lowercase.
        """
        email = email or ''
        try:
            email_name, domain_part = email.strip().rsplit('@', 1)
        except ValueError:
            pass
        else:
            email = '@'.join([email_name, domain_part.lower()])
            return email

    def create_user(self, username, email=None, password=None):
        """
        Creates and saves a User with the given username, email and password.
        """
        email = '[email protected]' if email.strip() == '' else email
        now = timezone.now()
        if not username:
            raise ValueError('The given username must be set')
        email = BarbuserManager.normalize_email(email)
        user = User(username=username, email=email,
            is_staff=False, is_active=True, is_superuser=False,
            last_login=now, date_joined=now)
        user.set_password(password)
        user.save()
        barbuser = Barbuser(user=user, birthday=date.today(), last_login=user.last_login, name=username)
        barbuser.save()
        return barbuser

    def create_superuser(self, username, email, password):
        u = self.create_user(username, email, password)
        u.is_staff = True
        u.is_active = True
        u.is_superuser = True
        u.save(using=self._db)
        return u
    def make_random_password(self, length=10,
                             allowed_chars='abcdefghjkmnpqrstuvwxyz'
                                           'ABCDEFGHJKLMNPQRSTUVWXYZ'
                                           '23456789'):
        """        Generates a random password with the given length and given        allowed_chars. Note that the default value of allowed_chars does not        have "I" or "O" or letters and digits that look similar -- just to        avoid confusion.        """
        return get_random_string(length, allowed_chars)

    def get_by_natural_key(self, username):
        return self.get(username=username)

class Barbuser(models.Model):
    user = models.OneToOneField(User)
    username = models.CharField(max_length=200)
    last_login = DateTimeField(blank=True)
    is_active  = BooleanField(default=True)
    birthday = models.DateField()
    name = models.CharField(max_length=200)
    objects = BarbuserManager()

    def __init__(self, *args, **kwargs):
        me = super(Barbuser, self).__init__(*args, **kwargs)
        barbuser = me
        return me


    def __unicode__(self):
        return self.name

    def is_authenticated(self):
        return self.user.is_authenticated()

I've updated my urls.py to include 'social_auth.urls' and after authentication the user is redirected to ViewProfile view from my views.py:

# Create your views here.
from barbuser.forms import RegistrationForm, LoginForm
from barbuser.models import Barbuser
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext

def create_Barbuser(form):
    user = User.objects.create_user(form.cleaned_data['username'], form.cleaned_data['email'], form.cleaned_data['password'])
    user.save()
    barbuser = Barbuser(user=user, name=form.cleaned_data['name'], birthday=form.cleaned_data['birthday'])
    barbuser.save()


def process_form(form, request_context):
    if form.is_valid():
        create_Barbuser(form)
        return HttpResponseRedirect('/profile/')
    else:
        return render_to_response('register.html', {'form': form}, context_instance=request_context)


def render_blank_registration_form(request):
    '''When the user is not submitting the form, show them the blank registration form.'''
    form = RegistrationForm()
    context = {'form': form}
    return render_to_response('register.html', context, context_instance=RequestContext(request))


def BarbuserRegistration(request):
    """
    Handles the registration of new Barbwire users.
    """
    if request.user.is_authenticated():
        return HttpResponseRedirect('/profile/')
    if request.method == "POST":
        return process_form(RegistrationForm(request.POST), RequestContext(request))
    else:
        return render_blank_registration_form(request)

def LoginRequest(request):
    '''
    Handles Login requests.
    '''
    if request.user.is_authenticated():
        return HttpResponseRedirect('/profile/')
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            barbuser = authenticate(username=username, password=password)
            if barbuser is not None:
                login(request, barbuser)
                return HttpResponseRedirect('/profile/')
            else:
                return render_to_response('login.html', {'form' : form}, context_instance=RequestContext(request))
        else:
            return render_to_response('login.html', {'form' : form}, context_instance=RequestContext(request))
    else:
        form = LoginForm()
        return render_to_response('login.html', {'form' : form}, context_instance=RequestContext(request))

def LoginError(request):
    return render_to_response('login.html', {'form' : LoginForm()}, context_instance=RequestContext(request))

def LogoutRequest(request):
    logout(request)
    return HttpResponseRedirect('/')

def ViewProfile(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/login/')
    else:
        return render_to_response('profile.html',{'barbuser' : request.user.barbuser }, context_instance=RequestContext(request))

My problem is 2-fold. When I add this extra stuff in my models.py:

def facebook_extra_values(sender, user, response, details, **kwargs):
    return False

from social_auth.signals import pre_update
from social_auth.backends.facebook import FacebookBackend

pre_update.connect(facebook_extra_values, sender=FacebookBackend)

I get errors on server startup: assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.class._name_, to, RECURSIVE_RELATIONSHIP_CONSTANT) AssertionError: ForeignKey(None) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string 'self'

When I remove it I can go thru the login with facebook flow but I get: AttributeError at /complete/facebook/ 'NoneType' object has no attribute 'extra_data'

I'm not sure what I'm missing or where I've gone wrong. could somebody help explain where I'm going wrong?

Update I've traced the problem in debug and apparently I'm getting an IntegrityError in social_auth.backends.pipeline.social.py in the associate_user function when it tries "UserSocialAuth.objects.create". It then falls back into an Except block that calls social_auth_user() and this function returns None for the social_user. The IntegrityError I'm getting is:

insert or update on table "social_auth_usersocialauth" violates foreign key constraint "social_auth_usersocialauth_user_id_fkey"
DETAIL:  Key (user_id)=(17) is not present in table "auth_user".

I'm not familiar enough to know how, where or why to associate a social_user with the user created in my CustomUserManager in my models.py. Also I've removed the facebook_extra_values, extra imports, and the preupdate.connect stuff from the bottom of my models.py since I really don't understand what it does or what it's for. I was merely copying things from the example app trying to fix my first problem with the missing association. Help? Update

Upvotes: 2

Views: 5001

Answers (1)

JCotton
JCotton

Reputation: 11760

The first problem — the ForeignKey error — is caused by a circular import. The solution is easy and follows convention. Take that signal registering code block at the end of models.py and move it to a new file called barbuser/signals.py. Then in barbuser/__init__.py put the line, from .signals import *

I haven't run your code far enough to get your second error — 'NoneType' object has no attribute 'extra_data' — but I found a couple other issues.

Remove the SOCIAL_AUTH_ENABLED_BACKENDS setting from settings.py. I'm not sure where you got that from, but it's not in the documentation for the current social-auth 0.7.0. It's not in the social_auth code either. Probably an old setting.

You reference a non-existent context processor in settings.py (social_auth_login_redirect). Again, maybe an old function. Make sure you are running the newest social-auth (available through PyPi with pip install django-social-auth). Also, use only the context processors you need, never all of them. (Context processors add variables to the template context. If you aren't using the social_auth variable in your templates then you don't need any of the social-auth context processors.) This is important because two of the processors conflict with each other. From the documentation,

social_auth_backends and social_auth_by_type_backends don't play nice together.

Make sure you've run ./manage.py syncdb to setup the database for the social-auth Model. If there is no database table for the model, that would cause "UserSocialAuth.objects.create()" to break.

Last thought, Django doesn't like the type of thing you are doing defining your own User Model. It's best to leave auth.User alone and make a UserProfile.

UPDATE

Check your database structure; I suspect the indexes are incorrect. Better yet, just delete the three social_auth_* tables and syncdb again. The UserSocialAuth table has a ForeignKey Constraint to the User Model. It should be mapping "user_id" to "Barbuser.id" but if the table was created before you set the settings.py/SOCIAL_AUTH_USER_MODEL value, then the table will be forever broken because SocialAuth defaulted to Django's Auth.User when the initial SQL was run. Make sense? Anyway, just recreate the tables now that you have Social Auth configured.

Note: Your recent experiment using self.model._default_manager.create() isn't accomplishing anything. "self" is a BarbuserManager; "model" is a Barbuser; "_default_manager" is back to a BarbuserManager. Why? "Barbuser.objects" is the default manager for the Model.

Oh, and you had it correct the first time. BarbuserManager.create_user() needs to return a Barbuser.

Upvotes: 4

Related Questions