Skip to content
This repository has been archived by the owner on Jan 13, 2022. It is now read-only.

Commit

Permalink
Merge pull request #101 from pmclanahan/add_fxa_id_1051806
Browse files Browse the repository at this point in the history
Bug 1051806: Add /fxa-register endpoint.
  • Loading branch information
pmac committed Sep 12, 2014
2 parents 5930f4d + 92f1d35 commit a9a52bd
Show file tree
Hide file tree
Showing 7 changed files with 446 additions and 9 deletions.
64 changes: 64 additions & 0 deletions news/migrations/0008_auto__add_field_subscriber_fxa_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models


class Migration(SchemaMigration):

def forwards(self, orm):
# Adding field 'Subscriber.fxa_id'
db.add_column(u'news_subscriber', 'fxa_id',
self.gf('django.db.models.fields.CharField')(db_index=True, max_length=100, null=True, blank=True),
keep_default=False)


def backwards(self, orm):
# Deleting field 'Subscriber.fxa_id'
db.delete_column(u'news_subscriber', 'fxa_id')


models = {
u'news.apiuser': {
'Meta': {'object_name': 'APIUser'},
'api_key': ('django.db.models.fields.CharField', [], {'default': "'c17bac3d-1abd-4d6c-801e-866671c77dfd'", 'max_length': '40', 'db_index': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '256'})
},
u'news.failedtask': {
'Meta': {'object_name': 'FailedTask'},
'args': ('jsonfield.fields.JSONField', [], {'default': '[]'}),
'einfo': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
'exc': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kwargs': ('jsonfield.fields.JSONField', [], {'default': '{}'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'task_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'when': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
},
u'news.newsletter': {
'Meta': {'ordering': "['order']", 'object_name': 'Newsletter'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'confirm_message': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'languages': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'requires_double_optin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'show': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'vendor_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'welcome': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
},
u'news.subscriber': {
'Meta': {'object_name': 'Subscriber'},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'primary_key': 'True'}),
'fxa_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '100', 'null': 'True', 'blank': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'default': "'b498d69d-441a-46fa-818d-faa447a5acd1'", 'max_length': '40', 'db_index': 'True'})
}
}

complete_apps = ['news']
17 changes: 11 additions & 6 deletions news/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@


class SubscriberManager(models.Manager):
def get_and_sync(self, email, token):
def get_and_sync(self, email, token, fxa_id=None):
"""
Get the subscriber for the email and token and ensure that such a
subscriber exists.
"""
sub, created = self.get_or_create(
email=email,
defaults={'token': token},
)
if not created and sub.token != token:
defaults = {'token': token}
if fxa_id:
defaults['fxa_id'] = fxa_id

sub, created = self.get_or_create(email=email, defaults=defaults)
if not created:
sub.token = token
if fxa_id:
sub.fxa_id = fxa_id
sub.save()
# FIXME: this could mean there's another record in Exact Target
# with the other token
Expand All @@ -34,6 +37,8 @@ class Subscriber(models.Model):
email = models.EmailField(primary_key=True)
token = models.CharField(max_length=40, default=lambda: str(uuid4()),
db_index=True)
fxa_id = models.CharField(max_length=100, null=True, blank=True,
db_index=True)

objects = SubscriberManager()

Expand Down
63 changes: 61 additions & 2 deletions news/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

from celery.task import Task, task

from .backends.common import NewsletterException
from .backends.common import NewsletterException, NewsletterNoResultsException
from .backends.exacttarget import (ExactTarget, ExactTargetDataExt)
from .models import FailedTask, Newsletter
from .models import FailedTask, Newsletter, Subscriber
from .newsletters import (is_supported_newsletter_language, newsletter_field,
newsletter_slugs)

Expand Down Expand Up @@ -77,6 +77,7 @@
# This is prefixed with the 2-letter language code + _ before sending,
# e.g. 'en_recovery_message', and '_T' if text, e.g. 'en_recovery_message_T'.
RECOVERY_MESSAGE_ID = 'recovery_message'
FXACCOUNT_WELCOME = 'FxAccounts_Welcome'

# Vendor IDs for Firefox OS and Firefox & You:
FFOS_VENDOR_ID = 'FIREFOX_OS'
Expand Down Expand Up @@ -253,6 +254,64 @@ def parse_newsletters(record, type, newsletters, cur_newsletters):
return to_subscribe, to_unsubscribe


def get_external_user_data(email=None, token=None, fields=None, database=None):
database = database or settings.EXACTTARGET_DATA
fields = fields or [
'EMAIL_ADDRESS_',
'EMAIL_FORMAT_',
'COUNTRY_',
'LANGUAGE_ISO2',
'TOKEN',
]
ext = ExactTargetDataExt(settings.EXACTTARGET_USER,
settings.EXACTTARGET_PASS)
try:
user = ext.get_record(database, token or email, fields,
'TOKEN' if token else 'EMAIL_ADDRESS_')
except NewsletterNoResultsException:
return None

user_data = {
'email': user['EMAIL_ADDRESS_'],
'format': user['EMAIL_FORMAT_'] or 'H',
'country': user['COUNTRY_'] or '',
'lang': user['LANGUAGE_ISO2'] or '', # Never None
'token': user['TOKEN'],
}
return user_data


@et_task
def update_fxa_info(email, lang, fxa_id, source_url=None):
user = get_external_user_data(email=email)
record = {
'EMAIL_ADDRESS_': email,
'FXA_ID': fxa_id,
'MODIFIED_DATE_': gmttime(),
}
if user:
format = user['format']
token = user['token']
Subscriber.objects.get_and_sync(email, token, fxa_id)
record['TOKEN'] = token
if not user['lang']:
record['LANGUAGE_ISO2'] = lang
else:
sub, created = Subscriber.objects.get_or_create(email=email, defaults={'fxa_id': fxa_id})
if not created:
sub.fxa_id = fxa_id
sub.save()
format = 'H'
token = sub.token
record['TOKEN'] = token
record['LANGUAGE_ISO2'] = lang
record['SOURCE_URL'] = source_url or 'https://accounts.firefox.com'

welcome = mogrify_message_id(FXACCOUNT_WELCOME, lang, format)
send_message(welcome, email, token, format)
apply_updates(settings.EXACTTARGET_DATA, record)


@et_task
def update_phonebook(data, email, token):
record = {
Expand Down
157 changes: 157 additions & 0 deletions news/tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,163 @@
from news.views import look_for_user, get_user_data


class UpdateFxAInfoTest(TestCase):
def setUp(self):
patcher = patch.object(tasks, 'send_message')
self.addCleanup(patcher.stop)
self.send_message = patcher.start()

patcher = patch.object(tasks, 'apply_updates')
self.addCleanup(patcher.stop)
self.apply_updates = patcher.start()

patcher = patch.object(tasks, 'get_external_user_data')
self.addCleanup(patcher.stop)
self.get_external_user_data = patcher.start()

def test_new_user(self):
"""Adding a new user to the DB should add fxa_id."""
self.get_external_user_data.return_value = None
email = 'dude@example.com'
fxa_id = 'the fxa abides'

tasks.update_fxa_info(email, 'de', fxa_id)
sub = models.Subscriber.objects.get(email=email)
self.assertEqual(sub.fxa_id, fxa_id)

self.apply_updates.assert_called_once_with(settings.EXACTTARGET_DATA, {
'EMAIL_ADDRESS_': email,
'TOKEN': sub.token,
'FXA_ID': fxa_id,
'LANGUAGE_ISO2': 'de',
'SOURCE_URL': 'https://accounts.firefox.com',
'MODIFIED_DATE_': ANY,
})
self.get_external_user_data.assert_called_with(email=email)
self.send_message.assert_called_with('de_{0}'.format(tasks.FXACCOUNT_WELCOME),
email, sub.token, 'H')

def test_user_in_et_not_basket(self):
"""A user could exist in basket but not ET, should still work."""
self.get_external_user_data.return_value = None
email = 'dude@example.com'
fxa_id = 'the fxa abides'

models.Subscriber.objects.create(email=email)
tasks.update_fxa_info(email, 'de', fxa_id)
sub = models.Subscriber.objects.get(email=email)
self.assertEqual(sub.fxa_id, fxa_id)

self.apply_updates.assert_called_once_with(settings.EXACTTARGET_DATA, {
'EMAIL_ADDRESS_': email,
'TOKEN': sub.token,
'FXA_ID': fxa_id,
'LANGUAGE_ISO2': 'de',
'SOURCE_URL': 'https://accounts.firefox.com',
'MODIFIED_DATE_': ANY,
})
self.get_external_user_data.assert_called_with(email=email)
self.send_message.assert_called_with('de_{0}'.format(tasks.FXACCOUNT_WELCOME),
email, sub.token, 'H')

def test_existing_user_not_in_basket(self):
"""Adding a user already in ET but not basket should preserve token."""
email = 'dude@example.com'
fxa_id = 'the fxa abides'
token = 'hehe... you said **token**.'
self.get_external_user_data.return_value = {
'email': email,
'token': token,
'lang': 'de',
'format': '',
}
tasks.update_fxa_info(email, 'de', fxa_id)
sub = models.Subscriber.objects.get(email=email)
self.assertEqual(sub.fxa_id, fxa_id)
self.assertEqual(sub.token, token)

self.apply_updates.assert_called_once_with(settings.EXACTTARGET_DATA, {
'EMAIL_ADDRESS_': email,
'TOKEN': token,
'FXA_ID': fxa_id,
'MODIFIED_DATE_': ANY,
})
self.get_external_user_data.assert_called_with(email=email)
self.send_message.assert_called_with('de_{0}'.format(tasks.FXACCOUNT_WELCOME),
email, sub.token, '')

def test_existing_user(self):
"""Adding a fxa_id to an existing user shouldn't modify other things."""
email = 'dude@example.com'
fxa_id = 'the fxa abides'
old_sub = models.Subscriber.objects.create(email=email)
self.get_external_user_data.return_value = {
'email': email,
'token': old_sub.token,
'lang': 'de',
'format': 'T',
}
tasks.update_fxa_info(email, 'de', fxa_id)
sub = models.Subscriber.objects.get(email=email)
self.assertEqual(sub.fxa_id, fxa_id)

self.apply_updates.assert_called_once_with(settings.EXACTTARGET_DATA, {
'EMAIL_ADDRESS_': email,
'TOKEN': old_sub.token,
'FXA_ID': fxa_id,
'MODIFIED_DATE_': ANY,
})
self.send_message.assert_called_with('de_{0}_T'.format(tasks.FXACCOUNT_WELCOME),
email, sub.token, 'T')

def test_existing_user_no_lang(self):
"""Adding a fxa_id to an existing user should update lang only if not set."""
email = 'dude@example.com'
fxa_id = 'the fxa abides'
old_sub = models.Subscriber.objects.create(email=email)
self.get_external_user_data.return_value = {
'email': email,
'token': old_sub.token,
'lang': '',
'format': '',
}
tasks.update_fxa_info(email, 'de', fxa_id)
sub = models.Subscriber.objects.get(email=email)
self.assertEqual(sub.fxa_id, fxa_id)

self.apply_updates.assert_called_once_with(settings.EXACTTARGET_DATA, {
'EMAIL_ADDRESS_': email,
'TOKEN': old_sub.token,
'FXA_ID': fxa_id,
'LANGUAGE_ISO2': 'de',
'MODIFIED_DATE_': ANY,
})
self.send_message.assert_called_with('de_{0}'.format(tasks.FXACCOUNT_WELCOME),
email, sub.token, '')

self.apply_updates.reset_mock()
self.send_message.reset_mock()
self.get_external_user_data.reset_mock()
self.get_external_user_data.return_value = {
'email': email,
'token': old_sub.token,
'lang': 'es',
'format': '',
}
tasks.update_fxa_info(email, 'de', fxa_id)
sub = models.Subscriber.objects.get(email=email)
self.assertEqual(sub.fxa_id, fxa_id)

self.apply_updates.assert_called_once_with(settings.EXACTTARGET_DATA, {
'EMAIL_ADDRESS_': email,
'TOKEN': old_sub.token,
'FXA_ID': fxa_id,
'MODIFIED_DATE_': ANY,
})
self.send_message.assert_called_with('de_{0}'.format(tasks.FXACCOUNT_WELCOME),
email, sub.token, '')


class DebugUserTest(TestCase):
def setUp(self):
self.sub = models.Subscriber.objects.create(email='dude@example.com')
Expand Down
Loading

0 comments on commit a9a52bd

Please sign in to comment.