diff --git a/thirdpart/termsandconditions/__init__.py b/thirdpart/termsandconditions/__init__.py
new file mode 100644
index 0000000000..d84400e5be
--- /dev/null
+++ b/thirdpart/termsandconditions/__init__.py
@@ -0,0 +1,2 @@
+"""Django Terms and Conditions Module"""
+from __future__ import unicode_literals
diff --git a/thirdpart/termsandconditions/admin.py b/thirdpart/termsandconditions/admin.py
new file mode 100644
index 0000000000..96bae518ee
--- /dev/null
+++ b/thirdpart/termsandconditions/admin.py
@@ -0,0 +1,25 @@
+"""Django Admin Site configuration"""
+
+# pylint: disable=R0904
+
+from django.contrib import admin
+from .models import TermsAndConditions, UserTermsAndConditions
+
+
+class TermsAndConditionsAdmin(admin.ModelAdmin):
+ """Sets up the custom Terms and Conditions admin display"""
+ list_display = ('slug', 'name', 'date_active', 'version_number',)
+ verbose_name = "Terms and Conditions"
+
+
+class UserTermsAndConditionsAdmin(admin.ModelAdmin):
+ """Sets up the custom User Terms and Conditions admin display"""
+ #fields = ('terms', 'user', 'date_accepted', 'ip_address',)
+ readonly_fields = ('date_accepted',)
+ list_display = ('terms', 'user', 'date_accepted', 'ip_address',)
+ date_hierarchy = 'date_accepted'
+ list_select_related = True
+
+
+admin.site.register(TermsAndConditions, TermsAndConditionsAdmin)
+admin.site.register(UserTermsAndConditions, UserTermsAndConditionsAdmin)
diff --git a/thirdpart/termsandconditions/apps.py b/thirdpart/termsandconditions/apps.py
new file mode 100644
index 0000000000..6abe3c73a2
--- /dev/null
+++ b/thirdpart/termsandconditions/apps.py
@@ -0,0 +1,8 @@
+"""Django Apps Config"""
+
+from django.apps import AppConfig
+
+
+class TermsAndConditionsConfig(AppConfig):
+ name = 'termsandconditions'
+ verbose_name = "Terms and Conditions"
diff --git a/thirdpart/termsandconditions/decorators.py b/thirdpart/termsandconditions/decorators.py
new file mode 100644
index 0000000000..569c4d0ed7
--- /dev/null
+++ b/thirdpart/termsandconditions/decorators.py
@@ -0,0 +1,31 @@
+"""View Decorators for termsandconditions module"""
+try:
+ from urllib.parse import urlparse, urlunparse
+except ImportError:
+ from urlparse import urlparse, urlunparse
+from functools import wraps
+from django.http import HttpResponseRedirect, QueryDict
+from django.utils.decorators import available_attrs
+from .models import TermsAndConditions
+from .middleware import ACCEPT_TERMS_PATH
+
+
+def terms_required(view_func):
+ """
+ This decorator checks to see if the user is logged in, and if so, if they have accepted the site terms.
+ """
+
+ @wraps(view_func, assigned=available_attrs(view_func))
+ def _wrapped_view(request, *args, **kwargs):
+ """Method to wrap the view passed in"""
+ if not request.user.is_authenticated() or TermsAndConditions.agreed_to_latest(request.user):
+ return view_func(request, *args, **kwargs)
+
+ currentPath = request.path
+ login_url_parts = list(urlparse(ACCEPT_TERMS_PATH))
+ querystring = QueryDict(login_url_parts[4], mutable=True)
+ querystring['returnTo'] = currentPath
+ login_url_parts[4] = querystring.urlencode(safe='/')
+ return HttpResponseRedirect(urlunparse(login_url_parts))
+
+ return _wrapped_view
diff --git a/thirdpart/termsandconditions/forms.py b/thirdpart/termsandconditions/forms.py
new file mode 100644
index 0000000000..2bb60c264c
--- /dev/null
+++ b/thirdpart/termsandconditions/forms.py
@@ -0,0 +1,26 @@
+"""Django forms for the termsandconditions application"""
+
+# pylint: disable=E1120,W0613
+
+from django import forms
+from termsandconditions.models import UserTermsAndConditions, TermsAndConditions
+
+
+class UserTermsAndConditionsModelForm(forms.ModelForm):
+ """Form used when accepting Terms and Conditions - returnTo is used to catch where to end up."""
+
+ returnTo = forms.CharField(required=False, initial="/", widget=forms.HiddenInput())
+
+ class Meta(object):
+ """Configuration for this Modelform"""
+ model = UserTermsAndConditions
+ exclude = ('date_accepted', 'ip_address', 'user')
+ widgets = {'terms': forms.HiddenInput()}
+
+
+class EmailTermsForm(forms.Form):
+ """Form used to collect email address to send terms and conditions to."""
+ email_subject = forms.CharField(widget=forms.HiddenInput())
+ email_address = forms.EmailField()
+ returnTo = forms.CharField(required=False, initial="/", widget=forms.HiddenInput())
+ terms = forms.ModelChoiceField(queryset=TermsAndConditions.objects.all(), widget=forms.HiddenInput())
diff --git a/thirdpart/termsandconditions/middleware.py b/thirdpart/termsandconditions/middleware.py
new file mode 100644
index 0000000000..9c0ebac80b
--- /dev/null
+++ b/thirdpart/termsandconditions/middleware.py
@@ -0,0 +1,55 @@
+"""Terms and Conditions Middleware"""
+from .models import TermsAndConditions
+from django.conf import settings
+import logging
+from .pipeline import redirect_to_terms_accept
+
+LOGGER = logging.getLogger(name='termsandconditions')
+
+ACCEPT_TERMS_PATH = getattr(settings, 'ACCEPT_TERMS_PATH', '/terms/accept/')
+TERMS_EXCLUDE_URL_PREFIX_LIST = getattr(settings, 'TERMS_EXCLUDE_URL_PREFIX_LIST', {'/admin', '/terms'})
+TERMS_EXCLUDE_URL_LIST = getattr(settings, 'TERMS_EXCLUDE_URL_LIST', {'/', '/termsrequired/', '/logout/', '/securetoo/'})
+
+
+class TermsAndConditionsRedirectMiddleware(object):
+ """
+ This middleware checks to see if the user is logged in, and if so,
+ if they have accepted the site terms.
+ """
+
+ def process_request(self, request):
+ """Process each request to app to ensure terms have been accepted"""
+
+ LOGGER.debug('termsandconditions.middleware')
+
+ current_path = request.META['PATH_INFO']
+ protected_path = is_path_protected(current_path)
+
+ if request.user.is_authenticated() and protected_path:
+ for term in TermsAndConditions.get_active_list():
+ if not TermsAndConditions.agreed_to_latest(request.user, term):
+ return redirect_to_terms_accept(current_path, term)
+ return None
+
+
+def is_path_protected(path):
+ """
+ returns True if given path is to be protected, otherwise False
+
+ The path is not to be protected when it appears on:
+ TERMS_EXCLUDE_URL_PREFIX_LIST, TERMS_EXCLUDE_URL_LIST or as
+ ACCEPT_TERMS_PATH
+ """
+ protected = True
+
+ for exclude_path in TERMS_EXCLUDE_URL_PREFIX_LIST:
+ if path.startswith(exclude_path):
+ protected = False
+
+ if path in TERMS_EXCLUDE_URL_LIST:
+ protected = False
+
+ if path.startswith(ACCEPT_TERMS_PATH):
+ protected = False
+
+ return protected
diff --git a/thirdpart/termsandconditions/migrations/0001_initial.py b/thirdpart/termsandconditions/migrations/0001_initial.py
new file mode 100644
index 0000000000..f3e2bf1344
--- /dev/null
+++ b/thirdpart/termsandconditions/migrations/0001_initial.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='TermsAndConditions',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('slug', models.SlugField(default=b'site-terms')),
+ ('name', models.TextField(max_length=255)),
+ ('version_number', models.DecimalField(default=1.0, max_digits=6, decimal_places=2)),
+ ('text', models.TextField(null=True, blank=True)),
+ ('date_active', models.DateTimeField(help_text=b'Leave Null To Never Make Active', null=True, blank=True)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ],
+ options={
+ 'ordering': ['-date_active'],
+ 'get_latest_by': 'date_active',
+ 'verbose_name': 'Terms and Conditions',
+ 'verbose_name_plural': 'Terms and Conditions',
+ },
+ ),
+ migrations.CreateModel(
+ name='UserTermsAndConditions',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('ip_address', models.GenericIPAddressField(null=True, verbose_name=b'IP Address', blank=True)),
+ ('date_accepted', models.DateTimeField(auto_now_add=True, verbose_name=b'Date Accepted')),
+ ('terms', models.ForeignKey(related_name='userterms', to='termsandconditions.TermsAndConditions')),
+ ('user', models.ForeignKey(related_name='userterms', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'get_latest_by': 'date_accepted',
+ 'verbose_name': 'User Terms and Conditions',
+ 'verbose_name_plural': 'User Terms and Conditions',
+ },
+ ),
+ migrations.AddField(
+ model_name='termsandconditions',
+ name='users',
+ field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='termsandconditions.UserTermsAndConditions', blank=True),
+ ),
+ migrations.AlterUniqueTogether(
+ name='usertermsandconditions',
+ unique_together=set([('user', 'terms')]),
+ ),
+ ]
diff --git a/thirdpart/termsandconditions/migrations/0002_termsandconditions_info.py b/thirdpart/termsandconditions/migrations/0002_termsandconditions_info.py
new file mode 100644
index 0000000000..50bacfc1be
--- /dev/null
+++ b/thirdpart/termsandconditions/migrations/0002_termsandconditions_info.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('termsandconditions', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='termsandconditions',
+ name='info',
+ field=models.TextField(
+ help_text=b"Provide users with some info about "
+ b"what's changed and why", null=True, blank=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/thirdpart/termsandconditions/migrations/__init__.py b/thirdpart/termsandconditions/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/thirdpart/termsandconditions/models.py b/thirdpart/termsandconditions/models.py
new file mode 100644
index 0000000000..2b636bbd83
--- /dev/null
+++ b/thirdpart/termsandconditions/models.py
@@ -0,0 +1,122 @@
+"""Django Models for TermsAndConditions App"""
+
+# pylint: disable=C1001,E0202,W0613
+from collections import OrderedDict
+
+from django.db import models
+from django.conf import settings
+from django.http import Http404
+from django.utils import timezone
+import logging
+
+LOGGER = logging.getLogger(name='termsandconditions')
+
+DEFAULT_TERMS_SLUG = getattr(settings, 'DEFAULT_TERMS_SLUG', 'site-terms')
+
+
+class UserTermsAndConditions(models.Model):
+ """Holds mapping between TermsAndConditions and Users"""
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="userterms")
+ terms = models.ForeignKey("TermsAndConditions", related_name="userterms")
+ ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP Address')
+ date_accepted = models.DateTimeField(auto_now_add=True, verbose_name='Date Accepted')
+
+ class Meta:
+ """Model Meta Information"""
+ get_latest_by = 'date_accepted'
+ verbose_name = 'User Terms and Conditions'
+ verbose_name_plural = 'User Terms and Conditions'
+ unique_together = ('user', 'terms',)
+
+
+class TermsAndConditions(models.Model):
+ """Holds Versions of TermsAndConditions
+ Active one for a given slug is: date_active is not Null and is latest not in future"""
+ slug = models.SlugField(default=DEFAULT_TERMS_SLUG)
+ name = models.TextField(max_length=255)
+ users = models.ManyToManyField(settings.AUTH_USER_MODEL, through=UserTermsAndConditions, blank=True)
+ version_number = models.DecimalField(default=1.0, decimal_places=2, max_digits=6)
+ text = models.TextField(null=True, blank=True)
+ info = models.TextField(null=True, blank=True, help_text="Provide users with some info about what's changed and why")
+ date_active = models.DateTimeField(blank=True, null=True, help_text="Leave Null To Never Make Active")
+ date_created = models.DateTimeField(blank=True, auto_now_add=True)
+
+ class Meta:
+ """Model Meta Information"""
+ ordering = ['-date_active', ]
+ get_latest_by = 'date_active'
+ verbose_name = 'Terms and Conditions'
+ verbose_name_plural = 'Terms and Conditions'
+
+ def __str__(self):
+ return "{0}-{1:.2f}".format(self.slug, self.version_number)
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('tc_view_specific_version_page', [self.slug, self.version_number]) # pylint: disable=E1101
+
+ @staticmethod
+ def create_default_terms():
+ """Create a default TermsAndConditions Object"""
+ default_terms = TermsAndConditions.objects.create(
+ slug=DEFAULT_TERMS_SLUG,
+ name=DEFAULT_TERMS_SLUG,
+ date_active=timezone.now(),
+ version_number=1,
+ text=DEFAULT_TERMS_SLUG + " Text. CHANGE ME.")
+ return default_terms
+
+ @staticmethod
+ def get_active(slug=DEFAULT_TERMS_SLUG):
+ """Finds the latest of a particular terms and conditions"""
+
+ try:
+ active_terms = TermsAndConditions.objects.filter(
+ date_active__isnull=False,
+ date_active__lte=timezone.now(),
+ slug=slug).latest('date_active')
+ except TermsAndConditions.DoesNotExist:
+ if slug == DEFAULT_TERMS_SLUG:
+ active_terms = TermsAndConditions.create_default_terms()
+ else: # pragma: nocover
+ raise Http404
+
+ return active_terms
+
+ @staticmethod
+ def get_active_list():
+ """Finds the latest of all terms and conditions"""
+ terms_list = {}
+ try:
+ all_terms_list = TermsAndConditions.objects.filter(
+ date_active__isnull=False,
+ date_active__lte=timezone.now()).order_by('slug')
+ for term in all_terms_list:
+ terms_list.update({term.slug: TermsAndConditions.get_active(slug=term.slug)})
+ except TermsAndConditions.DoesNotExist: # pragma: nocover
+ terms_list.update({DEFAULT_TERMS_SLUG: TermsAndConditions.create_default_terms()})
+
+ terms_list = OrderedDict(sorted(terms_list.items(), key=lambda t: t[0]))
+ return terms_list
+
+ @staticmethod
+ def agreed_to_latest(user, slug=DEFAULT_TERMS_SLUG):
+ """Checks to see if a specified user has agreed to the latest of a particular terms and conditions"""
+
+ try:
+ UserTermsAndConditions.objects.get(user=user, terms=TermsAndConditions.get_active(slug))
+ return True
+ except UserTermsAndConditions.MultipleObjectsReturned: # pragma: nocover
+ return True
+ except UserTermsAndConditions.DoesNotExist:
+ return False
+
+ @staticmethod
+ def agreed_to_terms(user, terms=None):
+ """Checks to see if a specified user has agreed to a specific terms and conditions"""
+
+ try:
+ UserTermsAndConditions.objects.get(user=user, terms=terms)
+ return True
+ except UserTermsAndConditions.DoesNotExist:
+ return False
diff --git a/thirdpart/termsandconditions/pipeline.py b/thirdpart/termsandconditions/pipeline.py
new file mode 100644
index 0000000000..0ade7d5d69
--- /dev/null
+++ b/thirdpart/termsandconditions/pipeline.py
@@ -0,0 +1,40 @@
+"""This file contains functions used as part of a user creation pipeline, such as django-social-auth."""
+
+# pylint: disable=W0613
+
+try:
+ from urllib.parse import urlparse, urlunparse
+except ImportError:
+ from urlparse import urlparse, urlunparse
+from .models import TermsAndConditions
+from django.http import HttpResponseRedirect, QueryDict
+from django.conf import settings
+from django.core.urlresolvers import reverse
+import logging
+
+ACCEPT_TERMS_PATH = getattr(settings, 'ACCEPT_TERMS_PATH', '/terms/accept/')
+TERMS_RETURNTO_PARAM = getattr(settings, 'TERMS_RETURNTO_PARAM', 'returnTo')
+
+LOGGER = logging.getLogger(name='termsandconditions')
+
+
+def user_accept_terms(backend, user, uid, social_user=None, *args, **kwargs):
+ """Check if the user has accepted the terms and conditions after creation."""
+
+ LOGGER.debug('user_accept_terms')
+
+ if not TermsAndConditions.agreed_to_latest(user):
+ return redirect_to_terms_accept('/')
+ else:
+ return {'social_user': social_user, 'user': user}
+
+
+def redirect_to_terms_accept(current_path='/', slug='default'):
+ """Redirect the user to the terms and conditions accept page."""
+ redirect_url_parts = list(urlparse(ACCEPT_TERMS_PATH))
+ if slug != 'default':
+ redirect_url_parts[2] += slug
+ querystring = QueryDict(redirect_url_parts[4], mutable=True)
+ querystring[TERMS_RETURNTO_PARAM] = current_path
+ redirect_url_parts[4] = querystring.urlencode(safe='/')
+ return HttpResponseRedirect(urlunparse(redirect_url_parts))
diff --git a/thirdpart/termsandconditions/static/termsandconditions/css/modal.css b/thirdpart/termsandconditions/static/termsandconditions/css/modal.css
new file mode 100644
index 0000000000..2e7cc8da98
--- /dev/null
+++ b/thirdpart/termsandconditions/static/termsandconditions/css/modal.css
@@ -0,0 +1,65 @@
+.termsandconditions-modal {
+ visibility: hidden;
+ position: fixed;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ font-family: Arial, Helvetica, sans-serif;
+ text-align:center;
+ z-index: 999;
+ background: rgba(4, 10, 30, 0.8);
+ -moz-transition: all 0.5s ease-out;
+ -webkit-transition: all 0.5s ease-out;
+ -o-transition: all 0.5s ease-out;
+ transition: all 0.5s ease-out;
+}
+
+.termsandconditions-modal div {
+ poistion: absolute;
+ z-index: 1000;
+ top: 33%;
+ left: 25%;
+ width: 50%;
+ margin: 10% auto;
+ padding: 5px 20px 13px 20px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+ background: #ffffff;
+ background: -moz-linear-gradient(#ffffff, #cccccc);
+ background: -webkit-linear-gradient(#ffffff, #cccccc);
+ background: -o-linear-gradient(#ffffff, #cccccc);
+ box-shadow: 0 0 10px #000000;
+ -moz-box-shadow: 0 0 10px #000000;
+ -webkit-box-shadow: 0 0 10px #000000;
+}
+
+.termsandconditions-close {
+ display: block;
+ background: #606061;
+ color: #FFFFFF;
+ line-height: 25px;
+ position: relative;
+ left: -32px;
+ top: -16px;
+ width: 24px;
+ text-align: center;
+ text-decoration: none;
+ font-weight: bold;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+ border-radius: 12px;
+ box-shadow: 0 0 10px #000000;
+ -moz-box-shadow: 0 0 10px #000000;
+ -webkit-box-shadow: 0 0 10px #000000;
+}
+
+.termsandconditions-close:hover {
+ background: #bd362f;
+}
+
+body {
+ height:100%;
+}
diff --git a/thirdpart/termsandconditions/static/termsandconditions/js/modal.js b/thirdpart/termsandconditions/static/termsandconditions/js/modal.js
new file mode 100644
index 0000000000..bef33e0615
--- /dev/null
+++ b/thirdpart/termsandconditions/static/termsandconditions/js/modal.js
@@ -0,0 +1,7 @@
+function termsandconditions_overlay() {
+ var el = document.getElementsByClassName("termsandconditions-modal");
+ var i;
+ for (i = 0; i < el.length; i++) {
+ el[i].style.visibility = (el[i].style.visibility == "visible") ? "hidden" : "visible";
+ };
+};
diff --git a/thirdpart/termsandconditions/templates/termsandconditions/snippets/termsandconditions.html b/thirdpart/termsandconditions/templates/termsandconditions/snippets/termsandconditions.html
new file mode 100644
index 0000000000..2611fbf2cc
--- /dev/null
+++ b/thirdpart/termsandconditions/templates/termsandconditions/snippets/termsandconditions.html
@@ -0,0 +1,29 @@
+{% load staticfiles %}
+{% load i18n %}
+
+{% if terms %}
+
+
+
+
+
+
+
X
+
+ {% if terms.name %}
+ {{ terms.name|safe }}
+ {% else %}
+ {% trans 'Terms and Conditions' %}
+ {% endif %}
+
+ {% if terms.info %}{{ terms.info|safe }}{% endif %}
+ {% trans 'To accept new Terms, visit' %}
+
{% url 'tc_accept_specific_page' terms.slug %}
+
+
+{% endif %}
+
diff --git a/thirdpart/termsandconditions/templates/termsandconditions/tc_accept_terms.html b/thirdpart/termsandconditions/templates/termsandconditions/tc_accept_terms.html
new file mode 100644
index 0000000000..618762681b
--- /dev/null
+++ b/thirdpart/termsandconditions/templates/termsandconditions/tc_accept_terms.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+
+{% block headtitle %}Accept Terms and Conditions{% endblock %}
+
+{% block header %}{% endblock %}
+
+{% block content %}
+
+ Please Accept {{ form.initial.terms.name|safe }}
+ {{ form.errors }}
+
+ {{ form.initial.terms.text|safe }}
+
+
+ Print Terms & Conditions
+
+{% endblock %}
+{% block footer %}{% endblock %}
\ No newline at end of file
diff --git a/thirdpart/termsandconditions/templates/termsandconditions/tc_email_terms.html b/thirdpart/termsandconditions/templates/termsandconditions/tc_email_terms.html
new file mode 100644
index 0000000000..316bf53222
--- /dev/null
+++ b/thirdpart/termsandconditions/templates/termsandconditions/tc_email_terms.html
@@ -0,0 +1,5 @@
+{{ terms.name|safe }}
+
+Version {{ terms.version_number|safe }}
+
+{{ terms.text|safe }}
\ No newline at end of file
diff --git a/thirdpart/termsandconditions/templates/termsandconditions/tc_email_terms_form.html b/thirdpart/termsandconditions/templates/termsandconditions/tc_email_terms_form.html
new file mode 100644
index 0000000000..02b0bb2993
--- /dev/null
+++ b/thirdpart/termsandconditions/templates/termsandconditions/tc_email_terms_form.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block headtitle %}Email Terms and Conditions{% endblock %}
+
+{% block content %}
+
+ Email {{ form.initial.terms.name|safe }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/thirdpart/termsandconditions/templates/termsandconditions/tc_print_terms.html b/thirdpart/termsandconditions/templates/termsandconditions/tc_print_terms.html
new file mode 100644
index 0000000000..22af9fc8d0
--- /dev/null
+++ b/thirdpart/termsandconditions/templates/termsandconditions/tc_print_terms.html
@@ -0,0 +1,24 @@
+{% extends 'base.html' %}
+
+{% block headtitle %}Print Terms and Conditions{% endblock %}
+
+{% block body_class %}{% endblock %}
+
+{% block head %}
+
+{% endblock %}
+
+{% block header %}{% endblock %}
+
+{% block content %}
+ {{ terms.name|safe }}
+ Version {{ terms.version_number|safe }}
+
+
+ {{ terms.text|safe }}
+
+{% endblock %}
+
+{% block footer %}{% endblock %}
\ No newline at end of file
diff --git a/thirdpart/termsandconditions/templates/termsandconditions/tc_view_terms.html b/thirdpart/termsandconditions/templates/termsandconditions/tc_view_terms.html
new file mode 100644
index 0000000000..c19588a156
--- /dev/null
+++ b/thirdpart/termsandconditions/templates/termsandconditions/tc_view_terms.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+ {{ terms.name|safe }}
+
+
+ {{ terms.text|safe }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/thirdpart/termsandconditions/templatetags/__init__.py b/thirdpart/termsandconditions/templatetags/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/thirdpart/termsandconditions/templatetags/__init__.py
@@ -0,0 +1 @@
+
diff --git a/thirdpart/termsandconditions/templatetags/terms_tags.py b/thirdpart/termsandconditions/templatetags/terms_tags.py
new file mode 100644
index 0000000000..e8208f86a1
--- /dev/null
+++ b/thirdpart/termsandconditions/templatetags/terms_tags.py
@@ -0,0 +1,42 @@
+"""Django Tags"""
+from django import template
+from ..models import TermsAndConditions, DEFAULT_TERMS_SLUG
+from ..middleware import is_path_protected
+from django.conf import settings
+
+try:
+ from urllib.parse import urlparse
+except ImportError:
+ from urlparse import urlparse
+
+register = template.Library()
+DEFAULT_HTTP_PATH_FIELD = 'PATH_INFO'
+TERMS_HTTP_PATH_FIELD = getattr(settings, 'TERMS_HTTP_PATH_FIELD', DEFAULT_HTTP_PATH_FIELD)
+
+
+@register.inclusion_tag('termsandconditions/snippets/termsandconditions.html',
+ takes_context=True)
+def show_terms_if_not_agreed(context, slug=DEFAULT_TERMS_SLUG, field=TERMS_HTTP_PATH_FIELD):
+ """Displays a modal on a current page if a user has not yet agreed to the
+ given terms. If terms are not specified, the default slug is used.
+
+ How it works? A small snippet is included into your template if a user
+ who requested the view has not yet agreed the terms. The snippet takes
+ care of displaying a respective modal.
+ """
+ request = context['request']
+ terms = TermsAndConditions.get_active(slug)
+ agreed = TermsAndConditions.agreed_to_terms(request.user, terms)
+
+ # stop here, if terms has been agreed
+ if agreed:
+ return {}
+
+ # handle excluded url's
+ url = urlparse(request.META[field])
+ protected = is_path_protected(url.path)
+
+ if (not agreed) and terms and protected:
+ return {'terms': terms}
+
+ return {}
diff --git a/thirdpart/termsandconditions/tests.py b/thirdpart/termsandconditions/tests.py
new file mode 100644
index 0000000000..c8b8d1c738
--- /dev/null
+++ b/thirdpart/termsandconditions/tests.py
@@ -0,0 +1,392 @@
+"""Unit Tests for the termsandconditions module"""
+
+# pylint: disable=R0904, C0103
+
+from django.core import mail
+from django.http import HttpResponseRedirect
+from django.conf import settings
+from django.test import TestCase, RequestFactory
+from django.contrib.auth.models import User
+from django.template import Context, Template
+from django.utils import timezone
+from .models import TermsAndConditions, UserTermsAndConditions, DEFAULT_TERMS_SLUG
+from .pipeline import user_accept_terms
+from .templatetags.terms_tags import show_terms_if_not_agreed
+import logging
+from importlib import import_module
+
+LOGGER = logging.getLogger(name='termsandconditions')
+
+
+class TermsAndConditionsTests(TestCase):
+ """Tests Terms and Conditions Module"""
+
+ def setUp(self):
+ """Setup for each test"""
+ LOGGER.debug('Test Setup')
+
+ self.user1 = User.objects.create_user('user1', 'user1@user1.com', 'user1password')
+ self.user2 = User.objects.create_user('user2', 'user2@user2.com', 'user2password')
+ self.terms1 = TermsAndConditions.objects.create(slug="site-terms", name="Site Terms",
+ text="Site Terms and Conditions 1", version_number=1.0,
+ date_active="2012-01-01")
+ self.terms2 = TermsAndConditions.objects.create(slug="site-terms", name="Site Terms",
+ text="Site Terms and Conditions 2", version_number=2.0,
+ date_active="2012-01-05")
+ self.terms3 = TermsAndConditions.objects.create(slug="contrib-terms", name="Contributor Terms",
+ text="Contributor Terms and Conditions 1.5", version_number=1.5,
+ date_active="2012-01-01")
+ self.terms4 = TermsAndConditions.objects.create(slug="contrib-terms", name="Contributor Terms",
+ text="Contributor Terms and Conditions 2", version_number=2.0,
+ date_active="2100-01-01")
+
+ def tearDown(self):
+ """Teardown for each test"""
+ LOGGER.debug('Test TearDown')
+ User.objects.all().delete()
+ TermsAndConditions.objects.all().delete()
+ UserTermsAndConditions.objects.all().delete()
+
+ def test_agreed_to(self):
+ """Test the agreed_to_terms static method"""
+ LOGGER.debug("Test that user1 has not agreed to terms1.")
+ self.assertFalse(TermsAndConditions.agreed_to_terms(self.user1, self.terms1))
+
+ def test_social_redirect(self):
+ """Test the agreed_to_terms redirect from social pipeline"""
+ LOGGER.debug("Test the social pipeline")
+ response = user_accept_terms('backend', self.user1, '123')
+ self.assertIsInstance(response, HttpResponseRedirect)
+
+ UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms2)
+ response = user_accept_terms('backend', self.user1, '123')
+ self.assertIsInstance(response, dict)
+
+ def test_get_active_list(self):
+ """Test get list of active T&Cs"""
+ active_list = TermsAndConditions.get_active_list()
+ self.assertEqual(2, len(active_list))
+
+ def test_terms_and_conditions_models(self):
+ """Various tests of the TermsAndConditions Module"""
+
+ # Testing Direct Assignment of Acceptance
+ UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms1)
+ UserTermsAndConditions.objects.create(user=self.user2, terms=self.terms3)
+
+ self.assertEquals(1.0, self.user1.userterms.get().terms.version_number)
+ self.assertEquals(1.5, self.user2.userterms.get().terms.version_number)
+
+ self.assertEquals('user1', self.terms1.users.all()[0].username)
+
+ # Testing the get_active static method of TermsAndConditions
+ self.assertEquals(2.0, TermsAndConditions.get_active(slug='site-terms').version_number)
+ self.assertEquals(1.5, TermsAndConditions.get_active(slug='contrib-terms').version_number)
+
+ # Testing the agreed_to_latest static method of TermsAndConditions
+ self.assertEquals(False, TermsAndConditions.agreed_to_latest(user=self.user1, slug='site-terms'))
+ self.assertEquals(True, TermsAndConditions.agreed_to_latest(user=self.user2, slug='contrib-terms'))
+
+ # Testing the unicode method of TermsAndConditions
+ self.assertEquals('site-terms-2.00', str(TermsAndConditions.get_active(slug='site-terms')))
+ self.assertEquals('contrib-terms-1.50', str(TermsAndConditions.get_active(slug='contrib-terms')))
+
+ def test_middleware_redirect(self):
+ """Validate that a user is redirected to the terms accept page if they are logged in, and decorator is on method"""
+
+ UserTermsAndConditions.objects.all().delete()
+
+ LOGGER.debug('Test user1 login for middleware')
+ login_response = self.client.login(username='user1', password='user1password')
+ self.assertTrue(login_response)
+
+ LOGGER.debug('Test /secure/ after login')
+ logged_in_response = self.client.get('/secure/', follow=True)
+ self.assertRedirects(logged_in_response, 'http://testserver/terms/accept/contrib-terms?returnTo=/secure/')
+
+ def test_terms_required_redirect(self):
+ """Validate that a user is redirected to the terms accept page if logged in, and decorator is on method"""
+
+ LOGGER.debug('Test /termsrequired/ pre login')
+ not_logged_in_response = self.client.get('/termsrequired/', follow=True)
+ self.assertRedirects(not_logged_in_response, 'http://testserver/accounts/login/?next=/termsrequired/')
+
+ LOGGER.debug('Test user1 login')
+ login_response = self.client.login(username='user1', password='user1password')
+ self.assertTrue(login_response)
+
+ LOGGER.debug('Test /termsrequired/ after login')
+ logged_in_response = self.client.get('/termsrequired/', follow=True)
+ self.assertRedirects(logged_in_response, 'http://testserver/terms/accept/?returnTo=/termsrequired/')
+
+ LOGGER.debug('Test no redirect for /termsrequired/ after accept')
+ accepted_response = self.client.post('/terms/accept/', {'terms': 2, 'returnTo': '/termsrequired/'}, follow=True)
+ self.assertContains(accepted_response, "Terms and Conditions Acceptance Required")
+ LOGGER.debug('Test response after termsrequired accept')
+ terms_required_response = self.client.get('/termsrequired/', follow=True)
+ self.assertContains(terms_required_response, "Terms and Conditions Acceptance Required")
+
+ def test_accept(self):
+ """Validate that accepting terms works"""
+
+ LOGGER.debug('Test user1 login for accept')
+ login_response = self.client.login(username='user1', password='user1password')
+ self.assertTrue(login_response)
+
+ LOGGER.debug('Test /terms/accept/ get')
+ accept_response = self.client.get('/terms/accept/', follow=True)
+ self.assertContains(accept_response, "Accept")
+
+ LOGGER.debug('Test /terms/accept/ post')
+ chained_terms_response = self.client.post('/terms/accept/', {'terms': 2, 'returnTo': '/secure/'}, follow=True)
+ self.assertContains(chained_terms_response, "Contributor")
+
+ self.assertEquals(True, TermsAndConditions.agreed_to_latest(user=self.user1, slug='site-terms'))
+
+ LOGGER.debug('Test /terms/accept/contrib-terms/1.5/ post')
+ accept_version_response = self.client.get('/terms/accept/contrib-terms/1.5/', follow=True)
+ self.assertContains(accept_version_response, "Contributor Terms and Conditions 1.5")
+
+ LOGGER.debug('Test /terms/accept/contrib-terms/3/ post')
+ accept_version_post_response = self.client.post('/terms/accept/', {'terms': 3, 'returnTo': '/secure/'}, follow=True)
+ self.assertContains(accept_version_post_response, "Secure")
+ self.assertTrue(TermsAndConditions.agreed_to_terms(user=self.user1, terms=self.terms3))
+
+ def test_accept_store_ip_address(self):
+ """Test with IP address storage setting true (default)"""
+ self.client.login(username='user1', password='user1password')
+ self.client.post('/terms/accept/', {'terms': 2, 'returnTo': '/secure/'}, follow=True)
+ user_terms = UserTermsAndConditions.objects.all()[0]
+ self.assertEqual(user_terms.user, self.user1)
+ self.assertEqual(user_terms.terms, self.terms2)
+ self.assertTrue(user_terms.ip_address)
+
+ def test_accept_no_ip_address(self):
+ """Test with IP address storage setting false"""
+ self.client.login(username='user1', password='user1password')
+ with self.settings(TERMS_STORE_IP_ADDRESS=False):
+ self.client.post('/terms/accept/', {'terms': 2, 'returnTo': '/secure/'}, follow=True)
+ user_terms = UserTermsAndConditions.objects.all()[0]
+ self.assertFalse(user_terms.ip_address)
+
+ def test_auto_create(self):
+ """Validate that a terms are auto created if none exist"""
+ LOGGER.debug('Test auto create terms')
+
+ TermsAndConditions.objects.all().delete()
+
+ num_terms = TermsAndConditions.objects.count()
+ self.assertEquals(0, num_terms)
+
+ LOGGER.debug('Test user1 login for autocreate')
+ login_response = self.client.login(username='user1', password='user1password')
+ self.assertTrue(login_response)
+
+ LOGGER.debug('Test /termsrequired/ after login with no TermsAndConditions')
+ logged_in_response = self.client.get('/termsrequired/', follow=True)
+ self.assertRedirects(logged_in_response, 'http://testserver/terms/accept/?returnTo=/termsrequired/')
+
+ LOGGER.debug('Test TermsAndConditions Object Was Created')
+ num_terms = TermsAndConditions.objects.count()
+ self.assertEquals(1, num_terms)
+
+ terms = TermsAndConditions.objects.get()
+ self.assertEquals('site-terms-1.00', str(terms))
+
+ LOGGER.debug('Test Not Creating Non-Default TermsAndConditions')
+ non_default_response = self.client.get('/terms/accept/contrib-terms/', follow=True)
+ self.assertEquals(404, non_default_response.status_code)
+
+ def test_terms_upgrade(self):
+ """Validate a user is prompted to accept terms again when new version comes out"""
+
+ UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms2)
+
+ LOGGER.debug('Test user1 login pre upgrade')
+ login_response = self.client.login(username='user1', password='user1password')
+ self.assertTrue(login_response)
+
+ LOGGER.debug('Test user1 not redirected after login')
+ logged_in_response = self.client.get('/secure/', follow=True)
+ self.assertContains(logged_in_response, "Contributor")
+
+ # First, Accept Contributor Terms
+ LOGGER.debug('Test /terms/accept/contrib-terms/3/ post')
+ self.client.post('/terms/accept/', {'terms': 3, 'returnTo': '/secure/'}, follow=True)
+
+ LOGGER.debug('Test upgrade terms')
+ self.terms5 = TermsAndConditions.objects.create(slug="site-terms", name="Site Terms",
+ text="Terms and Conditions2", version_number=2.5,
+ date_active="2012-02-05")
+
+ LOGGER.debug('Test user1 is redirected when changing pages')
+ post_upgrade_response = self.client.get('/secure/', follow=True)
+ self.assertRedirects(post_upgrade_response, 'http://testserver/terms/accept/site-terms?returnTo=/secure/')
+
+ def test_no_middleware(self):
+ """Test a secure page with the middleware excepting it"""
+
+ UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms2)
+
+ LOGGER.debug('Test user1 login no middleware')
+ login_response = self.client.login(username='user1', password='user1password')
+ self.assertTrue(login_response)
+
+ LOGGER.debug('Test user1 not redirected after login')
+ logged_in_response = self.client.get('/securetoo/', follow=True)
+ self.assertContains(logged_in_response, "SECOND")
+
+ LOGGER.debug("Test startswith '/admin' pages not redirecting")
+ admin_response = self.client.get('/admin', follow=True)
+ self.assertContains(admin_response, "administration")
+
+ def test_terms_view(self):
+ """Test Accessing the View Terms and Conditions Functions"""
+
+ LOGGER.debug('Test /terms/')
+ root_response = self.client.get('/terms/', follow=True)
+ self.assertContains(root_response, 'Terms and Conditions')
+
+ LOGGER.debug('Test /terms/view/site-terms')
+ slug_response = self.client.get('/terms/view/site-terms', follow=True)
+ self.assertContains(slug_response, 'Terms and Conditions')
+
+ LOGGER.debug('Test /terms/view/site-terms/1.5')
+ version_response = self.client.get(self.terms3.get_absolute_url(), follow=True)
+ self.assertContains(version_response, 'Terms and Conditions')
+
+ def test_user_pipeline(self):
+ """Test the case of a user being partially created via the django-socialauth pipeline"""
+
+ LOGGER.debug('Test /terms/accept/ post for no user')
+ no_user_response = self.client.post('/terms/accept/', {'terms': 2}, follow=True)
+ self.assertContains(no_user_response, "Home")
+
+ user = {'pk': 1}
+ kwa = {'user': user}
+ partial_pipeline = {'kwargs': kwa}
+
+ engine = import_module(settings.SESSION_ENGINE)
+ store = engine.SessionStore()
+ store.save()
+ self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key
+
+ session = self.client.session
+ session["partial_pipeline"] = partial_pipeline
+ session.save()
+
+ self.assertTrue('partial_pipeline' in self.client.session)
+
+ LOGGER.debug('Test /terms/accept/ post for pipeline user')
+ pipeline_response = self.client.post('/terms/accept/', {'terms': 2, 'returnTo': '/anon'}, follow=True)
+ self.assertContains(pipeline_response, "Anon")
+
+ def test_email_terms(self):
+ """Test emailing terms and conditions"""
+ LOGGER.debug('Test /terms/email/')
+ email_form_response = self.client.get('/terms/email/', follow=True)
+ self.assertContains(email_form_response, 'Email')
+
+ LOGGER.debug('Test /terms/email/ post, expecting email fail')
+ email_send_response = self.client.post('/terms/email/',
+ {'email_address': 'foo@foo.com', 'email_subject': 'Terms Email',
+ 'terms': 2, 'returnTo': '/'}, follow=True)
+ self.assertEqual(len(mail.outbox), 1) # Check that there is one email in the test outbox
+ self.assertContains(email_send_response, 'Sent')
+
+ LOGGER.debug('Test /terms/email/ post, expecting email fail')
+ email_fail_response = self.client.post('/terms/email/', {'email_address': 'INVALID EMAIL ADDRESS',
+ 'email_subject': 'Terms Email', 'terms': 2,
+ 'returnTo': '/'}, follow=True)
+ self.assertContains(email_fail_response, 'Invalid')
+
+
+class TermsAndConditionsTemplateTagsTestCase(TestCase):
+ def setUp(self):
+ """Setup for each test"""
+ self.user1 = User.objects.create_user(
+ 'user1', 'user1@user1.com', 'user1password')
+ self.template_string_1 = (
+ '{% load terms_tags %}'
+ '{% show_terms_if_not_agreed %}'
+ )
+ self.template_string_2 = (
+ '{% load terms_tags %}'
+ '{% show_terms_if_not_agreed slug="specific-terms" %}'
+ )
+
+ def _make_context(self, url):
+ """Build Up Context - Used in many tests"""
+ context = dict()
+ context['request'] = RequestFactory()
+ context['request'].user = self.user1
+ context['request'].META = {'PATH_INFO': url}
+ return context
+
+ def render_template(self, string, context=None):
+ """a helper method to render simplistic test templates"""
+ request = RequestFactory().get('/test')
+ request.user = self.user1
+ request.context = context or {}
+ return Template(string).render(Context({'request': request}))
+
+ def test_show_terms_if_not_agreed(self):
+ """test if show_terms_if_not_agreed template tag renders html code"""
+ rendered = self.render_template(self.template_string_1)
+ terms = TermsAndConditions.get_active()
+ self.assertIn(terms.slug, rendered)
+
+ def test_not_show_terms_if_agreed(self):
+ """test if show_terms_if_not_agreed template tag does not load if user
+ agreed terms"""
+ terms = TermsAndConditions.get_active()
+ UserTermsAndConditions.objects.create(terms=terms, user=self.user1)
+ rendered = self.render_template(self.template_string_1)
+ self.assertNotIn(terms.slug, rendered)
+
+ def test_show_terms_if_not_agreed_by_slug(self):
+ """Test if show_terms not agreed to by looking up slug"""
+ terms = TermsAndConditions.objects.create(
+ slug='specific-terms',
+ date_active=timezone.now()
+ )
+ rendered = self.render_template(self.template_string_2)
+ self.assertIn(terms.slug, rendered)
+
+ def test_show_terms_if_not_agreed_on_protected_url_not_agreed(self):
+ """Check terms on protected url if not agreed"""
+ context = self._make_context('/test')
+ result = show_terms_if_not_agreed(context)
+ terms = TermsAndConditions.get_active(slug=DEFAULT_TERMS_SLUG)
+ self.assertDictEqual(result, {'terms': terms})
+
+ def test_show_terms_if_not_agreed_on_unprotected_url_not_agreed(self):
+ """Check terms on unprotected url if not agreed"""
+ context = self._make_context('/')
+ result = show_terms_if_not_agreed(context)
+ self.assertDictEqual(result, {})
+
+
+class TermsAndConditionsModelsTestCase(TestCase):
+ """Tests Models for T&C"""
+
+ def setUp(self):
+ """Set Up T&C Model Tests"""
+ self.terms_1 = TermsAndConditions.objects.create(
+ name='terms_1',
+ date_active=timezone.now()
+ )
+ self.terms_2 = TermsAndConditions.objects.create(
+ name='terms_2',
+ date_active=None
+ )
+
+ def test_tnc_default_slug(self):
+ """test if default slug is used"""
+ self.assertEqual(self.terms_1.slug, DEFAULT_TERMS_SLUG)
+
+ def test_tnc_get_active(self):
+ """test if right terms are active"""
+ active = TermsAndConditions.get_active()
+ self.assertEqual(active.name, self.terms_1.name)
+ self.assertNotEqual(active.name, self.terms_2.name)
diff --git a/thirdpart/termsandconditions/urls.py b/thirdpart/termsandconditions/urls.py
new file mode 100644
index 0000000000..ca2ccc7654
--- /dev/null
+++ b/thirdpart/termsandconditions/urls.py
@@ -0,0 +1,43 @@
+"""
+ Master URL Pattern List for the application. Most of the patterns here should be top-level
+ pass-offs to sub-modules, who will have their own urls.py defining actions within.
+"""
+
+# pylint: disable=W0401, W0614, E1120
+
+from django.conf.urls import url
+from django.contrib import admin
+from .views import TermsView, AcceptTermsView, EmailTermsView
+from .models import DEFAULT_TERMS_SLUG
+
+admin.autodiscover()
+
+urlpatterns = (
+ # View Default Terms
+ url(r'^$', TermsView.as_view(), {"slug": DEFAULT_TERMS_SLUG}, name="tc_view_page"),
+
+ # View Specific Active Terms
+ url(r'^view/(?P[a-zA-Z0-9_.-]+)/$', TermsView.as_view(), name="tc_view_specific_page"),
+
+ # View Specific Version of Terms
+ url(r'^view/(?P[a-zA-Z0-9_.-]+)/(?P[0-9.]+)/$', TermsView.as_view(), name="tc_view_specific_version_page"),
+
+ # Print Specific Version of Terms
+ url(r'^print/(?P[a-zA-Z0-9_.-]+)/(?P[0-9.]+)/$', TermsView.as_view(template_name="termsandconditions/tc_print_terms.html"), name="tc_print_page"),
+
+ # Accept Terms
+ url(r'^accept/$', AcceptTermsView.as_view(), name="tc_accept_page"),
+
+ # Accept Specific Terms
+ url(r'^accept/(?P[a-zA-Z0-9_.-]+)$', AcceptTermsView.as_view(), name="tc_accept_specific_page"),
+
+ # Accept Specific Terms Version
+ url(r'^accept/(?P[a-zA-Z0-9_.-]+)/(?P[0-9\.]+)/$', AcceptTermsView.as_view(), name="tc_accept_specific_version_page"),
+
+ # Email Terms
+ url(r'^email/$', EmailTermsView.as_view(), name="tc_email_page"),
+
+ # Email Specific Terms Version
+ url(r'^email/(?P[a-zA-Z0-9_.-]+)/(?P[0-9\.]+)/$', EmailTermsView.as_view(), name="tc_specific_version_page"),
+
+)
diff --git a/thirdpart/termsandconditions/views.py b/thirdpart/termsandconditions/views.py
new file mode 100644
index 0000000000..ecb5d614fb
--- /dev/null
+++ b/thirdpart/termsandconditions/views.py
@@ -0,0 +1,133 @@
+"""Django Views for the termsandconditions module"""
+
+# pylint: disable=E1120,R0901,R0904
+
+from django.contrib.auth.models import User
+from .forms import UserTermsAndConditionsModelForm, EmailTermsForm
+from .models import TermsAndConditions, UserTermsAndConditions, DEFAULT_TERMS_SLUG
+from django.conf import settings
+from django.contrib import messages
+from django.http import HttpResponseRedirect
+from django.views.generic import DetailView, CreateView, FormView
+from django.template.loader import get_template
+from django.core.mail import send_mail
+import logging
+from smtplib import SMTPException
+
+LOGGER = logging.getLogger(name='termsandconditions')
+
+
+def get_terms(kwargs):
+ """Checks URL parameters for slug and/or version to pull the right TermsAndConditions object"""
+ slug = kwargs.get("slug", DEFAULT_TERMS_SLUG)
+
+ if kwargs.get("version"):
+ terms = TermsAndConditions.objects.get(slug=slug, version_number=kwargs.get("version"))
+ else:
+ terms = TermsAndConditions.get_active(slug)
+ return terms
+
+
+class TermsView(DetailView):
+ """
+ View Terms and Conditions View
+
+ url: /terms/view
+ """
+ template_name = "termsandconditions/tc_view_terms.html"
+ context_object_name = 'terms'
+
+ def get_object(self, queryset=None):
+ """Override of DetailView method, queries for which T&C to return"""
+ LOGGER.debug('termsandconditions.views.TermsView.get_object')
+
+ return get_terms(self.kwargs)
+
+
+class AcceptTermsView(CreateView):
+ """
+ Terms and Conditions Acceptance view
+
+ url: /terms/accept
+ """
+
+ model = UserTermsAndConditions
+ form_class = UserTermsAndConditionsModelForm
+ template_name = "termsandconditions/tc_accept_terms.html"
+
+ def get_initial(self):
+ """Override of CreateView method, queries for which T&C to accept and catches returnTo from URL"""
+ LOGGER.debug('termsandconditions.views.AcceptTermsView.get_initial')
+
+ terms = get_terms(self.kwargs)
+
+ returnTo = self.request.GET.get('returnTo', '/')
+
+ return {'terms': terms, 'returnTo': returnTo}
+
+ def form_valid(self, form):
+ """Override of CreateView method, assigns default values based on user situation"""
+ if self.request.user.is_authenticated():
+ form.instance.user = self.request.user
+ else: #Get user out of saved pipeline from django-socialauth
+ if self.request.session.has_key('partial_pipeline'):
+ user_pk = self.request.session['partial_pipeline']['kwargs']['user']['pk']
+ form.instance.user = User.objects.get(id=user_pk)
+ else:
+ return HttpResponseRedirect('/')
+ store_ip_address = getattr(settings, 'TERMS_STORE_IP_ADDRESS', True)
+ if store_ip_address:
+ form.instance.ip_address = self.request.META['REMOTE_ADDR']
+ self.success_url = form.cleaned_data.get('returnTo', '/') or '/'
+ return super(AcceptTermsView, self).form_valid(form)
+
+
+class EmailTermsView(FormView):
+ """
+ Email Terms and Conditions View
+
+ url: /terms/email
+ """
+ template_name = "termsandconditions/tc_email_terms_form.html"
+
+ form_class = EmailTermsForm
+
+ def get_initial(self):
+ """Override of CreateView method, queries for which T&C send, catches returnTo from URL"""
+ LOGGER.debug('termsandconditions.views.EmailTermsView.get_initial')
+
+ terms = get_terms(self.kwargs)
+
+ returnTo = self.request.GET.get('returnTo', '/')
+
+ return {'terms': terms, 'returnTo': returnTo}
+
+ def form_valid(self, form):
+ """Override of CreateView method, sends the email."""
+ LOGGER.debug('termsandconditions.views.EmailTermsView.form_valid')
+
+ template = get_template("termsandconditions/tc_email_terms.html")
+ template_rendered = template.render({"terms": form.cleaned_data.get('terms')})
+
+ LOGGER.debug("Email Terms Body:")
+ LOGGER.debug(template_rendered)
+
+ try:
+ send_mail(form.cleaned_data.get('email_subject', 'Terms'),
+ template_rendered,
+ settings.DEFAULT_FROM_EMAIL,
+ [form.cleaned_data.get('email_address')],
+ fail_silently=False)
+ messages.add_message(self.request, messages.INFO, "Terms and Conditions Sent.")
+ except SMTPException: # pragma: no cover
+ messages.add_message(self.request, messages.ERROR, "An Error Occurred Sending Your Message.")
+
+ self.success_url = form.cleaned_data.get('returnTo', '/') or '/'
+
+ return super(EmailTermsView, self).form_valid(form)
+
+ def form_invalid(self, form):
+ """Override of CreateView method, logs invalid email form submissions."""
+ LOGGER.debug("Invalid Email Form Submitted")
+ messages.add_message(self.request, messages.ERROR, "Invalid Email Address.")
+ return super(EmailTermsView, self).form_invalid(form)