diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..508a30c
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+Contributors based on gitlog:
+Joe Jasinski
+Jin Sun
+amatellanes
+Pablo Recio (pablorecio)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ebc8d00
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+Copyright 2014 (c) Imaginary Landscape LLC
+All rights reserved.
+
+Major updates to the project and name change made by Imaginary Landscape LLC. 
+All licensing follows the below 3-clause BSD license. 
+
+
+Copyright (c) Praekelt Foundation
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Praekelt Foundation nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL PRAEKELT FOUNDATION BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..15f1435
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,9 @@
+global-include LICENSE README.md setup.py LICENSE MANIFEST.in
+include LICENSE
+include README.md
+include MANIFEST.in
+include setup.py
+recursive-include demo *
+recursive-include nocaptcha_recaptcha *
+recursive-exclude * __pycache__
+recursive-exclude * *.py[co]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0d02c03
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+[![Build Status](https://travis-ci.org/ImaginaryLandscape/django-nocaptcha-recaptcha.svg?branch=master)](https://travis-ci.org/ImaginaryLandscape/django-nocaptcha-recaptcha)
+
+# SUMMARY
+
+Add new-style Google ReCaptcha widgets to your Django forms simply by adding a 
+NoReCaptchaField field to said forms. 
+
+# ABOUT 
+
+In late 2014, Google updated their ReCaptcha service, changing its API.  The update significantly
+changes the appearance and function of ReCaptcha.  This has been referred to as
+ReCaptcha 2 or "nocaptcha recaptcha". 
+
+This module is intended to be a successor to django-recaptcha to support the new style 
+Google Recaptcha.  It borrows a lot of the logic from the django-recaptcha, but has been
+updated to support the Google change. 
+
+For the Google documentation for this service, visit the following: 
+
+    https://developers.google.com/recaptcha/intro
+   
+The original django-recaptcha project is located at the following location:
+
+    https://github.com/praekelt/django-recaptcha
+
+# FEATURES
+
+ - Implements Google's New "NoCaptcha ReCaptcha Field"
+ - Uses the fallback option for browsers without JavaScript
+ - Easy to add to a Form via a FormField
+ - Works similar to django-recaptcha 
+ - Working demo projects
+ - Works with Python 2.7 and 3.4
+
+# INSTALL
+
+    pip install django-nocaptcha-recaptcha
+
+# CONFIGURE 
+
+Add nocaptcha_recaptcha to your INSTALLED_APPS setting
+    
+Add the following to settings.py
+    
+    Required settings: 
+    NORECAPTCHA_SITE_KEY  (string) = the Google provided site_key
+    NORECAPTCHA_SECRET_KEY (string) = the Google provided secret_key 
+    
+    Optional Settings:
+    NORECAPTCHA_VERIFY_URL (string) = reCaptcha api endpoint for verification.
+        Best to leave this as the default setting.
+        Default is https://www.google.com/recaptcha/api/siteverify
+    NORECAPTCHA_WIDGET_TEMPLATE (string) = location for the widget template.  
+        Default is nocaptcha_recaptcha/widget.html
+
+
+Add the field to a form that you want to protect.
+
+	from nocaptcha_recaptcha.fields import NoReCaptchaField
+	
+	class DemoForm(forms.Form):
+	    .....
+	    captcha = NoReCaptchaField()
+	    
+
+Add Google's JavaScript library to your base template or elsewhere, so it is
+available on the page containing the django form.
+
+    <script src="https://www.google.com/recaptcha/api.js" async defer></script>	    
+
+
+(optional)
+You can customize the field.  
+	
+- You can add attributes to the g-recaptcha div tag through the following
+     
+     captcha = NoReCaptchaField(gtag_attrs={'data-theme':'dark'}))
+     
+- You can override the template for the widget like you would any
+  other django template. 
+
+
+# DEMO PROJECT 
+
+The demo project includes a fully working example of this module. 
+To use it, run the following:
+ 
+    cd demo
+    export NORECAPTCHA_SITE_KEY="<your site key>"
+    export NORECAPTCHA_SECRET_KEY="<your secret key>"
+    ./manage.py runserver 
+   
+    # in a browser, visit http://localhost:8000
+     
+# TESTING
+
+    python setup.py test
diff --git a/demo/demo/__init__.py b/demo/demo/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/demo/demo/settings.py b/demo/demo/settings.py
new file mode 100644
index 0000000..d763a55
--- /dev/null
+++ b/demo/demo/settings.py
@@ -0,0 +1,212 @@
+# Django settings for demo project.
+import os
+import sys
+from django import VERSION
+
+PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ))
+sys.path.insert(0, os.path.join(PROJECT_ROOT, '..', '..'))
+
+DEBUG = True
+
+ADMINS = (
+    # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+    'default': {
+        # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': 'demo.sqlite3',  # Or path to database file if using sqlite3.
+        # The following settings are not used with sqlite3:
+        'USER': '',
+        'PASSWORD': '',
+        'HOST': '',
+        # Empty for localhost through domain sockets or '127.0.0.1' for
+        # localhost through TCP.
+        'PORT': '',  # Set to empty string for default.
+    }
+}
+
+
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = []
+
+# 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.
+# In a Windows environment this must be set to 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
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale.
+USE_L10N = True
+
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/var/www/example.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://example.com/media/", "http://media.example.com/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/var/www/example.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://example.com/static/", "http://static.example.com/"
+STATIC_URL = '/static/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+    # Put strings here, like "/home/html/static" or "C:/www/django/static".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+    'django.contrib.staticfiles.finders.FileSystemFinder',
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'x$47^-hmv-kaa0trcc*ry%+b^^2f)$rs#cl)6j&!)j2c&h%88e'
+
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    # Uncomment the next line for simple clickjacking protection:
+    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'demo.urls'
+
+# Python dotted path to the WSGI application used by Django's runserver.
+WSGI_APPLICATION = 'demo.wsgi.application'
+
+
+if VERSION >= (1, 8, 0):
+
+    TEMPLATES = [
+        {
+            'BACKEND': 'django.template.backends.django.DjangoTemplates',
+            'DIRS': [
+                os.path.join(PROJECT_ROOT, 'templates'),
+            ],
+            'APP_DIRS': True,
+            'OPTIONS': {
+                'context_processors': [
+                    'django.contrib.auth.context_processors.auth',
+                    'django.template.context_processors.i18n',
+                    'django.template.context_processors.request',
+                    'django.template.context_processors.media',
+                    'django.template.context_processors.static',
+                    'django.contrib.messages.context_processors.messages',
+                    'example.apps.core.context_processors.site',
+                ],
+            },
+        },
+    ]
+else:
+
+    TEMPLATE_DEBUG = DEBUG
+
+    # List of callables that know how to import templates from various sources.
+    TEMPLATE_LOADERS = (
+        'django.template.loaders.filesystem.Loader',
+        'django.template.loaders.app_directories.Loader',
+        # 'django.template.loaders.eggs.Loader',
+    )
+
+    TEMPLATE_DIRS = (
+        # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+        # Always use forward slashes, even on Windows.
+        # Don't forget to use absolute paths, not relative paths.
+        os.path.join(PROJECT_ROOT, 'templates'),
+    )
+
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'django.contrib.admin',
+    # Uncomment the next line to enable admin documentation:
+    # 'django.contrib.admindocs',
+
+    # Only needed for running unit tests
+    'nocaptcha_recaptcha',
+)
+
+
+SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error when DEBUG=False.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'filters': {
+        'require_debug_false': {
+            '()': 'django.utils.log.RequireDebugFalse'
+        }
+    },
+    'handlers': {
+        'mail_admins': {
+            'level': 'ERROR',
+            'filters': ['require_debug_false'],
+            'class': 'django.utils.log.AdminEmailHandler'
+        },
+        'console': {
+            'level': 'DEBUG',
+            'class': 'logging.StreamHandler',
+        },
+    },
+    'loggers': {
+        'django.request': {
+            'handlers': ['mail_admins'],
+            'level': 'ERROR',
+            'propagate': True,
+        },
+        'iscapeauth': {
+            'handlers': ['console'],
+            'level': "DEBUG",
+            'propogate': True,
+        }
+    }
+}
+
+NORECAPTCHA_SITE_KEY = os.environ.get('NORECAPTCHA_SITE_KEY', "")
+NORECAPTCHA_SECRET_KEY = os.environ.get('NORECAPTCHA_SECRET_KEY', "")
diff --git a/demo/demo/templates/index.html b/demo/demo/templates/index.html
new file mode 100644
index 0000000..e02b2b4
--- /dev/null
+++ b/demo/demo/templates/index.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+    <title>reCAPTCHA demo: Simple page</title>
+    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
+</head>
+<body>
+
+<form method="post" action=".">{% csrf_token %}
+    {{ form.as_p }}
+    <button type="submit">Submit</button>
+</form>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/demo/demo/templates/success.html b/demo/demo/templates/success.html
new file mode 100644
index 0000000..7af069f
--- /dev/null
+++ b/demo/demo/templates/success.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+    <title>reCAPTCHA demo: Simple page</title>
+</head>
+<body>
+Success!
+</body>
+</html>
\ No newline at end of file
diff --git a/demo/demo/urls.py b/demo/demo/urls.py
new file mode 100644
index 0000000..0307794
--- /dev/null
+++ b/demo/demo/urls.py
@@ -0,0 +1,18 @@
+from django.contrib import admin
+from django.conf.urls import include, url
+from django.views.generic import TemplateView
+
+from . import views
+
+admin.autodiscover()
+
+urlpatterns = [
+
+    url(r'^$', views.DemoView.as_view(template_name="index.html"), {},
+        name="index"),
+    url(r'^success/$', TemplateView.as_view(template_name="success.html"), {},
+        name="success"),
+
+    # Uncomment the next line to enable the admin:
+    url(r'^admin/', include(admin.site.urls)),
+]
diff --git a/demo/demo/views.py b/demo/demo/views.py
new file mode 100644
index 0000000..6d2c4b4
--- /dev/null
+++ b/demo/demo/views.py
@@ -0,0 +1,18 @@
+from django import forms
+from django.core.urlresolvers import reverse_lazy
+from django.views.generic import FormView
+
+from nocaptcha_recaptcha.fields import NoReCaptchaField
+
+
+class DemoForm(forms.Form):
+    username = forms.CharField(required=True)
+    captcha = NoReCaptchaField(gtag_attrs={'data-theme': 'dark'})
+
+
+class DemoView(FormView):
+    form_class = DemoForm
+    success_url = reverse_lazy('success')
+
+    def form_valid(self, form):
+        return super(DemoView, self).form_valid(form)
\ No newline at end of file
diff --git a/demo/demo/wsgi.py b/demo/demo/wsgi.py
new file mode 100644
index 0000000..8d4d998
--- /dev/null
+++ b/demo/demo/wsgi.py
@@ -0,0 +1,33 @@
+"""
+WSGI config for demo project.
+
+This module contains the WSGI application used by Django's development server
+and any production WSGI deployments. It should expose a module-level variable
+named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
+this application via the ``WSGI_APPLICATION`` setting.
+
+Usually you will have the standard Django WSGI application here, but it also
+might make sense to replace the whole Django WSGI application with a custom one
+that later delegates to the Django one. For example, you could introduce WSGI
+middleware here, or combine a Django application with an application of another
+framework.
+
+"""
+import os
+
+# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
+# if running multiple sites in the same mod_wsgi process. To fix this, use
+# mod_wsgi daemon mode with each site in its own daemon process, or use
+# os.environ["DJANGO_SETTINGS_MODULE"] = "demo.settings"
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
+
+# This application object is used by any WSGI server configured to use this
+# file. This includes Django's development server, if the WSGI_APPLICATION
+# setting points here.
+from django.core.wsgi import get_wsgi_application
+
+application = get_wsgi_application()
+
+# Apply WSGI middleware here.
+# from helloworld.wsgi import HelloWorldApplication
+# application = HelloWorldApplication(application)
diff --git a/demo/manage.py b/demo/manage.py
new file mode 100755
index 0000000..86cc0b0
--- /dev/null
+++ b/demo/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)
diff --git a/demo/requirements.txt b/demo/requirements.txt
new file mode 100644
index 0000000..b16808d
--- /dev/null
+++ b/demo/requirements.txt
@@ -0,0 +1,3 @@
+Django>=1.5,<1.11
+mock>=1.0.1
+six>=1.8.0
diff --git a/nocaptcha_recaptcha/__init__.py b/nocaptcha_recaptcha/__init__.py
new file mode 100644
index 0000000..50404c7
--- /dev/null
+++ b/nocaptcha_recaptcha/__init__.py
@@ -0,0 +1,4 @@
+__version__ = '0.0.19'
+
+from .fields import NoReCaptchaField
+from .widgets import NoReCaptchaWidget
diff --git a/nocaptcha_recaptcha/_compat.py b/nocaptcha_recaptcha/_compat.py
new file mode 100644
index 0000000..22c1f91
--- /dev/null
+++ b/nocaptcha_recaptcha/_compat.py
@@ -0,0 +1,18 @@
+import sys
+
+PY2 = sys.version_info[0] == 2
+if PY2:
+    text_type = unicode
+    from urllib2 import Request, urlopen
+    from urllib import urlencode
+else:
+    from urllib.request import Request, urlopen
+    from urllib.parse import urlencode
+
+    text_type = str
+
+
+def want_bytes(s, encoding='utf-8', errors='strict'):
+    if isinstance(s, text_type):
+        s = s.encode(encoding, errors)
+    return s
diff --git a/nocaptcha_recaptcha/client.py b/nocaptcha_recaptcha/client.py
new file mode 100644
index 0000000..84dcadd
--- /dev/null
+++ b/nocaptcha_recaptcha/client.py
@@ -0,0 +1,117 @@
+import logging
+
+import django
+
+try:
+    import json
+except ImportError:
+    from django.utils import simplejson as json
+
+from django.conf import settings
+from django.template.loader import render_to_string
+from django.utils.translation import get_language
+from django.utils.encoding import force_text
+
+from ._compat import want_bytes, urlencode, Request, urlopen, PY2
+
+logger = logging.getLogger(__name__)
+
+DEFAULT_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
+DEFAULT_FALLBACK_URL = "https://www.google.com/recaptcha/api/fallback"
+DEFAULT_WIDGET_TEMPLATE = 'nocaptcha_recaptcha/widget.html'
+
+VERIFY_URL = getattr(settings, "NORECAPTCHA_VERIFY_URL",
+                     DEFAULT_VERIFY_URL)
+
+FALLBACK_URL = getattr(settings, "NORECAPTCHA_FALLBACK_URL",
+                       DEFAULT_FALLBACK_URL)
+
+WIDGET_TEMPLATE = getattr(settings, "NORECAPTCHA_WIDGET_TEMPLATE",
+                          DEFAULT_WIDGET_TEMPLATE)
+
+
+class RecaptchaResponse(object):
+    def __init__(self, is_valid, error_codes=None):
+        self.is_valid = is_valid
+        self.error_codes = error_codes
+
+
+def displayhtml(site_key, gtag_attrs, js_params):
+    """Gets the HTML to display for reCAPTCHA
+
+    site_key -- The public api key provided by Google ReCaptcha
+    """
+
+    if 'hl' not in js_params:
+        js_params['hl'] = get_language()[:2]
+
+    return render_to_string(
+        WIDGET_TEMPLATE,
+        {
+            'fallback_url': FALLBACK_URL,
+            'site_key': site_key,
+            'js_params': js_params,
+            'gtag_attrs': gtag_attrs,
+        })
+
+
+def submit(g_nocaptcha_response_value, secret_key, remoteip):
+    """
+    Submits a reCAPTCHA request for verification. Returns RecaptchaResponse
+    for the request
+
+    recaptcha_response_field -- The value of recaptcha_response_field
+    from the form
+    secret_key -- your reCAPTCHA private key
+    remoteip -- the user's ip address
+    """
+
+    if not (g_nocaptcha_response_value and len(g_nocaptcha_response_value)):
+        return RecaptchaResponse(
+            is_valid=False,
+            error_codes=['incorrect-captcha-sol']
+        )
+
+    params = urlencode({
+        'secret': want_bytes(secret_key),
+        'remoteip': want_bytes(remoteip),
+        'response': want_bytes(g_nocaptcha_response_value),
+    })
+
+    if not PY2:
+        params = params.encode('utf-8')
+
+    req = Request(
+        url=VERIFY_URL, data=params,
+        headers={
+            'Content-type': 'application/x-www-form-urlencoded',
+            'User-agent': 'noReCAPTCHA Python'
+        }
+    )
+
+    httpresp = urlopen(req)
+
+    try:
+        res = force_text(httpresp.read())
+        return_values = json.loads(res)
+    except (ValueError, TypeError):
+        return RecaptchaResponse(
+            is_valid=False,
+            error_codes=['json-read-issue']
+        )
+    except:
+        return RecaptchaResponse(
+            is_valid=False,
+            error_codes=['unknown-network-issue']
+        )
+    finally:
+        httpresp.close()
+
+    return_code = return_values.get("success", False)
+    error_codes = return_values.get('error-codes', [])
+    logger.debug("%s - %s" % (return_code, error_codes))
+
+    if return_code is True:
+        return RecaptchaResponse(is_valid=True)
+    else:
+        return RecaptchaResponse(is_valid=False, error_codes=error_codes)
diff --git a/nocaptcha_recaptcha/fields.py b/nocaptcha_recaptcha/fields.py
new file mode 100644
index 0000000..738a295
--- /dev/null
+++ b/nocaptcha_recaptcha/fields.py
@@ -0,0 +1,75 @@
+import os
+import sys
+
+from django import forms
+from django.conf import settings
+
+try:
+    from django.utils.encoding import smart_unicode
+except ImportError:
+    from django.utils.encoding import smart_text as smart_unicode
+
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext_lazy as _
+
+from . import client
+from .widgets import NoReCaptchaWidget
+
+
+class NoReCaptchaField(forms.CharField):
+    default_error_messages = {
+        'captcha_invalid': _('Incorrect, please try again.')
+    }
+
+    def __init__(self, site_key=None, secret_key=None,
+                 gtag_attrs={}, js_params={}, *args, **kwargs):
+        """
+        site_key = the Google provided site_key
+        secret_key = the Google provided secret_key
+        gtag_attrs = html input attributes to provide
+            to the g-recaptcha tag
+        js_params = parameters to passed to the javascript backend
+
+        See: https://developers.google.com/recaptcha/docs/display
+        """
+        site_key = site_key if site_key else \
+            settings.NORECAPTCHA_SITE_KEY
+        self.secret_key = secret_key if secret_key else \
+            settings.NORECAPTCHA_SECRET_KEY
+
+        self.widget = NoReCaptchaWidget(
+            site_key=site_key, gtag_attrs=gtag_attrs, js_params=js_params)
+        self.required = True
+        super(NoReCaptchaField, self).__init__(*args, **kwargs)
+
+    def get_remote_ip(self):
+        """
+        Return the remote IP from the request.
+        First check the REMOTE_ADDR header and then the
+        HTTP_X_FORWARDED_FOR header.
+        """
+        f = sys._getframe()
+        while f:
+            if 'request' in f.f_locals:
+                request = f.f_locals['request']
+                if request:
+                    remote_ip = request.META.get('REMOTE_ADDR', '')
+                    forwarded_ip = request.META.get('HTTP_X_FORWARDED_FOR', '')
+                    ip = remote_ip if not forwarded_ip else forwarded_ip
+                    return ip
+            f = f.f_back
+
+    def clean(self, value):
+        super(NoReCaptchaField, self).clean(value)
+        g_nocaptcha_response_value = smart_unicode(value)
+        if os.environ.get('NORECAPTCHA_TESTING', None) == 'True' \
+                and g_nocaptcha_response_value == 'PASSED':
+            return value
+
+        check_captcha = client.submit(
+            g_nocaptcha_response_value, secret_key=self.secret_key,
+            remoteip=self.get_remote_ip())
+
+        if not check_captcha.is_valid:
+            raise ValidationError(self.error_messages['captcha_invalid'])
+        return value
diff --git a/nocaptcha_recaptcha/models.py b/nocaptcha_recaptcha/models.py
new file mode 100644
index 0000000..e69de29
diff --git a/nocaptcha_recaptcha/templates/nocaptcha_recaptcha/widget.html b/nocaptcha_recaptcha/templates/nocaptcha_recaptcha/widget.html
new file mode 100644
index 0000000..17d6d11
--- /dev/null
+++ b/nocaptcha_recaptcha/templates/nocaptcha_recaptcha/widget.html
@@ -0,0 +1,21 @@
+<div class="g-recaptcha" {% for attr in gtag_attrs.items %}{{ attr.0 }}="{{ attr.1 }}" {% endfor %}data-sitekey="{{ site_key }}"></div>
+<noscript>
+    <div style="width: 302px; height: 352px;">
+        <div style="width: 302px; height: 352px; position: relative;">
+            <div style="width: 302px; height: 352px; position: absolute;">
+                <iframe src="{{ fallback_url }}?k={{ site_key }}"
+                        frameborder="0" scrolling="no"
+                        style="width: 302px; height:352px; border-style: none;">
+                </iframe>
+            </div>
+            <div style="width: 250px; height: 80px; position: absolute; border-style: none;
+                  bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
+                <textarea id="g-recaptcha-response" name="g-recaptcha-response"
+                          class="g-recaptcha-response"
+                          style="width: 250px; height: 80px; border: 1px solid #c1c1c1;
+                         margin: 0px; padding: 0px; resize: none;" value="">
+                </textarea>
+            </div>
+        </div>
+    </div>
+</noscript>
\ No newline at end of file
diff --git a/nocaptcha_recaptcha/tests.py b/nocaptcha_recaptcha/tests.py
new file mode 100644
index 0000000..9b0205c
--- /dev/null
+++ b/nocaptcha_recaptcha/tests.py
@@ -0,0 +1,79 @@
+import os
+import json
+
+from django.forms import Form
+from django.test import TestCase
+
+import mock
+
+from nocaptcha_recaptcha import fields, client
+
+
+class TestForm(Form):
+    captcha = fields.NoReCaptchaField(gtag_attrs={'data-theme': 'dark'})
+
+
+class TestCase(TestCase):
+    def setUp(self):
+        os.environ['NORECAPTCHA_TESTING'] = 'True'
+
+    def test_envvar_enabled(self):
+        form_params = {'g-recaptcha-response': 'PASSED'}
+        form = TestForm(form_params)
+        self.assertTrue(form.is_valid())
+
+    def test_envvar_disabled(self):
+        os.environ['NORECAPTCHA_TESTING'] = 'False'
+        form_params = {'g-recaptcha-response': 'PASSED'}
+        form = TestForm(form_params)
+        self.assertFalse(form.is_valid())
+
+    @mock.patch('nocaptcha_recaptcha.client.urlopen')
+    def test_client_submit_empty_input(self, mock_urlopen):
+        """
+        Should return False if input is empty string
+        """
+        result = client.submit('', '', '')
+        self.assertFalse(result.is_valid)
+        self.assertEqual(['incorrect-captcha-sol'], result.error_codes)
+
+    @mock.patch('nocaptcha_recaptcha.client.urlopen')
+    def test_client_submit_correct(self, mock_urlopen):
+        """
+        Should return True if response is correct
+        """
+        mock_resp = mock.Mock()
+        mock_resp.read.return_value = json.dumps(
+            {'success': True, 'error-codes': []})
+        mock_urlopen.return_value = mock_resp
+        result = client.submit('a', 'a', 'a')
+        self.assertTrue(result.is_valid)
+        self.assertEqual(result.error_codes, None)
+
+    @mock.patch('nocaptcha_recaptcha.client.urlopen')
+    def test_client_submit_response_not_json(self, mock_urlopen):
+        """
+        Should return json read error if response is not json
+        """
+        mock_resp = mock.Mock()
+        mock_resp.read.return_value = "{'success': True, 'error-codes': []}"
+        mock_urlopen.return_value = mock_resp
+        result = client.submit('a', 'a', 'a')
+        self.assertFalse(result.is_valid)
+        self.assertEqual(result.error_codes, ['json-read-issue'])
+
+    @mock.patch('nocaptcha_recaptcha.client.urlopen')
+    def test_client_submit_response_incorrect(self, mock_urlopen):
+        """
+        Should return false if response is incorrect
+        """
+        mock_resp = mock.Mock()
+        mock_resp.read.return_value = json.dumps(
+            {'success': False, 'error-codes': ['ERROR']})
+        mock_urlopen.return_value = mock_resp
+        result = client.submit('a', 'a', 'a')
+        self.assertFalse(result.is_valid)
+        self.assertEqual(result.error_codes, ['ERROR'])
+
+    def tearDown(self):
+        del os.environ['NORECAPTCHA_TESTING']
diff --git a/nocaptcha_recaptcha/views.py b/nocaptcha_recaptcha/views.py
new file mode 100644
index 0000000..e69de29
diff --git a/nocaptcha_recaptcha/widgets.py b/nocaptcha_recaptcha/widgets.py
new file mode 100644
index 0000000..95380e5
--- /dev/null
+++ b/nocaptcha_recaptcha/widgets.py
@@ -0,0 +1,24 @@
+from django import forms
+from django.conf import settings
+from django.utils.safestring import mark_safe
+
+from . import client
+
+
+class NoReCaptchaWidget(forms.widgets.Widget):
+    g_nocaptcha_response = 'g-recaptcha-response'
+
+    def __init__(self, site_key=None,
+                 gtag_attrs={}, js_params={}, *args, **kwargs):
+        self.site_key = site_key if site_key else \
+            settings.NORECAPTCHA_SITE_KEY
+        super(NoReCaptchaWidget, self).__init__(*args, **kwargs)
+        self.gtag_attrs = gtag_attrs
+        self.js_params = js_params
+
+    def render(self, name, value, gtag_attrs=None, **kwargs):
+        return mark_safe(u'%s' % client.displayhtml(
+            self.site_key, self.gtag_attrs, self.js_params))
+
+    def value_from_datadict(self, data, files, name):
+        return data.get(self.g_nocaptcha_response, None)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..40c19d7
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,38 @@
+import os
+from setuptools import setup, find_packages
+
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+
+setup(
+    name='django-nocaptcha-recaptcha',
+    version='0.0.20',
+    description='Django nocaptcha recaptcha form field/widget app.',
+    long_description=read('README.md'),
+    author='Imaginary Landscape',
+    author_email='jjasinski@imgescape.com',
+    keywords=['django', 'recaptcha', 'field', 'nocaptcha'],
+    license='BSD',
+    url='https://github.com/ImaginaryLandscape/django-nocaptcha-recaptcha',
+    packages=find_packages(),
+    tests_require=[
+        'mock',
+    ],
+    test_suite="setuptest.setuptest.SetupTestSuite",
+    include_package_data=True,
+    classifiers=[
+        "Framework :: Django",
+        "License :: OSI Approved :: BSD License",
+        "Operating System :: OS Independent",
+        "Intended Audience :: Developers",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
+    ],
+    zip_safe=False,
+)
diff --git a/test_settings.py b/test_settings.py
new file mode 100644
index 0000000..4ff3137
--- /dev/null
+++ b/test_settings.py
@@ -0,0 +1,23 @@
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': 'test.sqlite',
+    }
+}
+
+INSTALLED_APPS = [
+    'nocaptcha_recaptcha',
+]
+
+MIDDLEWARE_CLASSES = [
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.locale.LocaleMiddleware',
+    'django.middleware.doc.XViewMiddleware',
+    'django.middleware.common.CommonMiddleware',
+]
+
+NORECAPTCHA_SECRET_KEY = 'privkey'
+NORECAPTCHA_SITE_KEY = 'pubkey'