Problem
- I have an invite system in my django app that allow users to invite other people (using their email) to join their team.
- When an invited user clicks on an invitation, they are redirected to an url that carries informations about the specific team the user was invited to.
- Such url calls a view called
accept_invitationthat handles the following use-cases:
a) If a user is logged-in already AND they are also the invited user (i check the email of the invite), I add the user to the team and redirect them to the team's page.
b) If a user is logged-in already BUT they are not the invited user, I redirect them to a page where I invite them to log-in with a different account.
c) If a user is logged-out, I redirect them to a page where I invite them to log-in and/or sign-up to continue.
Use-case a) works just fine but use-case b) and c) I had to figure out how to propagate the informations of the invite to the following screens and past the login.
My initial idea was to add the parameters of the invitation to the request.session so that after the login-in I could have retrieved the informations from the request inside a MyAccountAdapter(DefaultAccountAdapter) adapter I defined then send the users where they had to go.
However, I am doing something wrong and MyAccountAdapter is not getting called. I am not sure why. I think it's because of another adapter MySocialAccountAdapter I made that maybe is messing things up (and I am not even sure if it's doing what it's supposed to do honestly).
Anyway, I am lost so any help would be super appreciated.
Setup
My accept_invitation view
def accept_invitation(request, invitation_id):
invitation = TeamMemberInvitation.objects.get(id=invitation_id)
print('I am entering here, yes')
print(request)
context = {
'project': invitation.project,
'invitation': invitation
}
if invitation.key_expired():
print('expired do something')
return render(request, 'action/team_and_permission/invitation_expired.html', context)
if request.user.is_authenticated:
print('user is logged in')
if request.user.email == invitation.invitee.email:
print('the logged in user is also the one invited!')
if TeamMember.objects.filter(project=invitation.project,user=request.user).exists():
print('the logged in user is the one invited and is already a member!')
return redirect('action:project_team', project_id=invitation.project.id)
else:
print('the logged in user is the one invited but is not a member, yet!')
#accept invitation
invitation.status = INVITATION_STATUS[1][0]
#add to team member
invitation.accept_invitation(request.user)
#send him to the project
return redirect('action:project_team', project_id=invitation.project.id)
else:
print('the invited user is not the user that is currently logged in, do something!')
# accept invitation
invitation.status = INVITATION_STATUS[1][0]
# add to team member
invitation.accept_invitation(request.user)
# send him to the project
return redirect('action:project_team', project_id=invitation.project.id)
# if user is logged out
else:
print('user is logged out')
request.session['team_membership_project_id'] = str(invitation.project.id)
request.session['invitation_id'] = str(invitation.id)
return redirect('myusermodel:sign_in')
return render(request, 'action/team_and_permission/accept_invitation.html', context)
My Adapters that I hoped was going to solve my problem but didn't (so I did something wrong on them)
class MyAccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
print('I am entering here')
print(request)
if 'team_membership_project_id' in request.session:
parameters = {}
#parameters['project_id'] = request.session['team_membership_project_id']
parameters['invitation_id'] = request.session['invitation_id']
path = reverse('action:accept_invitation', urlconf=None, args=None, kwargs=parameters)
return path
path = '/'
return path
def is_open_for_signup(self, request):
"""
Checks whether or not the site is open for signups.Next to simply returning True/False you can also intervene the
regular flow by raising an ImmediateHttpResponse. (Comment reproduced from the overridden method.)
"""
return True
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
user = sociallogin.user
if user.id:
return
try:
# if user exists, connect the account to the existing account and login
customer = CustomUser.objects.get(email=user.email)
sociallogin.state['process'] = 'connect'
#sociallogin.connect(request, user)
perform_login(request, customer, 'none')
except CustomUser.DoesNotExist:
pass
my settings
...
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.sites',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'whitenoise.runserver_nostatic', # new
]
MIDDLEWARE = [
#'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
'guardian.backends.ObjectPermissionBackend',
]
SITE_ID = 3
LOGIN_REDIRECT_URL = '/'
ACCOUNT_ADAPTER = 'myusermodel.adapter.MyAccountAdapter'
# Additional configuration settings
SOCIALACCOUNT_QUERY_EMAIL = True
ACCOUNT_LOGOUT_ON_GET = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
AUTH_USER_MODEL = 'myusermodel.CustomUser'
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
],
'AUTH_PARAMS': {
'access_type': 'online',
}
}
}
...
And my Custom user model, in case this also is doing something wrong (I don't think so though)
from django.contrib.auth.base_user import BaseUserManager
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _
class CustomUserManager(BaseUserManager):
"""
Custom user model manager where email is the unique identifiers
for authentication instead of usernames.
"""
def create_user(self, email, password, **extra_fields):
"""
Create and save a User with the given email and password.
"""
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = CustomUserManager()
def __str__(self):
return self.email