Permalink
Browse files

create news app, basic HTTP API for subscribing

  • Loading branch information...
jlongster committed Jul 14, 2011
1 parent d77bb65 commit 9c729a5e88d0f2c27a7043cc3d40cfd5e5a9eacf
Showing with 209 additions and 27 deletions.
  1. 0 apps/news/__init__.py
  2. +3 −0 apps/news/models.py
  3. +13 −0 apps/news/news.html
  4. +102 −0 apps/news/responsys.py
  5. +23 −0 apps/news/tests.py
  6. +6 −0 apps/news/urls.py
  7. +53 −0 apps/news/views.py
  8. +8 −27 settings.py
  9. +1 −0 urls.py
View
No changes.
View
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
View
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ </head>
+ <body>
+ <form action="http://localhost:8000/news/subscribe/" method="post">
+ <div>
+ Email: <input type="text" name="email" />
+ </div>
+ <input type="submit" />
+ </form>
+ </body>
+</html>
View
@@ -0,0 +1,102 @@
+from functools import wraps
+
+from suds import WebFault
+from suds.client import Client
+
+
+class UnathorizedException(Exception):
+ pass
+
+
+def logged_in(f):
+ """ Decorator to ensure an authenticated session with Responsys
+ before calling a function """
+
+ @wraps(f)
+ def wrapper(inst, *args, **kwargs):
+ if not inst.session:
+ raise UnauthorizedException("Not logged in to Responsys, "
+ "must call login()")
+ f(inst, *args, **kwargs)
+ return wrapper
+
+
+class Responsys(object):
+ WSDL_URL = 'https://ws2.responsys.net/webservices/wsdl/ResponsysWS_Level1.wsdl'
+
+ def __init__(self):
+ self.client = None
+ self.session = None
+
+ def login(self, user, pass_):
+ """ Login and create a Responsys session, returns False on
+ failure """
+
+ if not self.client:
+ self.client = Client(self.__class__.WSDL_URL)
+ elif self.session:
+ self.logout()
+
+ try:
+ res = self.client.service.login(user, pass_)
+ except WebFault, e:
+ return False
+
+ self.session = res['sessionId']
+
+ # Set auth token for all requests
+ header = self.client.factory.create('SessionHeader')
+ header.sessionId = self.session
+ self.client.set_options(soapheaders=header)
+
+ @logged_in
+ def logout(self):
+ """ Logout and expire the current Responsys session """
+
+ self.client.service.logout()
+ self.session = None
+
+ @logged_in
+ def merge_list_members(self, folder, list_, fields, records):
+ """
+ Add data to the list located at <folder>/<list_> in
+ Responsys.
+
+ <fields> is an array of field names
+ <records> is a single record or an array of records to insert
+ (record = array). If the email already exists, its data will
+ be updated
+ """
+
+ client = self.client
+ records = [records] if isinstance(records[0], basestring) else records
+
+ def make_record(record):
+ data = client.factory.create('Record')
+ data.fieldValues = record
+ return data
+
+ target = client.factory.create('InteractObject')
+ target.folderName = folder
+ target.objectName = list_
+
+ data = client.factory.create('RecordData')
+ data.fieldNames = fields
+ data.records = [make_record(r) for r in records]
+
+ # Configure the action to update the data when it matches on
+ # the email address field, otherwise insert a new entry, and
+ # default opt in
+ rule = client.factory.create('ListMergeRule')
+ rule.insertOnNoMatch = True
+ rule.updateOnMatch = 'REPLACE_ALL'
+ rule.matchColumnName1 = 'EMAIL_ADDRESS_'
+ rule.matchOperator = 'NONE'
+ rule.optinValue = 'I'
+ rule.optoutValue = 'O'
+ rule.htmlValue = 'H'
+ rule.textValue = 'T'
+ rule.rejectRecordIfChannelEmpty = 'E'
+ rule.defaultPermissionStatus = 'OPTIN'
+
+ self.client.service.mergeListMembers(target, data, rule)
View
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
View
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+from views import subscribe
+
+urlpatterns = patterns('',
+ url('^subscribe/$', subscribe),
+)
View
@@ -0,0 +1,53 @@
+from datetime import date
+import urlparse
+
+from django.http import (HttpResponse, HttpResponseRedirect,
+ HttpResponseBadRequest, HttpResponseForbidden)
+from django.views.decorators.csrf import csrf_exempt
+from django.conf import settings
+
+from responsys import Responsys
+
+
+@csrf_exempt
+def subscribe(request):
+ if request.method == 'POST':
+ data = request.POST
+
+ # validate parameters
+ for name in ['email']:
+ if name not in data:
+ return HttpResponseBadRequest('%s is missing' % name)
+
+ record = {'EMAIL_ADDRESS_': data['email'],
+ 'EMAIL_FORMAT_': data.get('format', 'H'),
+ 'COUNTRY_': data.get('country', 'en'),
+ 'MOZILLA_AND_YOU_FLG': 'Y',
+ 'MOZILLA_AND_YOU_DATE': date.today().strftime('%Y-%m-%d')}
+
+ # do the subscription
+ rs = Responsys()
+ rs.login(settings.RESPONSYS_USER, settings.RESPONSYS_PASS)
+ rs.merge_list_members(settings.RESPONSYS_FOLDER,
+ settings.RESPONSYS_LIST,
+ record.keys(),
+ record.values())
+ rs.logout()
+
+ # redirect back to the page, if any
+ next = data.get('next', request.META.get('HTTP_REFERER', None))
+ if next:
+ parts = urlparse.urlsplit(next)
+ query = '%s%s%s' % (parts.query,
+ '&' if parts.query else '',
+ 'subscribed')
+ next = urlparse.urlunsplit((parts.scheme,
+ parts.netloc,
+ parts.path,
+ query,
+ parts.fragment))
+ return HttpResponseRedirect(next)
+ return HttpResponse('Success! You have been subscribed.')
+
+ return HttpResponseBadRequest('GET is not supported')
+
View
@@ -29,39 +29,17 @@
}
}
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
TIME_ZONE = 'America/Chicago'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
-
SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
USE_I18N = True
-# Absolute path to the directory that holds media.
-# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = path('media')
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash if there is a path component (optional in other cases).
-# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = '/media/'
-
-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
-# trailing slash.
-# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/admin-media/'
# Make this unique, and don't share it with anybody.
-SECRET_KEY = ''
+SECRET_KEY = '0D8AE44F-5714-40EF-9AC8-4AC6EB556161'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
@@ -95,6 +73,7 @@
'nagios',
'subscriptions',
'vars',
+ 'news',
'fixture_magic',
'piston',
@@ -129,7 +108,7 @@
DEFAULT_FROM_NAME = 'Mozilla'
# Logging
-LOG_LEVEL = logging.DEBUG
+LOG_LEVEL = logging.INFO
HAS_SYSLOG = True # syslog is used if HAS_SYSLOG and NOT DEBUG.
SYSLOG_TAG = "http_app_basket"
# See PEP 391 and log_settings.py for formatting help. Each section of LOGGING
@@ -150,11 +129,8 @@
}
EMAIL_BACKEND = 'mysmtp.EmailBackend'
-
EMAIL_BACKLOG_TOLERANCE = 200
-
SYNC_UNSUBSCRIBE_LIMIT = 1000
-
LDAP_TIMEOUT = 2
def JINJA_CONFIG():
@@ -164,3 +140,8 @@ def JINJA_CONFIG():
'jinja2.ext.with_', 'jinja2.ext.loopcontrols'],
'finalize': lambda x: x if x is not None else ''}
return config
+
+RESPONSYS_USER = 'MOZILLA_API'
+RESPONSYS_PASS = ''
+RESPONSYS_FOLDER = '!MasterData'
+RESPONSYS_LIST = 'TEST_CONTACTS_LIST'
View
@@ -9,6 +9,7 @@
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/', include(admin.site.urls)),
('^nagios/', include('nagios.urls')),
+ (r'^news/', include('news.urls'))
)
if settings.DEBUG:

0 comments on commit 9c729a5

Please sign in to comment.