def beta_confirm(email, **kwargs): """ Send out email confirming that they requested an invite. """ templates_folder = setting('BETA_EMAIL_TEMPLATES_DIR', 'hunger') templates_folder = os.path.join(templates_folder, '') from_email = setting('EMAIL_HOST_USER') if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None context_dict = kwargs.copy() if templated_email_available: send_templated_mail( template_dir=templates_folder, template_name='beta_confirm', file_extension=file_extension, from_email=from_email, recipient_list=[email], context=context_dict, ) else: plaintext = get_template(os.path.join(templates_folder, 'beta_confirm.txt')) html = get_template(os.path.join(templates_folder, 'beta_confirm.html')) subject, to = 'You requested an invite!', email text_content = plaintext.render(Context()) html_content = html.render(Context()) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={'From': 'Mailer <%s>' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def invitation_code_created(sender, email, **kwargs): """Send confirmation email to user.""" email_module_name = setting('BETA_EMAIL_MODULE', 'hunger.email') email_module = importlib.import_module(email_module_name) email_function_name = setting('BETA_EMAIL_CONFIRM_FUNCTION', 'beta_confirm') email_function = getattr(email_module, email_function_name) email_function(email, **kwargs)
def __init__(self): self.enable_beta = setting('HUNGER_ENABLE') self.always_allow_views = setting('HUNGER_ALWAYS_ALLOW_VIEWS') self.always_allow_modules = setting('HUNGER_ALWAYS_ALLOW_MODULES') self.redirect = setting('HUNGER_REDIRECT') self.allow_flatpages = setting('HUNGER_ALLOW_FLATPAGES')
def beta_invite(email, request, code=None, **kwargs): """ Email for sending out the invitation code to the user. Invitation URL is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() if code: invite_url = request.build_absolute_uri( reverse('hunger-verify', args=[code])) else: invite_url = setting('HUNGER_VERIFIED_REDIRECT') context_dict.setdefault('invite_url', invite_url) context = Context(context_dict) templates_folder = setting('HUNGER_EMAIL_TEMPLATES_DIR') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', getattr(settings, 'DEFAULT_FROM_EMAIL')) if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None if templated_email_available: send_templated_mail( template_name='invite_email', from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template( os.path.join(templates_folder, 'invite_email.txt')) invite_path = os.path.join(templates_folder, 'invite_email.html') html = get_template(invite_path) subject_path = os.path.join(templates_folder, 'invite_email_subject.txt') subject = get_template(subject_path).render(context).strip() to = email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={'From': '%s' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def beta_invite(email, request, code=None, **kwargs): """ Email for sending out the invitation code to the user. Invitation URL is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() if code: invite_url = request.build_absolute_uri( reverse('hunger-verify', args=[code])) else: invite_url = setting('HUNGER_VERIFIED_REDIRECT') context_dict.setdefault('invite_url', invite_url) context = RequestContext(request, context_dict) templates_folder = setting('HUNGER_EMAIL_TEMPLATES_DIR') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', getattr(settings, 'DEFAULT_FROM_EMAIL')) if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None if templated_email_available: send_templated_mail( template_name='invite_email', from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template(os.path.join(templates_folder, 'invite_email.txt')) invite_path = os.path.join(templates_folder, 'invite_email.html') html = get_template(invite_path) subject_path = os.path.join(templates_folder, 'invite_email_subject.txt') subject = get_template(subject_path).render(context).strip() to = email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={'From': '%s' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def invitation_code_sent(sender, email, invitation_code, **kwargs): """Send invitation code to user.""" try: invitation_code = InvitationCode.objects.get(email=email) except InvitationCode.DoesNotExist: return invitation_code.is_invited = True invitation_code.invited = now() invitation_code.save() email_module_name = setting('BETA_EMAIL_MODULE', 'hunger.email') email_module = importlib.import_module(email_module_name) email_function_name = setting('BETA_EMAIL_INVITE_FUNCTION', 'beta_invite') email_function = getattr(email_module, email_function_name) email_function(email, invitation_code.code, **kwargs)
def invitation_code_sent(sender, invitation, **kwargs): """Send invitation code to user. Invitation could be InvitationCode or Invitation. """ logger.info("Sending invitation code %s %s" % (sender, invitation)) if sender.__name__ == "Invitation": email = invitation.email or invitation.user.email if invitation.code: code = invitation.code.code else: code = None elif sender.__name__ == "InvitationCode": email = kwargs.pop("email", None) code = invitation.code if not email: logger.warn("invitation_code_sent called without email") return # we can invite a user directly with no code # but, if we have no code and no user, # we eon't just open it to the email without a code. if code is None and not invitation.user: logger.warn("Invite with code+email or user") return bits = setting("HUNGER_EMAIL_INVITE_FUNCTION").rsplit(".", 1) module_name, func_name = bits module = importlib.import_module(module_name) func = getattr(module, func_name) func(email, code, **kwargs)
def beta_invite(email, code, request, **kwargs): """ Email for sending out the invitation code to the user. Invitation URL is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() context_dict.setdefault( "invite_url", request.build_absolute_uri(reverse("beta_verify_invite", args=[code])) ) context = Context(context_dict) from_email = kwargs.get('from_email', setting("DEFAULT_FROM_EMAIL")) html_content = text_content = None if kwargs.get('custom_message'): from hunger.utils import html2plain html_content = kwargs.get('custom_message').format(invite_url=context_dict['invite_url']) text_content = html2plain(html_content) MandrillMail('hunger/beta_invite.email', context=context_dict).send( from_email=from_email, recipient_list=[email], html=html_content, fulltext=text_content, )
def beta_confirm(email, **kwargs): """ Send out email confirming that they requested an invite. """ templates_folder = setting('BETA_EMAIL_TEMPLATES_DIR', 'hunger') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', setting("DEFAULT_FROM_EMAIL")) if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None context_dict = kwargs.copy() MandrillMail('hunger/beta_confirm.email', context=context_dict).send( from_email=from_email, recipient_list=[email], )
def beta_invite(email, code, request, **kwargs): """ Email for sending out the invitation code to the user. Invitation URL is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() context_dict.setdefault( "invite_url", request.build_absolute_uri(reverse("beta_verify_invite", args=[code])) ) context = Context(context_dict) templates_folder = setting('BETA_EMAIL_TEMPLATES_DIR', 'hunger') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', setting("DEFAULT_FROM_EMAIL")) if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None if templated_email_available: send_templated_mail( template_name='beta_invite', from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template(os.path.join(templates_folder, 'beta_invite.txt')) html = get_template(os.path.join(templates_folder, 'beta_invite.html')) subject, to = "Here is your invite", email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={'From': '%s' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def send_invitation(invitation, **kwargs): """Send invitation code to user. Invitation could be InvitationCode or Invitation. """ email = invitation.user.email if invitation.user else invitation.email code = invitation.code.code if invitation.code else None bits = setting('HUNGER_EMAIL_INVITE_FUNCTION').rsplit('.', 1) module_name, func_name = bits module = importlib.import_module(module_name) func = getattr(module, func_name) func(email, code=code, **kwargs)
def send_invitation(invitation, **kwargs): """Send invitation code to user. Invitation could be InvitationCode or Invitation. """ email = invitation.user.email code = invitation.code.code if invitation.code else None bits = setting('HUNGER_EMAIL_INVITE_FUNCTION').rsplit('.', 1) module_name, func_name = bits module = importlib.import_module(module_name) func = getattr(module, func_name) func(email, code=code, **kwargs)
def beta_invite(email, code, **kwargs): """ Email for sending out the invitation code to the user. Invitation code is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() context_dict.setdefault('code', code) context = Context(context_dict) templates_folder = setting('BETA_EMAIL_TEMPLATES_DIR', 'hunger') templates_folder = os.path.join(templates_folder, '') from_email = setting('EMAIL_HOST_USER') if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None if templated_email_available: send_templated_mail( template_dir=templates_folder, template_name='beta_invite', file_extension=file_extension, from_email=from_email, recipient_list=[email], context=context_dict, ) else: plaintext = get_template(os.path.join(templates_folder, 'beta_invite.txt')) html = get_template(os.path.join(templates_folder, 'beta_invite.html')) subject, to = "Here is your invite", email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={'From': 'Mailer <%s>' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def beta_invite(email, code, request, **kwargs): """ Email for sending out the invitation code to the user. Invitation URL is added to the context, so it can be rendered with standard django template engine. """ if Site._meta.installed: site = Site.objects.get_current() else: site = RequestSite(request) context_dict = kwargs.copy() context_dict.setdefault("site", site) context_dict.setdefault("code", code) if request: context_dict.setdefault("invite_url", request.build_absolute_uri(reverse("hunger-verify", args=[code]))) context = Context(context_dict) templates_folder = setting("HUNGER_EMAIL_TEMPLATES_DIR") templates_folder = os.path.join(templates_folder, "") from_email = kwargs.get("from_email", getattr(settings, "DEFAULT_FROM_EMAIL")) if templates_folder == "hunger": file_extension = "email" else: file_extension = None if templated_email_available: send_templated_mail( template_name="invite_email", from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template(os.path.join(templates_folder, "invite_email.txt")) html = get_template(os.path.join(templates_folder, "invite_email.html")) subject = get_template(os.path.join(templates_folder, "invite_email_subject.txt")).render(context).strip("\n") to = email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={"From": "%s" % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def test_invite_existing_user_without_email(self): """ Confirm that a registered user cannot join beta using a private InvitationCode meant for someone else. """ code = self.create_code(email='*****@*****.**') response = self.client.get(reverse('hunger-verify', args=[code.code]), follow=True) # Anonymous user cannot verify a private InvitationCode self.assertRedirects(response, setting('LOGIN_URL')) self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Alice should be denied, since she has no connection with email account self.assertRedirects(response, reverse('hunger-invalid', args=[code]))
def test_invite_existing_user_without_email(self): """ Confirm that a registered user cannot join beta using a private InvitationCode meant for someone else. """ code = self.create_code(email='*****@*****.**') response = self.client.get(reverse('hunger-verify', args=[code.code]), follow=True) # Anonymous user cannot verify a private InvitationCode self.assertRedirects(response, setting('LOGIN_URL')) self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Alice should be denied, since she has no connection with email account self.assertRedirects(response, reverse('hunger-invalid', args=[code.code]))
class InviteView(FormView): """ Allow a user to send invites. """ template_name = 'hunger/request_invite.html' form_class = InviteSendForm success_url = setting('HUNGER_INVITE_SENT_REDIRECT') def form_valid(self, form): valid_code = InvitationCode.objects.get(owner=self.request.user, num_invites__gt=0) form.instance.code = valid_code form.instance.invited = now() form.save() return super(InviteView, self).form_valid(form)
def invitation_code_sent(sender, invitation, **kwargs): """Send invitation code to user. Invitation could be InvitationCode or Invitation. """ if sender.__name__ == 'Invitation': email = invitation.email code = invitation.code.code elif sender.__name__ == 'InvitationCode': email = kwargs.pop('email', None) code = invitation.code bits = setting('HUNGER_EMAIL_INVITE_FUNCTION').rsplit('.', 1) module_name, func_name = bits module = importlib.import_module(module_name) func = getattr(module, func_name) func(email, code, **kwargs)
def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated(): verified_redirect = redirect(setting("HUNGER_VERIFIED_REDIRECT")) invitations = request.user.invitation_set.all() if any(i.used or i.invited for i in invitations): return verified_redirect elif invite_from_cookie_and_email(request): return verified_redirect elif not invitations: invitation = Invitation( user=request.user, email=request.user.email ) invitation.save() return super(TemplateView, self).dispatch(request, *args, **kwargs)
def beta_invite(email, code, request, **kwargs): """ Email for sending out the invitation code to the user. Invitation URL is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() context_dict.setdefault( "invite_url", "http://subreader.com:8080/activate/" + code ) context = Context(context_dict) templates_folder = setting('HUNGER_EMAIL_TEMPLATES_DIR') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', getattr(settings, 'DEFAULT_FROM_EMAIL')) if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None if templated_email_available: send_templated_mail( template_name='invite_email', from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template(os.path.join(templates_folder, 'invite_email.txt')) html = get_template(os.path.join(templates_folder, 'invite_email.html')) subject = get_template(os.path.join(templates_folder, 'invite_email_subject.txt')).render(context) to = email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers={'From': '%s' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def beta_invite(email, code, **kwargs): """ Email for sending out the invitation code to the user. Invitation code is added to the context, so it can be rendered with standard django template engine. """ context_dict = kwargs.copy() context_dict.setdefault('code', code) context = Context(context_dict) templates_folder = setting('BETA_EMAIL_TEMPLATES_DIR', 'hunger') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', '*****@*****.**') if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None if templated_email_available: send_templated_mail( template_name='beta_invite', from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template( os.path.join(templates_folder, 'beta_invite.txt')) html = get_template(os.path.join(templates_folder, 'beta_invite.html')) subject, to = "NSExtreme.com Beta Invite", email text_content = plaintext.render(context) html_content = html.render(context) msg = EmailMultiAlternatives( subject, text_content, from_email, [to], headers={'From': 'NSExtreme <%s>' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
def test_invite_non_user_public_invitation(self): """ Confirm that an unregistered user can join beta using a public InvitationCode. """ code = self.create_code(private=False) # Anonymous user can verify a public InvitationCode, but cannot # access pages behind beta until logged in. response = self.client.get(reverse('hunger-verify', args=[code.code]), follow=True) response = self.client.get(reverse('invited_only')) self.assertRedirects(response, setting('LOGIN_URL')) User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Dany is allowed in beta since he used public code earlier in session self.assertEqual(response.status_code, 200)
def beta_confirm(email, **kwargs): """ Send out email confirming that they requested an invite. """ templates_folder = setting('BETA_EMAIL_TEMPLATES_DIR', 'hunger') templates_folder = os.path.join(templates_folder, '') from_email = kwargs.get('from_email', '*****@*****.**') if templates_folder == 'hunger': file_extension = 'email' else: file_extension = None context_dict = kwargs.copy() if templated_email_available: send_templated_mail( template_name='beta_confirm', from_email=from_email, recipient_list=[email], context=context_dict, template_dir=templates_folder, file_extension=file_extension, ) else: plaintext = get_template( os.path.join(templates_folder, 'beta_confirm.txt')) html = get_template(os.path.join(templates_folder, 'beta_confirm.html')) subject, to = 'NSExtreme.com Beta Confirmation', email text_content = plaintext.render(Context()) html_content = html.render(Context()) msg = EmailMultiAlternatives( subject, text_content, from_email, [to], headers={'From': 'NSExtreme <%s>' % from_email}) msg.attach_alternative(html_content, "text/html") msg.send()
class BetaViewTests(TestCase): urls = 'tests.urls' redirect_url = setting('BETA_REDIRECT_URL', '/beta/') signup_url = setting('BETA_SIGNUP_URL', '/register/') signup_confirmation_view = setting('BETA_SIGNUP_CONFIRMATION_VIEW', '') def test_request_invite(self): """ Requesting an invite should generate a form and correct template.""" response = self.client.get(reverse('beta_invite')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'beta/request_invite.html') self.failUnless( isinstance(response.context['form'], forms.InviteRequestForm)) def test_request_invite_success(self): response = self.client.post(reverse('beta_invite'), data={'email': '*****@*****.**'}) self.assertRedirects(response, reverse('beta_confirmation')) def test_never_allow_view(self): response = self.client.get(reverse('never_allow')) self.assertRedirects(response, self.redirect_url) def test_always_allow_view(self): response = self.client.get(reverse('always_allow')) self.assertEqual(response.status_code, 200) def test_always_allow_module(self): response = self.client.get(reverse('always_allow_module')) self.assertEqual(response.status_code, 200) def test_garden_when_not_logged_in(self): response = self.client.get(reverse('logged_in_only')) self.assertRedirects( response, reverse('beta_invite') + '?next=%s' % reverse('logged_in_only')) def test_garden_when_logged_in(self): User.objects.create_user('alice', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('logged_in_only')) self.assertEqual(response.status_code, 200) self.client.logout() def test_using_invite(self): invitation = InvitationCode(email='*****@*****.**') invitation.save() response = self.client.get( reverse('beta_verify_invite', args=[invitation.code])) self.assertRedirects(response, self.signup_url) User.objects.create_user('alice', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse(self.signup_confirmation_view)) response = self.client.get( reverse('beta_verify_invite', args=[invitation.code])) self.assertRedirects(response, reverse('beta_used')) def test_invalid_invite(self): code = 'xoxoxoxo' with self.assertRaises(InvitationCode.DoesNotExist): InvitationCode.objects.get(code=code) response = self.client.get(reverse('beta_verify_invite', args=[code])) self.assertRedirects(response, self.redirect_url)
def verify_invite(request, code): response = redirect(setting('HUNGER_VERIFIED_REDIRECT')) response.set_cookie('hunger_code', code) return response
def test_garden_when_not_invited(self): """Confirm that an unauthenticated user is redirected to login""" response = self.client.get(reverse('invited_only')) self.assertRedirects(response, setting('LOGIN_URL'))
class BetaViewTests(TestCase): urls = 'tests.urls' redirect = setting('HUNGER_REDIRECT') def create_invite(self, email): code = InvitationCode(num_invites=0) code.save() invitation = Invitation(code=code, email=email, invited=now()) invitation.save() return invitation def create_code(self, private=True, email=''): code = InvitationCode(private=private) code.save() if private: invitation = Invitation(code=code, email=email, invited=now()) invitation.save() return code def setUp(self): """Creates a few basic users. Alice is registered but not in beta Bob is registered and in beta (self-signup) Charlie is in beta and has one invite """ self.alice = User.objects.create_user('alice', '*****@*****.**', 'secret') self.bob = User.objects.create_user('bob', '*****@*****.**', 'secret') right_now = now() invitation = Invitation(user=self.bob, invited=right_now, used=right_now) invitation.save() self.charlie = User.objects.create_user('charlie', '*****@*****.**', 'secret') invitation = Invitation(user=self.charlie, invited=right_now, used=right_now) invitation.save() code = InvitationCode(owner=self.charlie) code.save() def test_always_allow_view(self): response = self.client.get(reverse('always_allow')) self.assertEqual(response.status_code, 200) def test_always_allow_module(self): response = self.client.get(reverse('always_allow_module')) self.assertEqual(response.status_code, 200) def test_garden_when_not_invited(self): """Confirm that an unauthenticated user is redirected to login""" response = self.client.get(reverse('invited_only')) self.assertRedirects(response, setting('LOGIN_URL')) def test_using_invite(self): cary = User.objects.create_user('cary', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) self.assertRedirects(response, reverse(self.redirect)) response = self.client.get(reverse('invited_only')) self.assertRedirects(response, reverse(self.redirect)) invitation = Invitation.objects.get(user=cary) invitation.invited = now() invitation.save() response = self.client.get(reverse('invited_only')) self.assertEqual(response.status_code, 200) def test_user_invite(self): self.client.login(username='******', password='******') response = self.client.post(reverse('hunger-invite'), {'email': '*****@*****.**'}) self.assertEqual(response.status_code, 302) self.client.logout() # Replace with examining email body invitation = Invitation.objects.get(email='*****@*****.**') self.client.get(reverse('hunger-verify', args=[invitation.code.code])) User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Dany should be denied, since he has no connection with email account self.assertEqual(response.status_code, 302) def test_invite_non_user_with_email(self): self.create_invite(email='*****@*****.**') User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) self.assertEqual(response.status_code, 200) def test_invite_existing_user_with_email(self): self.create_invite(email='*****@*****.**') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) self.assertEqual(response.status_code, 200) def test_invite_non_user_without_email(self): code = self.create_code(email='*****@*****.**') self.client.get(reverse('hunger-verify', args=[code.code])) User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Dany should be denied, since he has no connection with email account self.assertEqual(response.status_code, 302) def test_invite_existing_user_without_email(self): code = self.create_code(email='*****@*****.**') self.client.get(reverse('hunger-verify', args=[code.code])) self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Alice should be denied, since she has no connection with email account self.assertEqual(response.status_code, 302) def test_invalid_code(self): invalid_code = 'XXXXinvalidcodeXXXX' self.client.login(username='******', password='******') response = self.client.get(reverse('hunger-verify', args=[invalid_code]), follow=True) self.assertRedirects(response, reverse('hunger-invalid', args=[invalid_code]))
def verify_invite(request, code): """Verify new invitee by storing invite code for middleware to validate.""" response = redirect(setting('HUNGER_VERIFIED_REDIRECT')) response.set_cookie('hunger_code', code) return response
import string, random from django.db import models from django.utils.translation import ugettext_lazy as _ from hunger.utils import setting from hunger.signals import invite_sent User = setting('AUTH_USER_MODEL') class Invitation(models.Model): user = models.ForeignKey(User, blank=True, null=True) email = models.EmailField(_('Email'), blank=True, null=True) code = models.ForeignKey('InvitationCode', blank=True, null=True) used = models.DateTimeField(_('Used'), blank=True, null=True) invited = models.DateTimeField(_('Invited'), blank=True, null=True) created = models.DateTimeField(_('Created'), auto_now_add=True) def save(self, *args, **kwargs): send_email = kwargs.pop('send_email', False) request = kwargs.pop('request', None) if send_email and self.invited and not self.used: invite_sent.send(sender=self.__class__, invitation=self, request=request, user=self.user) super(Invitation, self).save(*args, **kwargs) class Meta: unique_together = (('user', 'code'), )
def verify_invite(request, code): """Verify new invitee by storing invite code for middleware to validate.""" response = redirect(setting("HUNGER_VERIFIED_REDIRECT")) response.set_cookie("hunger_code", code) return response
import string, random from django.db import models from django.utils.translation import ugettext_lazy as _ from hunger.utils import setting from hunger.signals import invite_sent User = setting('AUTH_USER_MODEL') class Invitation(models.Model): user = models.ForeignKey(User, blank=True, null=True) email = models.EmailField(_('Email'), blank=True, null=True) code = models.ForeignKey('InvitationCode', blank=True, null=True) used = models.DateTimeField(_('Used'), blank=True, null=True) invited = models.DateTimeField(_('Invited'), blank=True, null=True) created = models.DateTimeField(_('Created'), auto_now_add=True) def save(self, *args, **kwargs): send_email = kwargs.pop('send_email', False) request = kwargs.pop('request', None) if send_email and self.invited and not self.used: invite_sent.send(sender=self.__class__, invitation=self, request=request, user=self.user) super(Invitation, self).save(*args, **kwargs) class Meta: unique_together = (('user', 'code'),) class InvitationCode(models.Model): code = models.CharField(_('Invitation code'), max_length=30, unique=True)
def test_settings(self): """ Confirm that settings override DEFAULT_SETTINGS """ self.assertEqual(False, setting('HUNGER_ENABLE'))
def process_view(self, request, view_func, view_args, view_kwargs): if not self.enable_beta: return if (request.path in self.allow_flatpages or (getattr(settings, 'APPEND_SLASH', True) and '%s/' % request.path in self.allow_flatpages)): from django.contrib.flatpages.views import flatpage return flatpage(request, request.path_info) whitelisted_modules = ['django.contrib.auth.views', 'django.contrib.admin.sites', 'django.views.static', 'django.contrib.staticfiles.views'] # All hunger views, except NotBetaView, are off limits until in beta whitelisted_views = ['hunger.views.NotBetaView', 'hunger.views.verify_invite', 'hunger.views.InvalidView'] short_name = view_func.__class__.__name__ if short_name == 'function': short_name = view_func.__name__ view_name = self._get_view_name(request) full_view_name = '%s.%s' % (view_func.__module__, short_name) if self.always_allow_modules: whitelisted_modules += self.always_allow_modules if '%s' % view_func.__module__ in whitelisted_modules: return if self.always_allow_views: whitelisted_views += self.always_allow_views if (full_view_name in whitelisted_views or view_name in whitelisted_views): return if not request.user.is_authenticated(): # Ask anonymous user to log in if trying to access in-beta view return redirect(setting('LOGIN_URL')) if request.user.is_staff: return # Prevent queries by caching in_beta status in session if request.session.get('hunger_in_beta'): return cookie_code = request.COOKIES.get('hunger_code') invitations = Invitation.objects.filter( Q(user=request.user) | Q(email=request.user.email) ).select_related('code') # User already in the beta - cache in_beta in session if any([i.used for i in invitations if i.invited]): request.session['hunger_in_beta'] = True return # User has been invited - use the invitation and place in beta. activates = [i for i in invitations if i.invited and not i.used] # Check for matching cookie code if available. if cookie_code: for invitation in activates: if invitation.code and invitation.code.code == cookie_code: # Invitation may be attached to email invitation.user = request.user invitation.used = now() invitation.save() request.session['hunger_in_beta'] = True request._hunger_delete_cookie = True return # No cookie - let's just choose the first invitation if it exists if activates: invitation = activates[0] # Invitation may be attached to email invitation.user = request.user invitation.used = now() invitation.save() request.session['hunger_in_beta'] = True return if not cookie_code: if not invitations: invitation = Invitation(user=request.user) invitation.save() return redirect(self.redirect) # No invitation, all we have is this cookie code try: code = InvitationCode.objects.get(code=cookie_code, num_invites__gt=0) except InvitationCode.DoesNotExist: request._hunger_delete_cookie = True return redirect(reverse('hunger-invalid', args=(cookie_code,))) right_now = now() if code.private: # User is trying to use a valid private code, but has no # authority to use it (neither via username nor email) request._hunger_delete_cookie = True return redirect(reverse('hunger-invalid', args=(cookie_code,))) else: invitation = Invitation(user=request.user, code=code, invited=right_now, used=right_now) code.num_invites -= 1 invitation.save() code.save() return
def process_view(self, request, view_func, view_args, view_kwargs): if not self.enable_beta: return if (request.path in self.allow_flatpages or (getattr(settings, 'APPEND_SLASH', True) and '%s/' % request.path in self.allow_flatpages)): from django.contrib.flatpages.views import flatpage return flatpage(request, request.path_info) whitelisted_modules = [ 'django.contrib.auth.views', 'django.contrib.admin.sites', 'django.views.static', 'django.contrib.staticfiles.views' ] # All hunger views, except NotBetaView, are off limits until in beta whitelisted_views = [ 'hunger.views.NotBetaView', 'hunger.views.verify_invite', 'hunger.views.InvalidView' ] short_name = view_func.__class__.__name__ if short_name == 'function': short_name = view_func.__name__ view_name = self._get_view_name(request) full_view_name = '%s.%s' % (view_func.__module__, short_name) if self.always_allow_modules: whitelisted_modules += self.always_allow_modules if '%s' % view_func.__module__ in whitelisted_modules: return if self.always_allow_views: whitelisted_views += self.always_allow_views if (full_view_name in whitelisted_views or view_name in whitelisted_views): return if not request.user.is_authenticated(): # Ask anonymous user to log in if trying to access in-beta view return redirect(setting('LOGIN_URL')) if request.user.is_staff: return # Prevent queries by caching in_beta status in session if request.session.get('hunger_in_beta'): return cookie_code = request.COOKIES.get('hunger_code') invitations = Invitation.objects.filter( Q(user=request.user) | Q(email=request.user.email)).select_related('code') # User already in the beta - cache in_beta in session if any([i.used for i in invitations if i.invited]): request.session['hunger_in_beta'] = True return # User has been invited - use the invitation and place in beta. activates = [i for i in invitations if i.invited and not i.used] # Check for matching cookie code if available. if cookie_code: for invitation in activates: if invitation.code.code == cookie_code: # Invitation may be attached to email invitation.user = request.user invitation.used = now() invitation.save() request.session['hunger_in_beta'] = True request._hunger_delete_cookie = True return # No cookie - let's just choose the first invitation if it exists if activates: invitation = activates[0] # Invitation may be attached to email invitation.user = request.user invitation.used = now() invitation.save() request.session['hunger_in_beta'] = True return if not cookie_code: if not invitations: invitation = Invitation(user=request.user) invitation.save() return redirect(self.redirect) # No invitation, all we have is this cookie code try: code = InvitationCode.objects.get(code=cookie_code, num_invites__gt=0) except InvitationCode.DoesNotExist: request._hunger_delete_cookie = True return redirect(reverse('hunger-invalid', args=(cookie_code, ))) right_now = now() if code.private: # If we got here, we're trying to fix up a previous private # invitation to the correct user/email. invitation = Invitation.objects.filter(code=code)[0] invitation.user = request.user invitation.invited = right_now invitation.used = right_now code.num_invites = 0 else: invitation = Invitation(user=request.user, code=code, invited=right_now, used=right_now) code.num_invites -= 1 invitation.save() code.save() return
def process_view(self, request, view_func, view_args, view_kwargs): if not self.enable_beta: return if request.path in self.allow_flatpages or ( getattr(settings, "APPEND_SLASH", True) and "%s/" % request.path in self.allow_flatpages ): from django.contrib.flatpages.views import flatpage return flatpage(request, request.path_info) whitelisted_modules = [ "django.contrib.auth.views", "django.contrib.admin.sites", "django.views.static", "django.contrib.staticfiles.views", ] # All hunger views, except NotBetaView, are off limits until in beta whitelisted_views = ["hunger.views.NotBetaView", "hunger.views.verify_invite", "hunger.views.InvalidView"] short_name = view_func.__class__.__name__ if short_name == "function": short_name = view_func.__name__ view_name = self._get_view_name(request) full_view_name = "%s.%s" % (view_func.__module__, short_name) if self.always_allow_modules: whitelisted_modules += self.always_allow_modules if "%s" % view_func.__module__ in whitelisted_modules: return if self.always_allow_views: whitelisted_views += self.always_allow_views if full_view_name in whitelisted_views or view_name in whitelisted_views: return if not request.user.is_authenticated(): # Ask anonymous user to log in if trying to access in-beta view return redirect(setting("LOGIN_URL")) if request.user.is_staff: return # Prevent queries by caching in_beta status in session if request.session.get("hunger_in_beta"): return cookie_code = request.COOKIES.get("hunger_code") invitations = Invitation.objects.filter(Q(user=request.user) | Q(email=request.user.email)).select_related( "code" ) # User already in the beta - cache in_beta in session if any([i.used for i in invitations if i.invited]): request.session["hunger_in_beta"] = True return # User has been invited - use the invitation and place in beta. activates = [i for i in invitations if i.invited and not i.used] # Check for matching cookie code if available. if cookie_code: for invitation in activates: if invitation.code.code == cookie_code: # Invitation may be attached to email invitation.user = request.user invitation.used = now() invitation.save() request.session["hunger_in_beta"] = True request._hunger_delete_cookie = True return # No cookie - let's just choose the first invitation if it exists if activates: invitation = activates[0] # Invitation may be attached to email invitation.user = request.user invitation.used = now() invitation.save() request.session["hunger_in_beta"] = True return if not cookie_code: if not invitations: invitation = Invitation(user=request.user) invitation.save() return redirect(self.redirect) # No invitation, all we have is this cookie code try: code = InvitationCode.objects.get(code=cookie_code, num_invites__gt=0) except InvitationCode.DoesNotExist: request._hunger_delete_cookie = True return redirect(reverse("hunger-invalid", args=(cookie_code,))) right_now = now() if code.private: # If we got here, we're trying to fix up a previous private # invitation to the correct user/email. invitation = Invitation.objects.filter(code=code)[0] invitation.user = request.user invitation.invited = right_now invitation.used = right_now code.num_invites = 0 else: invitation = Invitation(user=request.user, code=code, invited=right_now, used=right_now) code.num_invites -= 1 invitation.save() code.save() return
class BetaViewTests(TestCase): urls = 'tests.urls' redirect = setting('HUNGER_REDIRECT') def create_invite(self, email): code = InvitationCode(num_invites=0) code.save() invitation = Invitation(code=code, email=email, invited=now()) invitation.save() return invitation def create_code(self, private=True, email=''): code = InvitationCode(private=private) code.save() if private: invitation = Invitation(code=code, email=email, invited=now()) invitation.save() return code def setUp(self): """Creates a few basic users. Alice is registered but not in beta Bob is registered and in beta (self-signup) Charlie is in beta and has one invite """ self.alice = User.objects.create_user('alice', '*****@*****.**', 'secret') self.bob = User.objects.create_user('bob', '*****@*****.**', 'secret') right_now = now() invitation = Invitation(user=self.bob, invited=right_now, used=right_now) invitation.save() self.charlie = User.objects.create_user('charlie', '*****@*****.**', 'secret') invitation = Invitation(user=self.charlie, invited=right_now, used=right_now) invitation.save() code = InvitationCode(owner=self.charlie) code.save() def test_always_allow_view(self): response = self.client.get(reverse('always_allow')) self.assertEqual(response.status_code, 200) self.assertEqual(response.templates[0].name, 'default.html') def test_always_allow_module(self): response = self.client.get(reverse('always_allow_module')) self.assertEqual(response.status_code, 200) def test_garden_when_not_invited(self): """ Confirm that an unauthenticated user is redirected to login. """ response = self.client.get(reverse('invited_only')) self.assertRedirects(response, setting('LOGIN_URL')) def test_using_invite(self): cary = User.objects.create_user('cary', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) self.assertRedirects(response, reverse(self.redirect)) response = self.client.get(reverse('invited_only')) self.assertRedirects(response, reverse(self.redirect)) invitation = Invitation.objects.get(user=cary) invitation.invited = now() invitation.save() response = self.client.get(reverse('invited_only')) self.assertEqual(response.status_code, 200) def test_user_invite(self): """ Confirm that one user can invite another to beta. """ self.client.login(username='******', password='******') response = self.client.post(reverse('hunger-invite'), {'email': '*****@*****.**'}) self.assertRedirects(response, reverse('hunger-invite-sent')) self.client.logout() # @TODO: Replace with examining email body User.objects.create_user('cary', '*****@*****.**', 'secret') self.client.login(username='******', password='******') invitation = Invitation.objects.get(email='*****@*****.**') response = self.client.get( reverse('hunger-verify', args=[invitation.code.code])) # Cary should be allowed to verify the code that belongs to her self.assertRedirects(response, reverse('hunger-verified')) self.client.logout() User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Dany should be denied, since he has no connection with Cary self.assertRedirects(response, reverse('rejection')) def test_invite_non_user_with_email(self): """ Confirm that someone invited to beta can later register. """ self.create_invite(email='*****@*****.**') User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) self.assertEqual(response.status_code, 200) def test_invite_existing_user_with_email(self): """ Confirm that existing user can later be invited to beta. """ self.create_invite(email='*****@*****.**') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) self.assertEqual(response.status_code, 200) def test_invite_non_user_without_email(self): """ Confirm that an unregistered user cannot join beta using a private InvitationCode meant for someone else. """ code = self.create_code(email='*****@*****.**') response = self.client.get(reverse('hunger-verify', args=[code.code]), follow=True) # Anonymous user cannot verify a private InvitationCode self.assertRedirects(response, setting('LOGIN_URL')) User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Dany should be denied, since he has no connection with email account self.assertRedirects(response, reverse('hunger-invalid', args=[code.code])) def test_invite_non_user_public_invitation(self): """ Confirm that an unregistered user can join beta using a public InvitationCode. """ code = self.create_code(private=False) # Anonymous user can verify a public InvitationCode, but cannot # access pages behind beta until logged in. response = self.client.get(reverse('hunger-verify', args=[code.code]), follow=True) response = self.client.get(reverse('invited_only')) self.assertRedirects(response, setting('LOGIN_URL')) User.objects.create_user('dany', '*****@*****.**', 'secret') self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Dany is allowed in beta since he used public code earlier in session self.assertEqual(response.status_code, 200) def test_invite_existing_user_without_email(self): """ Confirm that a registered user cannot join beta using a private InvitationCode meant for someone else. """ code = self.create_code(email='*****@*****.**') response = self.client.get(reverse('hunger-verify', args=[code.code]), follow=True) # Anonymous user cannot verify a private InvitationCode self.assertRedirects(response, setting('LOGIN_URL')) self.client.login(username='******', password='******') response = self.client.get(reverse('invited_only')) # Alice should be denied, since she has no connection with email account self.assertRedirects(response, reverse('hunger-invalid', args=[code.code])) def test_invalid_code(self): """ Confirm that a registered user cannot join beta using a random code. """ invalid_code = 'XXXXinvalidcodeXXXX' self.client.login(username='******', password='******') response = self.client.get(reverse('hunger-verify', args=[invalid_code]), follow=True) self.assertRedirects(response, reverse('hunger-invalid', args=[invalid_code])) @override_settings(HUNGER_ENABLE=False) def test_settings(self): """ Confirm that settings override DEFAULT_SETTINGS """ self.assertEqual(False, setting('HUNGER_ENABLE'))