diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..9d7eced --- /dev/null +++ b/.coveragerc @@ -0,0 +1,33 @@ +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + def __unicode__ + def __repr__ + if settings.DEBUG + raise NotImplementedError + from django\. + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +[run] +omit = + *tests* + *migrations* + *management* + *urls* + *site-packages* + *src* + *manage* + *settings* diff --git a/.gitignore b/.gitignore index 0608b14..004a8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ *.pyc -templates/ +.coverage diff --git a/.travis.yml b/.travis.yml index 1686dae..da8e9ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,19 +3,20 @@ python: - "2.6" - "2.7" - "3.4" - install: - - pip install . + - pip install coverage - pip install $DJANGO script: - - django-admin.py test tos --settings=tos.tests.test_settings + - coverage run runtests.py + - coverage report -m env: - DJANGO="Django==1.4.12" - DJANGO="Django==1.5" - DJANGO="Django==1.5.7" - DJANGO="Django==1.6.4" - DJANGO="Django==1.7.11" - + - DJANGO="Django==1.8.11" + - DJANGO="Django==1.9.4" matrix: exclude: # Python 2.6 support has been dropped in Django 1.7 @@ -23,3 +24,7 @@ matrix: env: DJANGO="Django==1.7.11" - python: "3.4" env: DJANGO="Django==1.4.12" + - python: "2.6" + env: DJANGO="Django==1.8.11" + - python: "2.6" + env: DJANGO="Django==1.9.4" diff --git a/requirements.txt b/requirements.txt index 4a2eb22..03490d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ Django>=1.4 +coverage diff --git a/runtests.py b/runtests.py new file mode 100755 index 0000000..529fedf --- /dev/null +++ b/runtests.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +import sys + +import django + +from django.conf import settings +from django.core.management import execute_from_command_line + + +if not settings.configured: + settings.configure( + DATABASES={ + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + } + }, + INSTALLED_APPS=[ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.sites', + 'tos', + 'tos.tests' + ], + MIDDLEWARE_CLASSES=[ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], + ROOT_URLCONF='tos.tests.test_urls', + LOGIN_URL='/login/', + SITE_ID='1' + ) + + +import logging +logging.basicConfig( + level = logging.DEBUG, + format = '%(asctime)s %(levelname)s %(message)s', +) +logging.disable(logging.CRITICAL) + + +def runtests(): + argv = sys.argv[:1] + ['test', 'tos'] + execute_from_command_line(argv) + + +if __name__ == '__main__': + runtests() diff --git a/tos/compat.py b/tos/compat.py new file mode 100644 index 0000000..72f42f1 --- /dev/null +++ b/tos/compat.py @@ -0,0 +1,39 @@ +import django +from django.conf import settings + + +def get_fk_user_model(): + if django.VERSION >= (1, 5): + return settings.AUTH_USER_MODEL + from django.contrib.auth.models import User + return User + + +def get_runtime_user_model(): + if django.VERSION >= (1, 5): + from django.contrib.auth import get_user_model + return get_user_model() + from django.contrib.auth.models import User + return User + + +def get_request_site(): + if django.VERSION >= (1, 9): + from django.contrib.sites.requests import RequestSite + else: + from django.contrib.sites.models import RequestSite + return RequestSite + + +def get_library(): + if django.VERSION >= (1, 9): + from django.template.library import Library + else: + from django.template.base import Library + return Library + + +if django.VERSION < (1, 5): + from django.templatetags.future import url +else: + from django.template.defaulttags import url diff --git a/tos/models.py b/tos/models.py index 8f16853..1174b7a 100644 --- a/tos/models.py +++ b/tos/models.py @@ -2,14 +2,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ -# Django 1.4 compatability -try: - from django.contrib.auth import get_user_model - USER_MODEL = get_user_model() -except ImportError: - from django.contrib.auth.models import User - USER_MODEL = User - +from tos.compat import get_fk_user_model class NoActiveTermsOfService(ValidationError): pass @@ -65,7 +58,8 @@ class TermsOfService(BaseModel): else: if not TermsOfService.objects\ .exclude(id=self.id)\ - .filter(active=True): + .filter(active=True)\ + .exists(): raise NoActiveTermsOfService( u'One of the terms of service must be marked active' ) @@ -75,7 +69,7 @@ class TermsOfService(BaseModel): class UserAgreement(BaseModel): terms_of_service = models.ForeignKey(TermsOfService, related_name='terms') - user = models.ForeignKey(USER_MODEL, related_name='user_agreement') + user = models.ForeignKey(get_fk_user_model(), related_name='user_agreement') def __unicode__(self): return u'%s agreed to TOS: %s' % (self.user.username, diff --git a/tos/templates/tos/tos_check.html b/tos/templates/tos/tos_check.html index ae6ace6..cf0163a 100644 --- a/tos/templates/tos/tos_check.html +++ b/tos/templates/tos/tos_check.html @@ -1,4 +1,4 @@ -{% load url from future %} +{% load url from compat %} {% if note %}

{{ note }} {% else %} diff --git a/tos/templatetags/__init__.py b/tos/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tos/templatetags/compat.py b/tos/templatetags/compat.py new file mode 100644 index 0000000..9e0499d --- /dev/null +++ b/tos/templatetags/compat.py @@ -0,0 +1,10 @@ +from tos.compat import url as tos_url, get_library + + +Library = get_library() +register = Library() + + +@register.tag +def url(parser, token): + return tos_url(parser, token) diff --git a/tos/tests/__init__.py b/tos/tests/__init__.py index 027f01a..80ca9a7 100644 --- a/tos/tests/__init__.py +++ b/tos/tests/__init__.py @@ -1,2 +1,5 @@ -from tos.tests.test_models import * -from tos.tests.test_views import * \ No newline at end of file +import django + +if django.VERSION < (1, 6): + from tos.tests.test_models import * + from tos.tests.test_views import * diff --git a/tos/tests/templates/registration/login.html b/tos/tests/templates/registration/login.html new file mode 100644 index 0000000..178ffda --- /dev/null +++ b/tos/tests/templates/registration/login.html @@ -0,0 +1 @@ +Dummy login template. diff --git a/tos/tests/test_models.py b/tos/tests/test_models.py index 5987939..f978376 100644 --- a/tos/tests/test_models.py +++ b/tos/tests/test_models.py @@ -1,24 +1,25 @@ from django.core.exceptions import ValidationError from django.test import TestCase +from tos.compat import get_runtime_user_model from tos.models import ( + NoActiveTermsOfService, TermsOfService, UserAgreement, has_user_agreed_latest_tos, - USER_MODEL ) class TestModels(TestCase): def setUp(self): - self.user1 = USER_MODEL.objects.create_user('user1', + self.user1 = get_runtime_user_model().objects.create_user('user1', 'user1@example.com', 'user1pass') - self.user2 = USER_MODEL.objects.create_user('user2', + self.user2 = get_runtime_user_model().objects.create_user('user2', 'user2@example.com', 'user2pass') - self.user3 = USER_MODEL.objects.create_user('user3', + self.user3 = get_runtime_user_model().objects.create_user('user3', 'user3@example.com', 'user3pass') @@ -48,10 +49,6 @@ class TestModels(TestCase): # latest is active though self.assertTrue(latest.active) - def test_terms_of_service_manager(self): - - self.assertEquals(TermsOfService.objects.get_current_tos(), self.tos1) - def test_validation_error_all_set_false(self): """ If you try and set all to false the model will throw a ValidationError @@ -92,3 +89,18 @@ class TestModels(TestCase): self.assertTrue(has_user_agreed_latest_tos(self.user1)) self.assertFalse(has_user_agreed_latest_tos(self.user2)) self.assertTrue(has_user_agreed_latest_tos(self.user3)) + + +class TestManager(TestCase): + def test_terms_of_service_manager(self): + + tos1 = TermsOfService.objects.create( + content="first edition of the terms of service", + active=True + ) + + self.assertEquals(TermsOfService.objects.get_current_tos(), tos1) + + def test_terms_of_service_manager_raises_error(self): + + self.assertRaises(NoActiveTermsOfService, TermsOfService.objects.get_current_tos) diff --git a/tos/tests/test_settings.py b/tos/tests/test_settings.py deleted file mode 100644 index 770a032..0000000 --- a/tos/tests/test_settings.py +++ /dev/null @@ -1,64 +0,0 @@ -DEBUG = True -TEMPLATE_DEBUG = DEBUG -SITE_ID = 1 -SECRET_KEY = 'foobarbaz' - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'mydatabase' - } -} - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', -) - -TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.static', - 'django.core.context_processors.tz', - 'django.core.context_processors.request', - 'django.contrib.messages.context_processors.messages', -) - -INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.sites', - 'tos', -) - -TEMPLATE_LOADERS = ( - 'django.template.loaders.app_directories.Loader', - 'django.template.loaders.eggs.Loader', -) - -ROOT_URLCONF = 'tos.tests.test_urls' - -LOGIN_URL = '/login/' - -import logging -logging.basicConfig( - level = logging.DEBUG, - format = '%(asctime)s %(levelname)s %(message)s', -) - - -# Django 1.7 compatibility: -import django -if hasattr(django, 'setup'): - django.setup() - -if django.VERSION > (1, 6): - TEST_RUNNER = 'django.test.runner.DiscoverRunner' diff --git a/tos/tests/test_views.py b/tos/tests/test_views.py index 5297965..bb307cb 100644 --- a/tos/tests/test_views.py +++ b/tos/tests/test_views.py @@ -2,20 +2,15 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.test import TestCase -# Django 1.4 compatability -try: - from django.contrib.auth import get_user_model -except ImportError: - from django.contrib.auth.models import User - get_user_model = lambda: User +from tos.compat import get_runtime_user_model +from tos.models import TermsOfService, UserAgreement, has_user_agreed_latest_tos -from tos.models import TermsOfService, UserAgreement, has_user_agreed_latest_tos, USER_MODEL as USER class TestViews(TestCase): def setUp(self): - self.user1 = USER.objects.create_user('user1', 'user1@example.com', 'user1pass') - self.user2 = USER.objects.create_user('user2', 'user2@example.com', 'user2pass') + self.user1 = get_runtime_user_model().objects.create_user('user1', 'user1@example.com', 'user1pass') + self.user2 = get_runtime_user_model().objects.create_user('user2', 'user2@example.com', 'user2pass') self.tos1 = TermsOfService.objects.create( content="first edition of the terms of service", @@ -53,6 +48,34 @@ class TestViews(TestCase): self.assertFalse(has_user_agreed_latest_tos(self.user2)) + def test_do_not_need_agreement(self): + """ user2 tries to login and has already agreed""" + + self.assertTrue(has_user_agreed_latest_tos(self.user1)) + + response = self.client.post(self.login_url, dict(username='user1', + password='user1pass')) + self.assertEqual(302, response.status_code) + + def test_redirect_security(self): + """ redirect to outside url not allowed, should redirect to login url""" + + response = self.client.post(self.login_url, dict(username='user1', + password='user1pass', next='http://example.com')) + self.assertEqual(302, response.status_code) + self.assertIn(settings.LOGIN_REDIRECT_URL, response._headers['location'][1]) + + def test_need_to_log_in(self): + """ GET to login url shows login tempalte.""" + + response = self.client.get(self.login_url) + self.assertContains(response, "Dummy login template.") + + def test_root_tos_view(self): + + response = self.client.get('/tos/') + self.assertIn(b'first edition of the terms of service', response.content) + def test_reject_agreement(self): self.assertFalse(has_user_agreed_latest_tos(self.user2)) diff --git a/tos/views.py b/tos/views.py index 0b0bf1f..3846196 100644 --- a/tos/views.py +++ b/tos/views.py @@ -5,7 +5,7 @@ from django.contrib import messages from django.contrib.auth import login as auth_login from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.forms import AuthenticationForm -from django.contrib.sites.models import Site, RequestSite +from django.contrib.sites.models import Site from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext @@ -13,16 +13,9 @@ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect from django.utils.translation import ugettext_lazy as _ +from tos.compat import get_runtime_user_model, get_request_site from tos.models import has_user_agreed_latest_tos, TermsOfService, UserAgreement -# Django 1.4 compatability -try: - from django.contrib.auth import get_user_model - USER_MODEL = get_user_model() -except ImportError: - from django.contrib.auth.models import User - USER_MODEL = User - class TosView(TemplateView): template_name = "tos/tos.html" @@ -54,11 +47,11 @@ def _redirect_to(redirect_to): def check_tos(request, template_name='tos/tos_check.html', redirect_field_name=REDIRECT_FIELD_NAME,): - redirect_to = _redirect_to(request.REQUEST.get(redirect_field_name, '')) + redirect_to = _redirect_to(request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, ''))) tos = TermsOfService.objects.get_current_tos() if request.method == "POST": if request.POST.get("accept", "") == "accept": - user = USER_MODEL.objects.get(pk=request.session['tos_user']) + user = get_runtime_user_model().objects.get(pk=request.session['tos_user']) user.backend = request.session['tos_backend'] # Save the user agreement to the new TOS @@ -90,7 +83,7 @@ def login(request, template_name='registration/login.html', authentication_form=AuthenticationForm): """Displays the login form and handles the login action.""" - redirect_to = request.REQUEST.get(redirect_field_name, '') + redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, '')) if request.method == "POST": form = authentication_form(data=request.POST) @@ -135,7 +128,7 @@ def login(request, template_name='registration/login.html', if Site._meta.installed: current_site = Site.objects.get_current() else: - current_site = RequestSite(request) + current_site = get_request_site()(request) return render_to_response(template_name, { 'form': form,