diff --git a/README.md b/README.md index 7d6f7491..71a2c031 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The steps in this document assume that you have access to an OpenShift deploymen Prerequesites: * Docker -* Python 3.5 +* Python 3.6 To run this project in your development machine, follow these steps: @@ -22,21 +22,21 @@ To run this project in your development machine, follow these steps: 3. Install dependencies: - `pip3.5 install -r requirements.txt` + `pip3.6 install -r requirements.txt` 4. Create an environment settings file by copying `.env.example` to `.env` (`.env` will be ignored by Git) 5. Create a development database: - `python3.5 ./manage.py migrate` + `python3.6 ./manage.py migrate` 6. Load questions from fixtures: - `python3.5 ./manage.py loaddata edivorce/fixtures/Question.json` + `python3.6 ./manage.py loaddata edivorce/fixtures/Question.json` 7. If everything is alright, you should be able to start the Django development server: - `python3.5 ./manage.py runserver 0.0.0.0:8000` + `python3.6 ./manage.py runserver 0.0.0.0:8000` 8. Start the [Weasyprint server](https://hub.docker.com/r/aquavitae/weasyprint/) server on port 5005 diff --git a/edivorce/apps/core/authenticators.py b/edivorce/apps/core/authenticators.py index d70662a5..48b10f03 100644 --- a/edivorce/apps/core/authenticators.py +++ b/edivorce/apps/core/authenticators.py @@ -10,5 +10,8 @@ class BCeIDAuthentication(authentication.BaseAuthentication): """ def authenticate(self, request): - request.user = request._user # pylint: disable=protected-access + try: + request.user = request._user # pylint: disable=protected-access + except: + request.user = request._request.user # pylint: disable=protected-access return (request.user, None) diff --git a/edivorce/apps/core/decorators.py b/edivorce/apps/core/decorators.py index eb07c6ff..47fb9b76 100644 --- a/edivorce/apps/core/decorators.py +++ b/edivorce/apps/core/decorators.py @@ -13,7 +13,7 @@ def bceid_required(function=None): def _dec(view_func): def _view(request, *args, **kwargs): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return redirect(base_url + '/login') return view_func(request, *args, **kwargs) @@ -34,7 +34,7 @@ def intercept(function=None): def _dec(view_func): def _view(request, *args, **kwargs): - if (request.user.is_authenticated() and + if (request.user.is_authenticated and not request.user.has_seen_orders_page and not request.user.responses.filter(**terms).exists()): request.user.has_seen_orders_page = True diff --git a/edivorce/apps/core/middleware/basicauth_middleware.py b/edivorce/apps/core/middleware/basicauth_middleware.py index be37dff3..45b5de10 100644 --- a/edivorce/apps/core/middleware/basicauth_middleware.py +++ b/edivorce/apps/core/middleware/basicauth_middleware.py @@ -1,12 +1,12 @@ import base64 -import sys from django.http import HttpResponse from django.conf import settings from django.template.loader import render_to_string +from django.utils.deprecation import MiddlewareMixin -class BasicAuthMiddleware(object): +class BasicAuthMiddleware(MiddlewareMixin): """ Simple Basic Authentication module to password protect test environments based on : https://djangosnippets.org/snippets/2468/ diff --git a/edivorce/apps/core/middleware/bceid_middleware.py b/edivorce/apps/core/middleware/bceid_middleware.py index 79de81fb..cf9a1298 100644 --- a/edivorce/apps/core/middleware/bceid_middleware.py +++ b/edivorce/apps/core/middleware/bceid_middleware.py @@ -4,13 +4,14 @@ from ipaddress import ip_address, ip_network from django.conf import settings from django.shortcuts import redirect from django.utils import timezone +from django.utils.deprecation import MiddlewareMixin from ..models import BceidUser login_delta = datetime.timedelta(hours=2) -class AnonymousUser(): +class AnonymousUser: """ Anonymous user, present mainly to provide authentication checks in templates """ @@ -19,9 +20,11 @@ class AnonymousUser(): display_name = '' has_accepted_terms = False + @property def is_authenticated(self): return False + @property def is_anonymous(self): return True @@ -29,7 +32,7 @@ class AnonymousUser(): anonymous_user = AnonymousUser() -class BceidMiddleware(object): # pylint: disable=too-few-public-methods +class BceidMiddleware(MiddlewareMixin): # pylint: disable=too-few-public-methods """ Simple authentication middleware for operating in the BC Government OpenShift environment, with SiteMinder integration. @@ -67,7 +70,6 @@ class BceidMiddleware(object): # pylint: disable=too-few-public-methods In a local development environment, we generate a guid based on the login name and treat that guid/login name as guid/display name. """ - def process_request(self, request): # pylint: disable=too-many-branches """ Return None after populating request.user, or necessary redirects. diff --git a/edivorce/apps/core/migrations/0001_initial.py b/edivorce/apps/core/migrations/0001_initial.py index e4fe808a..275da625 100644 --- a/edivorce/apps/core/migrations/0001_initial.py +++ b/edivorce/apps/core/migrations/0001_initial.py @@ -31,7 +31,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('bceid', models.CharField(max_length=100)), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), migrations.CreateModel( @@ -46,8 +46,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('value', models.TextField(blank=True)), - ('question', models.ForeignKey(to='core.Question')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ('question', models.ForeignKey(to='core.Question', on_delete=models.CASCADE)), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), migrations.AddField( @@ -58,11 +58,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='formquestions', name='legal_form', - field=models.ForeignKey(to='core.LegalForm'), + field=models.ForeignKey(to='core.LegalForm', on_delete=models.CASCADE), ), migrations.AddField( model_name='formquestions', name='question', - field=models.ForeignKey(to='core.Question'), + field=models.ForeignKey(to='core.Question', on_delete=models.CASCADE), ), ] diff --git a/edivorce/apps/core/migrations/0005_auto_20170131_0004.py b/edivorce/apps/core/migrations/0005_auto_20170131_0004.py index 510f5043..28797e30 100644 --- a/edivorce/apps/core/migrations/0005_auto_20170131_0004.py +++ b/edivorce/apps/core/migrations/0005_auto_20170131_0004.py @@ -18,8 +18,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('value', models.TextField(blank=True)), - ('question', models.ForeignKey(to='core.Question')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ('question', models.ForeignKey(to='core.Question', on_delete=models.CASCADE)), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), migrations.RemoveField( diff --git a/edivorce/apps/core/migrations/0007_auto_20170210_1702.py b/edivorce/apps/core/migrations/0007_auto_20170210_1702.py index ae343e3d..efb1d8bf 100644 --- a/edivorce/apps/core/migrations/0007_auto_20170210_1702.py +++ b/edivorce/apps/core/migrations/0007_auto_20170210_1702.py @@ -35,7 +35,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='userresponse', name='bceid_user', - field=models.ForeignKey(default=1, to='core.BceidUser'), + field=models.ForeignKey(default=1, to='core.BceidUser', on_delete=models.CASCADE), preserve_default=False, ), ] diff --git a/edivorce/apps/core/migrations/0016_auto_20171114_2151.py b/edivorce/apps/core/migrations/0016_auto_20171114_2151.py index 90d21336..bc51d621 100644 --- a/edivorce/apps/core/migrations/0016_auto_20171114_2151.py +++ b/edivorce/apps/core/migrations/0016_auto_20171114_2151.py @@ -24,11 +24,11 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='userresponse', name='bceid_user', - field=models.ForeignKey(related_name='responses', to='core.BceidUser'), + field=models.ForeignKey(related_name='responses', to='core.BceidUser', on_delete=models.CASCADE), ), migrations.AlterField( model_name='userresponse', name='question', - field=models.ForeignKey(related_name='responses', to='core.Question'), + field=models.ForeignKey(related_name='responses', to='core.Question', on_delete=models.CASCADE), ), ] diff --git a/edivorce/apps/core/models.py b/edivorce/apps/core/models.py index dad9f4a2..4d1b1bba 100644 --- a/edivorce/apps/core/models.py +++ b/edivorce/apps/core/models.py @@ -31,9 +31,11 @@ class BceidUser(models.Model): has_accepted_terms = models.BooleanField(default=False) """ Flag for accepting terms of service """ + @property def is_authenticated(self): return True + @property def is_anonymous(self): return False @@ -91,10 +93,10 @@ class UserResponse(models.Model): User input """ - bceid_user = models.ForeignKey(BceidUser, related_name='responses') + bceid_user = models.ForeignKey(BceidUser, related_name='responses', on_delete=models.CASCADE) """ User providing response """ - question = models.ForeignKey(Question, related_name='responses') + question = models.ForeignKey(Question, related_name='responses', on_delete=models.CASCADE) """ Originating question """ value = models.TextField(blank=True) diff --git a/edivorce/apps/core/templates/acknowledgements.html b/edivorce/apps/core/templates/acknowledgements.html index 64dba713..72c47d0b 100644 --- a/edivorce/apps/core/templates/acknowledgements.html +++ b/edivorce/apps/core/templates/acknowledgements.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load staticfiles %} +{% load static %} {% block title %}{{ block.super }}: Acknowledgements{% endblock %} diff --git a/edivorce/apps/core/templates/question/06_children_what_for.html b/edivorce/apps/core/templates/question/06_children_what_for.html index bb44bc56..aa5aa3a1 100644 --- a/edivorce/apps/core/templates/question/06_children_what_for.html +++ b/edivorce/apps/core/templates/question/06_children_what_for.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load staticfiles %} +{% load static %} {% load input_field %} {% load step_order %} {% load load_json %} diff --git a/edivorce/apps/core/templatetags/input_field.py b/edivorce/apps/core/templatetags/input_field.py index e3f9abf6..df4703d5 100644 --- a/edivorce/apps/core/templatetags/input_field.py +++ b/edivorce/apps/core/templatetags/input_field.py @@ -81,7 +81,7 @@ def input_field(context, type, name='', value='', multiple='', **kwargs): try: date = datetime.strptime(value, '%d/%m/%Y') value = date.strftime('%b %d, %Y') - if context['request'].user.is_authenticated(): + if context['request'].user.is_authenticated: UserResponse.objects.filter( bceid_user=context['request'].user, question=name ).update(value=value) diff --git a/edivorce/apps/core/utils/user_response.py b/edivorce/apps/core/utils/user_response.py index 3ae97993..cb694348 100644 --- a/edivorce/apps/core/utils/user_response.py +++ b/edivorce/apps/core/utils/user_response.py @@ -151,7 +151,7 @@ def __get_data(bceid_user): COMMON_LAW = 'Living together in a marriage like relationship' MARRIED = 'Legally married' - responses = UserResponse.objects.filter(bceid_user=bceid_user) + responses = UserResponse.objects.filter(bceid_user=bceid_user).select_related('question') married_status = responses.filter(question_id='married_marriage_like') if married_status.count() > 0: diff --git a/edivorce/apps/core/views/api.py b/edivorce/apps/core/views/api.py index 74c276b7..e1a52f40 100644 --- a/edivorce/apps/core/views/api.py +++ b/edivorce/apps/core/views/api.py @@ -26,7 +26,7 @@ class UserResponseHandler(APIView): if question is not None: # As a result of discussion, decide to escape < and > only - if request.user.is_authenticated(): + if request.user.is_authenticated: save_to_db(serializer, question, value, request.user) else: # only prequalification questions can be answered when you @@ -36,7 +36,7 @@ class UserResponseHandler(APIView): status=status.HTTP_511_NETWORK_AUTHENTICATION_REQUIRED) save_to_session(request, question, value) else: - if request.user.is_authenticated() and hasattr(request.user, question_key): + if request.user.is_authenticated and hasattr(request.user, question_key): setattr(request.user, question_key, value == 'true') request.user.save() user_attribute_updated = True diff --git a/edivorce/apps/core/views/main.py b/edivorce/apps/core/views/main.py index 6575e44d..d98d5361 100644 --- a/edivorce/apps/core/views/main.py +++ b/edivorce/apps/core/views/main.py @@ -3,7 +3,6 @@ import datetime from django.conf import settings from django.shortcuts import render, redirect from django.utils import timezone -from django.template import RequestContext from edivorce.apps.core.utils.derived import get_derived_data from ..decorators import bceid_required, intercept @@ -38,7 +37,7 @@ def prequalification(request, step): """ template = 'prequalification/step_%s.html' % step - if not request.user.is_authenticated(): + if not request.user.is_authenticated: responses_dict = get_responses_from_session(request) else: responses_dict = get_responses_from_db(request.user) @@ -53,7 +52,7 @@ def success(request): """ This page is shown if the user passes the qualification test """ - if request.user.is_authenticated(): + if request.user.is_authenticated: return redirect(settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1] + '/overview') prequal_responses = get_responses_from_session_grouped_by_steps(request)['prequalification'] @@ -107,7 +106,7 @@ def login(request): if settings.DEPLOYMENT_TYPE in ['localdev', 'minishift'] and not request.session.get('fake_bceid_guid'): return redirect(settings.PROXY_BASE_URL + settings.FORCE_SCRIPT_NAME[:-1] + '/bceid') - if not request.user.is_authenticated(): + if not request.user.is_authenticated: # Fix for weird siteminder behaviour...... # If a user is logged into an IDIR then they can see the login page but # the SMGOV headers are missing. If this is the case, then log them out @@ -209,18 +208,18 @@ def question(request, step, sub_step=None): return render(request, template_name=template, context=responses_dict) -def page_not_found(request): +def page_not_found(request, exception, template_name='404.html'): """ 404 Error Page """ - return render(request, '404.html', status=404) + return render(request, template_name, status=404) -def server_error(request): +def server_error(request, template_name='500.html'): """ 500 Error Page """ - return render(request, '500.html', status=500) + return render(request, template_name, status=500) def legal(request): diff --git a/edivorce/apps/core/views/system.py b/edivorce/apps/core/views/system.py index abdd4720..c6bf7a95 100644 --- a/edivorce/apps/core/views/system.py +++ b/edivorce/apps/core/views/system.py @@ -25,26 +25,26 @@ def current(request): raise Http404() if request.GET.get('reset', False): - if not request.user.is_anonymous(): + if not request.user.is_anonymous: request.user.responses.all().delete() request.user.delete() request.session.flush() return redirect(reverse('current')) - if request.GET.get('intercept', False) and request.user.is_authenticated(): + if request.GET.get('intercept', False) and request.user.is_authenticated: request.user.has_seen_orders_page = False request.user.save() request.user.responses.filter(question__key='want_which_orders').delete() return redirect(reverse('current')) - if request.GET.get('terms', False) and request.user.is_authenticated(): + if request.GET.get('terms', False) and request.user.is_authenticated: request.user.has_accepted_terms = not request.user.has_accepted_terms request.user.save() return redirect(reverse('current')) context = { 'hide_nav': True, - 'is_anonymous': request.user.is_anonymous(), + 'is_anonymous': request.user.is_anonymous, } return render(request, 'dashboard/current.html', context=context) diff --git a/edivorce/settings/base.py b/edivorce/settings/base.py index 28345194..516a0deb 100644 --- a/edivorce/settings/base.py +++ b/edivorce/settings/base.py @@ -49,7 +49,7 @@ INSTALLED_APPS = ( 'sass_processor', ) -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'edivorce.apps.core.middleware.basicauth_middleware.BasicAuthMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/edivorce/settings/openshift.py b/edivorce/settings/openshift.py index c017cfe8..891d4e81 100644 --- a/edivorce/settings/openshift.py +++ b/edivorce/settings/openshift.py @@ -8,7 +8,7 @@ def openshift_db_config(): engines = { 'sqlite': 'django.db.backends.sqlite3', - 'postgresql': 'django.db.backends.postgresql_psycopg2', + 'postgresql': 'django.db.backends.postgresql', 'mysql': 'django.db.backends.mysql', } diff --git a/edivorce/urls.py b/edivorce/urls.py index 2e09695a..e80a8862 100644 --- a/edivorce/urls.py +++ b/edivorce/urls.py @@ -11,6 +11,8 @@ if settings.ENVIRONMENT in ['localdev', 'dev', 'test', 'minishift']: if settings.ENVIRONMENT in ['localdev', 'minishift']: urlpatterns.append(url(r'^admin/', admin.site.urls)) + urlpatterns.append(url(r'^404/$', main.page_not_found, {'exception': Exception()})) + urlpatterns.append(url(r'^500/$', main.server_error)) urlpatterns.append(url(r'^', include('edivorce.apps.core.urls'))) diff --git a/requirements.txt b/requirements.txt index bf2abfba..403a4989 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -Django<1.12 -django-compressor==2.1 -django-crispy-forms==1.6.1 -django-debug-toolbar==1.9 -django-sass-processor==0.5.5 -djangorestframework==3.5.3 -gunicorn==19.4.5 -libsass==0.13.3 -psycopg2==2.7.3 -requests==2.13.0 -six==1.10.0 -Unipath==1.1 -whitenoise==3.0 +Django>=2.2,<3.0 +django-compressor>=2.4,<3.0 +django-crispy-forms>=1.9,<2.0 +django-debug-toolbar>=2.2,<3.0 +django-sass-processor>=0.8,<1.0 +djangorestframework>=3.11,<4.0 +gunicorn>=20.0,<21.0 +libsass>=0.20.0<1.0 +psycopg2>=2.8,<3.0 +requests>=2.24,<3.0 +six>=1.15,<2.0 +Unipath>=1.1,<2.0 +whitenoise>=5.1,<6.0