I'm working on an app that has three "profile" models types. Each of these models has a foreign key to the contrib.auth.models User model.
I would like to have a single sign on for each model type and provide a redirect via classmethod depending on which "profile" model type is related to the logged in user.
Here's some pseudo-code to illustrate what I would like to do:
from django.contrib.auth import authenticate, login
from django.http import HttpResponse, HttpresponseRedirect
from lib.forms import Loginform #extends the built-in AuthenticationForm
def login_user(request):
if request.method == 'POST':
form = LoginForm(data=request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
user = authenticate(username=cleaned_data.get('username'), \
password=cleaned_data.get('password'))
if user:
login(request, user)
#Determine the content type of the model related to the user
#get the correct redirect value from an #classmethod
#called: after_login_redirect for that type and...
return HttpResponseRedirect(klass.after_login_redirect())
else:
response = form.errors_as_json()
return HttpResponse(json.dumps(response, ensure_ascii=False), \
mimetype='application/json')
Is it possible to leverage the ContentTypes framework to do this? or am I better off just writing a class resolver that loops over an array of the "profile" classes? I'm not seeing a way I can do this with ContentTypes from the User class, unless someone knows a workaround.
Thanks in advance,
Brandon
[SOLVED]
So, what I ended up doing was creating a UserContentType junction model that looks like this:
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
class UserContentType(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
Then, I made a pre-save signal to fire a get or create if the instance of one of the three "profile" models I have doesn't have an id:
from django.contrib.contenttypes.models import ContentType
from lib.models import UserContentType
def set_content_type_for_user(sender, **kwargs):
instance = kwargs.get('instance')
content_type = ContentType.objects.get_for_model(instance)
user = instance.user
if not instance.id:
user_content_type, \
created = UserContentType.objects.get_or_create(user=user, \
content_type=content_type, \
defaults={'user' : user, 'content_type' : content_type})
Then, in my custom login view which is posted to via ajax, I can get the content type associated with the User instance and get the URL to redirect to from the 'after_login_redirect()' classmethod on that content type. :)
def login_user(request):
if request.method == 'POST':
form = LoginForm(data=request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
user = authenticate(username=cleaned_data.get('username', ''),
password=cleaned_data.get('password', ''))
if user:
login(request, user)
user_content_type = UserContentType.objects.get(user=user)
redirect_path =
user_content_type.content_type.\
model_class().after_login_redirect_path()
response = {'redirect_path' : redirect_path}
else:
response = form.errors_as_json()
return HttpResponse(json.dumps(response, \
ensure_ascii=False), mimetype='application/json')
This way, I don't have to monkeypatch User. I hope this helps someone out. If there's a way I can improve on this, I'd love to hear some ideas.
Thanks,
Brandon
Related
hello guys in django i tryed to make a simple custom user creation form extends as UserCreationForm .everything works but after save the record in database dont have password and i dont know why is that happening.
help me plz
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class MyRegistrationForm(UserCreationForm):
email=forms.EmailField(required=True)
first_name = forms.TextInput()
last_name = forms.TextInput()
class Meta:
model=User
fields=('username','email','first_name','last_name','password1','password2')
def save(self,commit=True):
user=super(UserCreationForm,self).save(commit=False)
user.email=self.cleaned_data['email']
user.first_name=self.cleaned_data['first_name']
user.last_name=self.cleaned_data['last_name']
if commit:
user.save()
return user
and in the view the code is :
def register_user_view(request):
if request.method == 'POST':
form=MyRegistrationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/accounts/success')
args={}
args.update(csrf(request))
args['form']=MyRegistrationForm().as_ul()
print(args)
return render_to_response('register.html',args)
def register_succesfull_view(request):
return render_to_response('registersucc.html')
in database
You need to put the current class, not the parent class, in the call to super:
user=super(MyRegistrationForm, self).save(commit=False)
The way you had it meant that it was skipping the parent and going straight to the grandparent, ie ModelForm, which doesn't have the custom set_password logic.
Note though that since you are using Python 3 you don't need any parameters to super at all:
user=super().save(commit=False)
which would have avoided your problem.
I am new to django and i am trying to pass user instance from one view to another and then save data in model Category but i get this error:
ValueError at /bookmark/category/
Cannot assign "45": "Category.user" must be a "User" instance.
bookmark is the name of my application.
After registering a new user, he is redirected to another page where he selects category from a dropdown list. I wish to add this selected category in Category model with the same userid.
This is my views.py code:
def register(request):
if request.method == 'POST':
form = signUpForm(request.POST)
if form.is_valid():
user = form.save()
request.session['user'] = user.pk
return render(request,'category.html')
def get_category(request):
cname = request.POST.get("dropdown1")
user = request.session.get('user')
obj=Category(user=user,category=cname)
obj.save()
This is models.py file
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
user = models.ForeignKey(User)
category= models.CharField(max_length=100)
Can someone please guide if this is the right approach or something else is to be done?Thanks!
def get_category(request):
cname = request.POST.get("dropdown1")
user = request.session.get('user')
obj=Category(user_id=user,category=cname)
obj.save()
as user is a foreign key it needs an User Instance to get saved.
as you are having the id of the user you can use user_id for saving it
In addition to obj=Category(user_id=user,category=cname) there was also problem in my template.
I had written :
<select multiple = "dropdown1">
That is why i was getting Null constraint.
I modified it to
<select name = "dropdown1" multiple>
I am using Django's native Authorization/Authentication model to manage logins for my WebApp. This creates instances of the User model.
I would like to write a simple class-based-APIView that can tell me if a specific email is already used (IE: Is there already a user with the given email in my database?). Which generic view should I inherit from and what would the view look like?
Thanks
I would like to write a simple class-based-APIView that can tell me if
a specific email is already used (IE: Is there already a user with the
given email in my database?). Which generic view should I inherit from
and what would the view look like?
Sometimes, simple is best:
import json
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_http_methods
from django.contrib.auth import get_user_model()
#require_http_methods(['GET'])
def check_email(request, email=None):
e = get_object_or_404(get_user_model(), email=email)
return HttpResponse(json.dumps({'result': 'Found'}),
content_type="application/json")
Well you can try something like:
view
class MyView(View):
...
def post(self, request, email_address, *args, **kwargs):
response_data = {}
if User.objects.filter(email=email_address).exists():
response_data['result'] = 'success'
response_data['message'] = 'email exists'
else:
response_data['result'] = 'failed'
response_data['message'] = 'Email does not exist'
return HttpResponse(json.dumps(response_data), content_type="application/json")
url:
url(r'^something/(?P<email_address>[\w.%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4})/$',MyView.as_view()),)
I was using the q&a at
Get Primary Key after Saving a ModelForm in Django.
It's exactly on point with what I need to do.
I have the following model:
class meetingEvent(models.Model):
'''
A meeting event
'''
name = models.CharField(max_length=64, help_text="a name for this meeting")
account_number = models.ForeignKey(account)
meeting_type = models.ForeignKey(meetingType)
meeting_group = models.ForeignKey(meetingGroup)
start_time = models.DateTimeField(help_text="start time for this event")
end_time = models.DateTimeField(help_text="end time for this event")
created_time = models.DateTimeField(auto_now_add=True)
listed_products = models.ForeignKey(product)
additonal_notes = models.TextField(help_text="additional notes for this meeting")
def __unicode__(self):
return self.name
I have the following form:
class meetingEventForm(forms.ModelForm):
"""
to create a new meeting event.
"""
portal_user = forms.CharField(help_text="username to access portal data")
portal_pass = forms.CharField(widget=forms.PasswordInput, help_text="password to add access portal data")
def save(self, commit=True):
super(meetingEventForm, self).save(commit=commit)
class Meta:
model = meetingEvent
I have the following view:
def meeting_event(request):
if request.method == 'POST':
form = meetingEventForm(request.POST)
if form.is_valid():
new_agenda=form.save()
return HttpResponseRedirect(reverse('agenda_detail', args=(new_agenda.pk,)))
else:
form = meetingEventForm()
return render_to_response('agendas/event.html',{'form':form,}, context_instance=RequestContext(request))
I've confirmed that this makes it into the database cleanly.
However, I get the following error:
Traceback:
File "/usr/lib/python2.6/site-packages/Django-1.5.2-py2.6.egg/django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python2.6/site-packages/Django-1.5.2-py2.6.egg/django/contrib/auth/decorators.py" in _wrapped_view
25. return view_func(request, *args, **kwargs)
File "/var/www/html/tamtools/agendas/views.py" in meeting_event
44. return HttpResponseRedirect(reverse('agenda_detail', args=(new_agenda.pk,)))
Exception Type: AttributeError at /agendas/add/
Exception Value: 'NoneType' object has no attribute 'pk'
Has something changed in Django 1.5 that I don't know about? new_agenda should be a meetingEventForm type, shouldn't it?
You overwrite save methid in modelform, but you forgot to return model.
return super( ....
You don't need to override the ModeForm save method, since you aren't doing anything special with it. Your ModelForm should look like:
class MeetingEventForm(forms.ModelForm):
"""
to create a new meeting event.
"""
class Meta:
model = meetingEvent
I also changed the class name to conform with the Python style guide.
You also have two extra fields in the form that have nothing to do with your model. There could be two reasons - one, you need to save these fields in another model, or the second option is that you want someone to authorize themselves before they can add a new event.
Since the second one seems more plausible, restrict access to the form from your view:
from django.contrib.auth.decorators import login_required
from django.shorcuts import render, redirect
#login_required()
def meeting_event(request):
form = MeetingEventForm(request.POST or {})
context = {'form': form}
if request.method == 'POST':
if form.is_valid():
new_agenda = form.save()
return redirect('agenda_detail', args=(new_agenda.pk,))
else:
return render(request, 'agendas/event.html', context)
else:
return render(request, 'agendas/event.html', context)
As this is a common task, and you are using django 1.5, why not use the generic class based views?
Your code will be reduced, and you don't have to worry about the mundane details:
First, in your views.py, create a class that inherits from the generic CreateView which is used to display a model form for a model, let the user fill it in, and save the details:
from django.views.generic.edit import CreateView
class CreateMeetingRequest(CreateView):
template_name = 'agendas/event.html'
model = meetingRequest
Now, to map the view to a url, we add it to urls.py. Since we also want the user to be logged in before they can add a meeting request - the login_required decorator takes care of that for us. It will check if the user is logged in - if not, redirect the user to a login form and once they have logged in, redirect them back to the form:
from django.contrib.auth.decorators import login_required
from .views import CreateMeetingRequest
urlpatterns = patterns('',
# your other views
url(r'meeting-request/add/$',
login_required(CreateMeetingRequest.as_view()), name='add-meeting-req'),
)
Finally, we need to tell the view where to go once the form is successful. CreateView will check if the model has a get_absolute_url method, and call that. So in your models.py:
from django.core.urlresolvers import reverse
class meetingRequest(models.Model):
# your normal fields
def get_absolute_url(self):
return reverse('agenda_detail', args=(self.pk,))
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 = 'you#email.com' 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
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.