Skip to content

Commit

Permalink
Merge 5de2bfa into b0bc4bd
Browse files Browse the repository at this point in the history
  • Loading branch information
bee-keeper committed Dec 6, 2015
2 parents b0bc4bd + 5de2bfa commit e9b7aa9
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 92 deletions.
117 changes: 117 additions & 0 deletions invitations/adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from django.template.loader import render_to_string
from django.contrib import messages
from django.conf import settings
from django.template import TemplateDoesNotExist
from django.core.mail import EmailMultiAlternatives, EmailMessage
from django.contrib.sites.models import Site

try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text

from .app_settings import app_settings
from .utils import import_attribute


class BaseInvitationsAdapter(object):

def stash_verified_email(self, request, email):
request.session['account_verified_email'] = email

def unstash_verified_email(self, request):
ret = request.session.get('account_verified_email')
request.session['account_verified_email'] = None
return ret

def format_email_subject(self, subject):
site = Site.objects.get_current()
prefix = "[{name}] ".format(name=site.name)
return prefix + force_text(subject)

def render_mail(self, template_prefix, email, context):
"""
Renders an e-mail to `email`. `template_prefix` identifies the
e-mail that is to be sent, e.g. "account/email/email_confirmation"
"""
subject = render_to_string('{0}_subject.txt'.format(template_prefix),
context)
# remove superfluous line breaks
subject = " ".join(subject.splitlines()).strip()
subject = self.format_email_subject(subject)

bodies = {}
for ext in ['html', 'txt']:
try:
template_name = '{0}_message.{1}'.format(template_prefix, ext)
bodies[ext] = render_to_string(template_name,
context).strip()
except TemplateDoesNotExist:
if ext == 'txt' and not bodies:
# We need at least one body
raise
if 'txt' in bodies:
msg = EmailMultiAlternatives(subject,
bodies['txt'],
settings.DEFAULT_FROM_EMAIL,
[email])
if 'html' in bodies:
msg.attach_alternative(bodies['html'], 'text/html')
else:
msg = EmailMessage(subject,
bodies['html'],
settings.DEFAULT_FROM_EMAIL,
[email])
msg.content_subtype = 'html' # Main content is now text/html
return msg

def send_mail(self, template_prefix, email, context):
msg = self.render_mail(template_prefix, email, context)
msg.send()

def is_open_for_signup(self, request):
if hasattr(request, 'session') and request.session.get(
'account_verified_email'):
return True
elif app_settings.INVITATION_ONLY is True:
# Site is ONLY open for invites
return False
else:
# Site is open to signup
return True

def clean_email(self, email):
"""
Validates an email value. You can hook into this if you want to
(dynamically) restrict what email addresses can be chosen.
"""
return email

def add_message(self, request, level, message_template,
message_context=None, extra_tags=''):
"""
Wrapper of `django.contrib.messages.add_message`, that reads
the message text from a template.
"""
if 'django.contrib.messages' in settings.INSTALLED_APPS:
try:
if message_context is None:
message_context = {}
message = render_to_string(message_template,
message_context).strip()
if message:
messages.add_message(request, level, message,
extra_tags=extra_tags)
except TemplateDoesNotExist:
pass


def get_invitations_adapter():
if hasattr(settings, 'ACCOUNT_ADAPTER'):
if settings.ACCOUNT_ADAPTER == 'invitations.models.InvitationsAdapter':
# defer to allauth
from allauth.account.adapter import get_adapter
return get_adapter()
else:
# load an adapter from elsewhere
return import_attribute(app_settings.ADAPTER)()
6 changes: 6 additions & 0 deletions invitations/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ def SIGNUP_REDIRECT(self):
""" Where to redirect on email confirm of invite """
return self._setting('SIGNUP_REDIRECT', 'account_signup')

@property
def ADAPTER(self):
""" The adapter, setting ACCOUNT_ADAPTER overrides this default """
return self._setting(
'ADAPTER', 'invitations.adapters.BaseInvitationsAdapter')

app_settings = AppSettings('INVITATIONS_')
5 changes: 2 additions & 3 deletions invitations/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model

from allauth.account.adapter import get_adapter

from .models import Invitation
from .adapters import get_invitations_adapter
from .exceptions import AlreadyInvited, AlreadyAccepted, UserRegisteredEmail


Expand All @@ -24,7 +23,7 @@ def validate_invitation(self, email):

def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
email = get_invitations_adapter().clean_email(email)

errors = {
"already_invited": _("This e-mail address has already been"
Expand Down
33 changes: 18 additions & 15 deletions invitations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
from django.core.urlresolvers import reverse
from django.conf import settings

from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.adapter import get_adapter

from .managers import InvitationManager
from .app_settings import app_settings
from .adapters import get_invitations_adapter
from . import signals


Expand Down Expand Up @@ -63,7 +61,7 @@ def send_invitation(self, request, **kwargs):

email_template = 'invitations/email/email_invite'

get_adapter().send_mail(
get_invitations_adapter().send_mail(
email_template,
self.email,
ctx)
Expand All @@ -80,15 +78,20 @@ def __str__(self):
return "Invite: {0}".format(self.email)


class InvitationsAdapter(DefaultAccountAdapter):
# here for backwards compatibility, historic allauth adapter
if hasattr(settings, 'ACCOUNT_ADAPTER'):
if settings.ACCOUNT_ADAPTER == 'invitations.models.InvitationsAdapter':
from allauth.account.adapter import DefaultAccountAdapter

class InvitationsAdapter(DefaultAccountAdapter):

def is_open_for_signup(self, request):
if hasattr(request, 'session') and request.session.get(
'account_verified_email'):
return True
elif app_settings.INVITATION_ONLY is True:
# Site is ONLY open for invites
return False
else:
# Site is open to signup
return True
def is_open_for_signup(self, request):
if hasattr(request, 'session') and request.session.get(
'account_verified_email'):
return True
elif app_settings.INVITATION_ONLY is True:
# Site is ONLY open for invites
return False
else:
# Site is open to signup
return True
Empty file added invitations/tests/__init__.py
Empty file.
Empty file.
78 changes: 78 additions & 0 deletions invitations/tests/allauth/test_allauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test.client import RequestFactory
from django.test.utils import override_settings

from nose_parameterized import parameterized
from allauth.account.models import EmailAddress

from invitations.models import Invitation, InvitationsAdapter
from invitations.adapters import get_invitations_adapter


class AllAuthIntegrationTests(TestCase):

@classmethod
def setUpClass(cls):
cls.user = get_user_model().objects.create_user(
username='flibble',
password='password')
cls.invitation = Invitation.create(
'email@example.com', inviter=cls.user)
cls.adapter = get_invitations_adapter()

@classmethod
def tearDownClass(cls):
get_user_model().objects.all().delete()
Invitation.objects.all().delete()

@parameterized.expand([
('get'),
('post'),
])
def test_accept_invite_allauth(self, method):
client_with_method = getattr(self.client, method)
resp = client_with_method(
reverse('invitations:accept-invite',
kwargs={'key': self.invitation.key}), follow=True)
invite = Invitation.objects.get(email='email@example.com')
self.assertTrue(invite.accepted)
self.assertEqual(invite.inviter, self.user)
self.assertEqual(
resp.request['PATH_INFO'], reverse('account_signup'))

form = resp.context_data['form']
self.assertEqual('email@example.com', form.fields['email'].initial)
messages = resp.context['messages']
message_text = [message.message for message in messages]
self.assertEqual(
message_text, [
'Invitation to - email@example.com - has been accepted'])

resp = self.client.post(
reverse('account_signup'),
{'email': 'email@example.com',
'username': 'username',
'password1': 'password',
'password2': 'password'
})

allauth_email_obj = EmailAddress.objects.get(
email='email@example.com')
self.assertTrue(allauth_email_obj.verified)

def test_fetch_adapter(self):
self.assertIsInstance(self.adapter, InvitationsAdapter)

@override_settings(
INVITATIONS_INVITATION_ONLY=True,
)
def test_allauth_adapter_invitations_only(self):
signup_request = RequestFactory().get(reverse(
'account_signup', urlconf='allauth.account.urls'))
self.assertFalse(
self.adapter.is_open_for_signup(signup_request))
response = self.client.get(
reverse('account_signup'))
self.assertIn('Sign Up Closed', response.content.decode('utf8'))
Empty file.
Loading

0 comments on commit e9b7aa9

Please sign in to comment.