Skip to content

Commit

Permalink
Merge branch 'develop': release 0.11
Browse files Browse the repository at this point in the history
  • Loading branch information
mfogel committed Jul 24, 2014
2 parents b6bb7d5 + 551f172 commit 0237772
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 47 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
@@ -1,12 +1,11 @@
language: python
python:
- 2.6
- 2.7
- 3.2
- 3.3
env:
- DJANGO='django>=1.5,<1.6'
- DJANGO='git+git://github.com/django/django.git@1.6b2'
- DJANGO='django>=1.6,<1.7'
- DJANGO='git+git://github.com/django/django.git@1.7c1'
before_install:
- export PIP_USE_MIRRORS=true
install:
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Expand Up @@ -72,7 +72,7 @@ Installation
INSTALLED_APPS = (
...
simple_email_confirmation,
'simple_email_confirmation',
...
)
Expand Down
2 changes: 1 addition & 1 deletion simple_email_confirmation/__init__.py
@@ -1,4 +1,4 @@
__version__ = '0.10.2'
__version__ = '0.11'

from .models import SimpleEmailConfirmationUserMixin, EmailAddress
from .signals import (
Expand Down
93 changes: 76 additions & 17 deletions simple_email_confirmation/models.py
@@ -1,11 +1,9 @@
from __future__ import unicode_literals

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import post_save
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils import timezone

from .exceptions import (
EmailConfirmationExpired, EmailIsPrimary, EmailNotConfirmed,
Expand Down Expand Up @@ -52,25 +50,44 @@ def is_confirmed(self):
"Is the User's primary email address confirmed?"
return self.get_primary_email() in self.confirmed_emails

@property
def confirmed_at(self):
"When the User's primary email address was confirmed, or None"
address = self.email_address_set.get(email=self.get_primary_email())
return address.confirmed_at

@property
def confirmation_key(self):
"Confirmation key for the User's primary email"
"""
Confirmation key for the User's primary email
DEPRECATED. Use get_confirmation_key() instead.
"""
email = self.get_primary_email()
return self.get_confirmation_key(email)

def get_confirmation_key(self, email):
@property
def confirmed_emails(self):
"DEPRECATED. Use get_confirmed_emails() instead."
return self.get_confirmed_emails()

@property
def unconfirmed_emails(self):
"DEPRECATED. Use get_unconfirmed_emails() instead."
return self.get_unconfirmed_emails()

def get_confirmation_key(self, email=None):
"Get the confirmation key for an email"
email = email or self.get_primary_email()
address = self.email_address_set.get(email=email)
return address.key

@property
def confirmed_emails(self):
def get_confirmed_emails(self):
"List of emails this User has confirmed"
address_qs = self.email_address_set.filter(confirmed_at__isnull=False)
return [address.email for address in address_qs]

@property
def unconfirmed_emails(self):
def get_unconfirmed_emails(self):
"List of emails this User has been associated with but not confirmed"
address_qs = self.email_address_set.filter(confirmed_at__isnull=True)
return [address.email for address in address_qs]
Expand All @@ -83,19 +100,46 @@ def confirm_email(self, confirmation_key, save=True):
address = self.email_address_set.confirm(confirmation_key, save=save)
return address.email

def add_confirmed_email(self, email):
"Adds an email to the user that's already in the confirmed state"
# if email already exists, let exception be thrown
address = self.email_address_set.create_confirmed(email)
return address.key

def add_unconfirmed_email(self, email):
"Adds an unconfirmed email address and returns it's confirmation key"
# if email already exists, let exception be thrown
address = self.email_address_set.create_unconfirmed(email)
return address.key

def add_email_if_not_exists(self, email):
"""
If the user already has the email, and it's confirmed, do nothing
and return None.
If the user already has the email, and it's unconfirmed, reset the
confirmation. If the confirmation is unexpired, do nothing. Return
the confirmation key of the email.
"""
try:
address = self.email_address_set.get(email=email)
except EmailAddress.DoesNotExist:
key = self.add_unconfirmed_email(email)
else:
if not address.is_confirmed:
key = address.reset_confirmation()
else:
key = None

return key

def reset_email_confirmation(self, email):
"Reset the expiration of an email confirmation"
address = self.email_address_set.get(email=email)
address.reset_confirmation()
return address.reset_confirmation()

def remove_email(self, email):
"Remove an email address and returns it's confirmation key"
"Remove an email address"
# if email already exists, let exception be thrown
if email == self.get_primary_email():
raise EmailIsPrimary()
Expand All @@ -110,8 +154,21 @@ def generate_key(self):
# sticking with the django defaults
return get_random_string()

def create_confirmed(self, email, user=None):
"Create an email address in the confirmed state"
user = user or self.instance
if not user:
raise ValueError('Must specify user or call from related manager')
key = self.generate_key()
now = timezone.now()
# let email-already-exists exception propogate through
address = self.create(
user=user, email=email, key=key, set_at=now, confirmed_at=now,
)
return address

def create_unconfirmed(self, email, user=None):
"Create an email confirmation obj from the given email address obj"
"Create an email address in the unconfirmed state"
user = user or self.instance
if not user:
raise ValueError('Must specify user or call from related manager')
Expand All @@ -132,7 +189,7 @@ def confirm(self, key, user=None, save=True):
raise EmailConfirmationExpired()

if not address.is_confirmed:
address.confirmed_at = now()
address.confirmed_at = timezone.now()
if save:
address.save(update_fields=['confirmed_at'])
email_confirmed.send(sender=address.user, email=address.email)
Expand All @@ -150,7 +207,7 @@ class EmailAddress(models.Model):
key = models.CharField(max_length=40, unique=True)

set_at = models.DateTimeField(
default=lambda: now(),
default=lambda: timezone.now(),
help_text='When the confirmation key expiration was set',
)
confirmed_at = models.DateTimeField(
Expand All @@ -164,7 +221,7 @@ class Meta:
unique_together = (('user', 'email'),)

def __unicode__(self):
return '{} ({})'.format(self.email, self.user)
return u'{} <{}>'.format(self.user, self.email)

@property
def is_confirmed(self):
Expand All @@ -185,7 +242,7 @@ def key_expires_at(self):

@property
def is_key_expired(self):
return bool(self.key_expires_at and now() >= self.key_expires_at)
return self.key_expires_at and timezone.now() >= self.key_expires_at

def reset_confirmation(self):
"""
Expand All @@ -194,9 +251,11 @@ def reset_confirmation(self):
cease to work.
"""
self.key = self._default_manager.generate_key()
self.set_at = now()
self.set_at = timezone.now()

self.confirmed_at = None
self.save(update_fields=['key', 'set_at', 'confirmed_at'])
return self.key


# by default, auto-add unconfirmed EmailAddress objects for new Users
Expand Down
109 changes: 84 additions & 25 deletions simple_email_confirmation/tests.py
Expand Up @@ -32,6 +32,16 @@ def test_key_generation(self):
self.assertNotEqual(key2, key3)
self.assertNotEqual(key1, key3)

def test_create_confirmed(self):
"Add an unconfirmed email for a User"
email = 'test@test.test'

key = self.user.add_confirmed_email(email)

address = self.user.email_address_set.get(email=email)
self.assertTrue(address.is_confirmed)
self.assertEqual(address.key, key)

def test_create_unconfirmed(self):
"Add an unconfirmed email for a User"
email = 'test@test.test'
Expand All @@ -42,10 +52,12 @@ def listener(sender, **kwargs):
self.assertEqual(email, kwargs.get('email'))
unconfirmed_email_created.connect(listener)

self.user.add_unconfirmed_email(email)
key = self.user.add_unconfirmed_email(email)

address = self.user.email_address_set.get(email=email)
self.assertFalse(address.is_confirmed)
self.assertEqual(address.confirmed_at, None)
self.assertEqual(address.key, key)

def test_reset_confirmation(self):
"Reset a confirmation key"
Expand Down Expand Up @@ -73,30 +85,30 @@ def listener(sender, **kwargs):
self.assertEqual(kwargs.get('email'), self.user.email)
email_confirmed.connect(listener)

self.user.confirm_email(self.user.confirmation_key)
self.user.confirm_email(self.user.get_confirmation_key())

self.assertTrue(self.user.is_confirmed)
self.assertIn(self.user.email, self.user.confirmed_emails)
self.assertNotIn(self.user.email, self.user.unconfirmed_emails)
self.assertTrue(self.user.confirmed_at)
self.assertIn(self.user.email, self.user.get_confirmed_emails())
self.assertNotIn(self.user.email, self.user.get_unconfirmed_emails())

self.assertNotIn(email1, self.user.confirmed_emails)
self.assertNotIn(email2, self.user.confirmed_emails)
self.assertNotIn(email3, self.user.confirmed_emails)
self.assertIn(email1, self.user.unconfirmed_emails)
self.assertIn(email2, self.user.unconfirmed_emails)
self.assertIn(email3, self.user.unconfirmed_emails)
self.assertNotIn(email1, self.user.get_confirmed_emails())
self.assertNotIn(email2, self.user.get_confirmed_emails())
self.assertNotIn(email3, self.user.get_confirmed_emails())
self.assertIn(email1, self.user.get_unconfirmed_emails())
self.assertIn(email2, self.user.get_unconfirmed_emails())
self.assertIn(email3, self.user.get_unconfirmed_emails())

def test_confirm_previously_confirmed_confirmation(self):
"Re-confirm an confirmation that was already confirmed"
email = 't@t.t'
key = self.user.add_unconfirmed_email(email)
self.user.confirm_email(key)
key = self.user.add_confirmed_email(email)
at_before = self.user.email_address_set.get(email=email).confirmed_at

self.user.confirm_email(key)
at_after = self.user.email_address_set.get(email=email).confirmed_at

self.assertIn(email, self.user.confirmed_emails)
self.assertIn(email, self.user.get_confirmed_emails())
self.assertEqual(at_after, at_before)

@override_settings(SIMPLE_EMAIL_CONFIRMATION_PERIOD=timedelta(weeks=1))
Expand Down Expand Up @@ -127,23 +139,23 @@ def test_remove_email(self):
email_confirmed = 'confirmed@t.t'

self.user.add_unconfirmed_email(email_unconfirmed)
key = self.user.add_unconfirmed_email(email_confirmed)
self.user.confirm_email(key)
self.user.add_confirmed_email(email_confirmed)

self.assertIn(email_confirmed, self.user.confirmed_emails)
self.assertIn(email_unconfirmed, self.user.unconfirmed_emails)
self.assertIn(email_confirmed, self.user.get_confirmed_emails())
self.assertIn(email_unconfirmed, self.user.get_unconfirmed_emails())

# can't remove the primary
with self.assertRaises(EmailIsPrimary):
self.user.remove_email(self.user.email)

# can remove the unconfirmed
self.user.remove_email(email_unconfirmed)
self.assertNotIn(email_unconfirmed, self.user.unconfirmed_emails)
self.assertNotIn(email_unconfirmed, self.user.get_unconfirmed_emails())

# can remove the confirmed
self.user.remove_email(email_confirmed)
self.assertNotIn(email_confirmed, self.user.confirmed_emails)
self.assertNotIn(email_confirmed, self.user.get_confirmed_emails())


class PrimaryEmailTestCase(TestCase):

Expand All @@ -155,13 +167,11 @@ def test_set_primary_email(self):
"Set an email to priamry"
# set up two emails, confirm them post
email1 = '1@t.t'
key1 = self.user.add_unconfirmed_email(email1)
self.user.confirm_email(key1)
self.user.add_confirmed_email(email1)
self.user.set_primary_email(email1)

email2 = '2@t.t'
key2 = self.user.add_unconfirmed_email(email2)
self.user.confirm_email(key2)
self.user.add_confirmed_email(email2)

# assert signal fires as expected
def listener(sender, **kwargs):
Expand All @@ -180,8 +190,7 @@ def test_attempt_set_primary_email_to_unowned_email(self):
'myname', email='somebody@important.com',
)
email = '1@t.t'
key = other_user.add_unconfirmed_email(email)
other_user.confirm_email(key)
other_user.add_confirmed_email(email)

with self.assertRaises(EmailNotConfirmed):
self.user.set_primary_email(email)
Expand All @@ -193,3 +202,53 @@ def test_attempt_set_primary_email_to_unconfirmed_email(self):

with self.assertRaises(EmailNotConfirmed):
self.user.set_primary_email(email)


class AddEmailIfNotExistsTestCase(TestCase):

def setUp(self):
self.email1 = 'e1@go.com'
self.email2 = 'e2@go.com'
self.email3 = 'e3@go.com'
self.email4 = 'e4@go.com'

# adds this email as an unconfirmed email
self.user = get_user_model().objects.create_user(
'uname', email=self.email1
)

def test_add_new_unconfirmed_email(self):
result = self.user.add_email_if_not_exists(self.email2)

self.assertEqual(self.user.email_address_set.count(), 2)
address = self.user.email_address_set.get(key=result)
self.assertEqual(address.email, self.email2)
self.assertEqual(address.is_confirmed, False)

def test_add_old_unconfirmed_email(self):
self.user.add_unconfirmed_email(self.email2)
self.user.add_unconfirmed_email(self.email3)

address = self.user.email_address_set.get(email=self.email2)
org_key, org_at = address.key, address.set_at

sleep(0.1)
result = self.user.add_email_if_not_exists(self.email2)

self.assertEqual(self.user.email_address_set.count(), 3)
address = self.user.email_address_set.get(key=result)
self.assertEqual(address.email, self.email2)
self.assertEqual(address.is_confirmed, False)
self.assertNotEqual(address.key, org_key)
self.assertGreater(address.set_at, org_at)

def test_add_confirmed_email(self):
self.user.add_confirmed_email(self.email2)
self.user.add_confirmed_email(self.email3)

result = self.user.add_email_if_not_exists(self.email2)

self.assertIsNone(result)
self.assertEqual(self.user.email_address_set.count(), 3)
address = self.user.email_address_set.get(email=self.email2)
self.assertEqual(address.is_confirmed, True)

0 comments on commit 0237772

Please sign in to comment.