mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 07:27:04 +00:00
Add ToC
This commit is contained in:
2
thirdpart/termsandconditions/__init__.py
Normal file
2
thirdpart/termsandconditions/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"""Django Terms and Conditions Module"""
|
||||||
|
from __future__ import unicode_literals
|
25
thirdpart/termsandconditions/admin.py
Normal file
25
thirdpart/termsandconditions/admin.py
Normal file
@@ -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)
|
8
thirdpart/termsandconditions/apps.py
Normal file
8
thirdpart/termsandconditions/apps.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""Django Apps Config"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TermsAndConditionsConfig(AppConfig):
|
||||||
|
name = 'termsandconditions'
|
||||||
|
verbose_name = "Terms and Conditions"
|
31
thirdpart/termsandconditions/decorators.py
Normal file
31
thirdpart/termsandconditions/decorators.py
Normal file
@@ -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
|
26
thirdpart/termsandconditions/forms.py
Normal file
26
thirdpart/termsandconditions/forms.py
Normal file
@@ -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())
|
55
thirdpart/termsandconditions/middleware.py
Normal file
55
thirdpart/termsandconditions/middleware.py
Normal file
@@ -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
|
57
thirdpart/termsandconditions/migrations/0001_initial.py
Normal file
57
thirdpart/termsandconditions/migrations/0001_initial.py
Normal file
@@ -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')]),
|
||||||
|
),
|
||||||
|
]
|
@@ -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,
|
||||||
|
),
|
||||||
|
]
|
0
thirdpart/termsandconditions/migrations/__init__.py
Normal file
0
thirdpart/termsandconditions/migrations/__init__.py
Normal file
122
thirdpart/termsandconditions/models.py
Normal file
122
thirdpart/termsandconditions/models.py
Normal file
@@ -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
|
40
thirdpart/termsandconditions/pipeline.py
Normal file
40
thirdpart/termsandconditions/pipeline.py
Normal file
@@ -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))
|
@@ -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%;
|
||||||
|
}
|
@@ -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";
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,29 @@
|
|||||||
|
{% load staticfiles %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if terms %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'termsandconditions/css/modal.css' %}">
|
||||||
|
<script src="{% static 'termsandconditions/js/modal.js' %}" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
termsandconditions_overlay();
|
||||||
|
}, false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="termsandconditions" class="termsandconditions-modal">
|
||||||
|
<div>
|
||||||
|
<a href='javascript:void(0)' onclick='termsandconditions_overlay();' title="{% trans 'Close' %}" class="termsandconditions-close">X</a>
|
||||||
|
<h2>
|
||||||
|
{% if terms.name %}
|
||||||
|
{{ terms.name|safe }}
|
||||||
|
{% else %}
|
||||||
|
{% trans 'Terms and Conditions' %}
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
{% if terms.info %}{{ terms.info|safe }}{% endif %}
|
||||||
|
{% trans 'To accept new Terms, visit' %}
|
||||||
|
<a href="{% url 'tc_accept_specific_page' terms.slug %}">{% url 'tc_accept_specific_page' terms.slug %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block headtitle %}Accept Terms and Conditions{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section title="termsandconditions" data-role="content">
|
||||||
|
<h1>Please Accept {{ form.initial.terms.name|safe }}</h1>
|
||||||
|
{{ form.errors }}
|
||||||
|
<div id="tc-terms-html">
|
||||||
|
{{ form.initial.terms.text|safe }}
|
||||||
|
</div>
|
||||||
|
<form action="{% url 'tc_accept_page' %}" method="post" id="tc-terms-form" data-ajax="false">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.terms }}
|
||||||
|
{{ form.returnTo }}
|
||||||
|
<p><input type="submit" value="Accept" data-role="button"></p>
|
||||||
|
</form>
|
||||||
|
<p><a href="{% url "tc_print_page" form.initial.terms.slug form.initial.terms.version_number %}"
|
||||||
|
target="_blank">Print Terms & Conditions</a></p>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
{% block footer %}{% endblock %}
|
@@ -0,0 +1,5 @@
|
|||||||
|
{{ terms.name|safe }}
|
||||||
|
|
||||||
|
Version {{ terms.version_number|safe }}
|
||||||
|
|
||||||
|
{{ terms.text|safe }}
|
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block headtitle %}Email Terms and Conditions{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section title="termsandconditions" data-role="content">
|
||||||
|
<h1>Email {{ form.initial.terms.name|safe }}</h1>
|
||||||
|
<form action="{% url 'tc_email_page' %}" method="post" id="tc-email-form" data-ajax="false">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="id_email_address">Email To:</label>{{ form.email_address }}
|
||||||
|
{{ form.terms }}
|
||||||
|
<input type="hidden" id="id_email_subject" name="email_subject" value="You Requested: {{ form.initial.terms }}">
|
||||||
|
<p><input type="submit" value="Send" data-role="button"></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block headtitle %}Print Terms and Conditions{% endblock %}
|
||||||
|
|
||||||
|
{% block body_class %}{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script>
|
||||||
|
window.print();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ terms.name|safe }}</h1>
|
||||||
|
<h3>Version {{ terms.version_number|safe }}</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ terms.text|safe }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section title="Terms and Conditions" data-role="content">
|
||||||
|
<h1>{{ terms.name|safe }}</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ terms.text|safe }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
1
thirdpart/termsandconditions/templatetags/__init__.py
Normal file
1
thirdpart/termsandconditions/templatetags/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
42
thirdpart/termsandconditions/templatetags/terms_tags.py
Normal file
42
thirdpart/termsandconditions/templatetags/terms_tags.py
Normal file
@@ -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 {}
|
392
thirdpart/termsandconditions/tests.py
Normal file
392
thirdpart/termsandconditions/tests.py
Normal file
@@ -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)
|
43
thirdpart/termsandconditions/urls.py
Normal file
43
thirdpart/termsandconditions/urls.py
Normal file
@@ -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<slug>[a-zA-Z0-9_.-]+)/$', TermsView.as_view(), name="tc_view_specific_page"),
|
||||||
|
|
||||||
|
# View Specific Version of Terms
|
||||||
|
url(r'^view/(?P<slug>[a-zA-Z0-9_.-]+)/(?P<version>[0-9.]+)/$', TermsView.as_view(), name="tc_view_specific_version_page"),
|
||||||
|
|
||||||
|
# Print Specific Version of Terms
|
||||||
|
url(r'^print/(?P<slug>[a-zA-Z0-9_.-]+)/(?P<version>[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<slug>[a-zA-Z0-9_.-]+)$', AcceptTermsView.as_view(), name="tc_accept_specific_page"),
|
||||||
|
|
||||||
|
# Accept Specific Terms Version
|
||||||
|
url(r'^accept/(?P<slug>[a-zA-Z0-9_.-]+)/(?P<version>[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<slug>[a-zA-Z0-9_.-]+)/(?P<version>[0-9\.]+)/$', EmailTermsView.as_view(), name="tc_specific_version_page"),
|
||||||
|
|
||||||
|
)
|
133
thirdpart/termsandconditions/views.py
Normal file
133
thirdpart/termsandconditions/views.py
Normal file
@@ -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)
|
Reference in New Issue
Block a user