From 356b0857c9f0fc6d71a2c850eadc82c4ba6bd73e Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Tue, 4 Aug 2020 10:33:29 +0200 Subject: [PATCH 01/16] Added resend_validation endpoint, view --- api/views.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- main/urls.py | 6 ++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/api/views.py b/api/views.py index b51ef4252..6170ad149 100644 --- a/api/views.py +++ b/api/views.py @@ -10,6 +10,7 @@ from django.utils.decorators import method_decorator from django.contrib.auth import authenticate from django.contrib.auth.models import User +from django.conf import settings from django.views import View from django.db.models.functions import TruncMonth, TruncYear from django.db.models import Count, Sum @@ -26,7 +27,7 @@ from deployments.models import Heop from notifications.models import Subscription from notifications.notification import send_notification -from registrations.models import Recovery +from registrations.models import Recovery, Pending from main.frontend import frontend_url @@ -420,7 +421,7 @@ def handle_post(self, request, *args, **kwargs): return bad_request('Must include an `email` property') try: - user = User.objects.get(email__iexact=body['email']) + user = User.objects.filter(email__iexact=body['email']).first() except ObjectDoesNotExist: return bad_request('That email is not associated with a user') @@ -435,6 +436,46 @@ def handle_post(self, request, *args, **kwargs): return JsonResponse({'status': 'ok'}) +class ResendValidation(PublicJsonPostView): + def handle_post(self, request, *args, **kwargs): + body = json.loads(request.body.decode('utf-8')) + if 'username' not in body: + return bad_request('Must provide a username') + username = body['username'] + pending_user = Pending.objects.select_related('user').filter(user__username__iexact=username).first() + if pending_user: + if pending_user.user.is_active is True: + return bad_request('Your registration is already active, \ + you can try logging in with your registered username and password') + if pending_user.created_at < timezone.now() - timedelta(days=1): + return bad_request('The verification period is expired. \ + You must verify your email within 24 hours. \ + Please contact your system administrator.') + + # Construct and re-send the email + email_context = { + 'confirmation_link': 'https://%s/verify_email/?token=%s&user=%s' % ( + settings.BASE_URL, # on PROD it should point to goadmin... + pending_user.token, + username, + ) + } + + if pending_user.user.is_staff: + template = 'email/registration/verify-staff-email.html' + else: + template = 'email/registration/verify-outside-email.html' + + send_notification('Validate your account', + pending_user.user.email, + render_to_string(template, email_context), + 'Validate account - ' + username) + return JsonResponse({'status': 'ok'}) + else: + return bad_request('No pending registration found with the provided username. \ + Please check your input.') + + class AddCronJobLog(APIView): authentication_classes = (authentication.TokenAuthentication,) permissions_classes = (permissions.IsAuthenticated,) diff --git a/main/urls.py b/main/urls.py index b36a4844e..50afed877 100644 --- a/main/urls.py +++ b/main/urls.py @@ -35,12 +35,13 @@ AreaAggregate, AddCronJobLog, DummyHttpStatusError, - DummyExceptionError + DummyExceptionError, + ResendValidation ) from registrations.views import ( NewRegistration, VerifyEmail, - ValidateUser, + ValidateUser ) from per.views import ( DraftSent, @@ -145,6 +146,7 @@ url(r'^change_password', ChangePassword.as_view()), url(r'^recover_password', RecoverPassword.as_view()), url(r'^show_username', ShowUsername.as_view()), + url(r'^resend_validation', ResendValidation.as_view()), url(r'^api/v2/', include(router.urls)), url(r'^api/v2/event/(?P\d+)', api_views.EventViewset.as_view({'get': 'retrieve'})), url(r'^api/v2/event/(?P[-\w]+)', api_views.EventViewset.as_view({'get': 'retrieve'}, lookup_field='slug')), From 54e91e8e8d80b70c21b5f2dc2143731ea448c831 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Tue, 4 Aug 2020 10:33:37 +0200 Subject: [PATCH 02/16] flake8 fix --- registrations/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/registrations/views.py b/registrations/views.py index 4de9bee75..741176d8a 100644 --- a/registrations/views.py +++ b/registrations/views.py @@ -25,10 +25,10 @@ def is_valid_domain(email): domain = email.lower().split('@')[1] is_allowed = ( DomainWhitelist.objects - .filter(is_active=True) - .annotate(domain_name_lower=Lower('domain_name')) - .filter(domain_name_lower=domain) - .exists() + .filter(is_active=True) + .annotate(domain_name_lower=Lower('domain_name')) + .filter(domain_name_lower=domain) + .exists() ) if is_allowed or domain in ['ifrc.org', 'arcs.org.af', 'voroskereszt.hu']: @@ -189,7 +189,7 @@ def handle_get(self, request, *args, **kwargs): token = pending_user.admin_token_1 if idx == 0 else pending_user.admin_token_2 email_context = { 'validation_link': 'https://%s/validate_user/?token=%s&user=%s' % ( - settings.BASE_URL, # on PROD it should point to goadmin... + settings.BASE_URL, # on PROD it should point to goadmin... token, user, ), From dcaa397974a311dab1309e2ae896e5dc469378e6 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Thu, 6 Aug 2020 09:37:36 +0200 Subject: [PATCH 03/16] Error handling fix --- api/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/views.py b/api/views.py index 6170ad149..9bccfbaf0 100644 --- a/api/views.py +++ b/api/views.py @@ -420,9 +420,8 @@ def handle_post(self, request, *args, **kwargs): if 'email' not in body: return bad_request('Must include an `email` property') - try: user = User.objects.filter(email__iexact=body['email']).first() - except ObjectDoesNotExist: + if user is None: return bad_request('That email is not associated with a user') email_context = { From 4847a24ed6905c98af46dcaff293912b220bdd47 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Thu, 6 Aug 2020 09:37:52 +0200 Subject: [PATCH 04/16] Change PublicJsonPostView to APIView --- api/views.py | 73 ++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/api/views.py b/api/views.py index 9bccfbaf0..c48c6ac62 100644 --- a/api/views.py +++ b/api/views.py @@ -420,7 +420,7 @@ def handle_post(self, request, *args, **kwargs): if 'email' not in body: return bad_request('Must include an `email` property') - user = User.objects.filter(email__iexact=body['email']).first() + user = User.objects.filter(email__iexact=body['email']).first() if user is None: return bad_request('That email is not associated with a user') @@ -435,44 +435,45 @@ def handle_post(self, request, *args, **kwargs): return JsonResponse({'status': 'ok'}) -class ResendValidation(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - body = json.loads(request.body.decode('utf-8')) - if 'username' not in body: - return bad_request('Must provide a username') - username = body['username'] - pending_user = Pending.objects.select_related('user').filter(user__username__iexact=username).first() - if pending_user: - if pending_user.user.is_active is True: - return bad_request('Your registration is already active, \ - you can try logging in with your registered username and password') - if pending_user.created_at < timezone.now() - timedelta(days=1): - return bad_request('The verification period is expired. \ - You must verify your email within 24 hours. \ - Please contact your system administrator.') - - # Construct and re-send the email - email_context = { - 'confirmation_link': 'https://%s/verify_email/?token=%s&user=%s' % ( - settings.BASE_URL, # on PROD it should point to goadmin... - pending_user.token, - username, - ) - } +class ResendValidation(APIView): + def post(self, request): + username = request.data.get('username', None) + + if username: + pending_user = Pending.objects.select_related('user').filter(user__username__iexact=username).first() + if pending_user: + if pending_user.user.is_active is True: + return bad_request('Your registration is already active, \ + you can try logging in with your registered username and password') + if pending_user.created_at < timezone.now() - timedelta(days=1): + return bad_request('The verification period is expired. \ + You must verify your email within 24 hours. \ + Please contact your system administrator.') + + # Construct and re-send the email + email_context = { + 'confirmation_link': 'https://%s/verify_email/?token=%s&user=%s' % ( + settings.BASE_URL, # on PROD it should point to goadmin... + pending_user.token, + username, + ) + } - if pending_user.user.is_staff: - template = 'email/registration/verify-staff-email.html' - else: - template = 'email/registration/verify-outside-email.html' + if pending_user.user.is_staff: + template = 'email/registration/verify-staff-email.html' + else: + template = 'email/registration/verify-outside-email.html' - send_notification('Validate your account', - pending_user.user.email, - render_to_string(template, email_context), - 'Validate account - ' + username) - return JsonResponse({'status': 'ok'}) + send_notification('Validate your account', + pending_user.user.email, + render_to_string(template, email_context), + 'Validate account - ' + username) + return Response({'data': 'Success'}) + else: + return bad_request('No pending registration found with the provided username. \ + Please check your input.') else: - return bad_request('No pending registration found with the provided username. \ - Please check your input.') + return bad_request('Please provide your username in the request.') class AddCronJobLog(APIView): From 3b32ed02a4304c4f11c60e2c6f259aa5f3164b5d Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Thu, 6 Aug 2020 09:40:39 +0200 Subject: [PATCH 05/16] flake8 fix --- api/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/views.py b/api/views.py index c48c6ac62..08de5a2b4 100644 --- a/api/views.py +++ b/api/views.py @@ -465,9 +465,9 @@ def post(self, request): template = 'email/registration/verify-outside-email.html' send_notification('Validate your account', - pending_user.user.email, - render_to_string(template, email_context), - 'Validate account - ' + username) + pending_user.user.email, + render_to_string(template, email_context), + 'Validate account - ' + username) return Response({'data': 'Success'}) else: return bad_request('No pending registration found with the provided username. \ From 3adbd95895ad375d2a78191d338c5ce0805381e6 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Mon, 10 Aug 2020 16:45:10 +0200 Subject: [PATCH 06/16] Change PublicJsonPostView to APIView for api --- api/views.py | 85 +++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/api/views.py b/api/views.py index 08de5a2b4..401b2c1f2 100644 --- a/api/views.py +++ b/api/views.py @@ -314,18 +314,20 @@ def post(self, request, *args, **kwargs): return self.handle_post(request, *args, **kwargs) -class GetAuthToken(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - body = json.loads(request.body.decode('utf-8')) - if 'username' not in body or 'password' not in body: +class GetAuthToken(APIView): + permission_classes = [] + + def post(self, request): + username = request.data.get('username', None) + password = request.data.get('password', None) + + if username is None or password is None: return bad_request('Body must contain `username` and `password`') - username = body['username'] - password = body['password'] # Get the case-correct username for authenticate() casecorr_uname = User.objects.filter(username__iexact=username).values_list('username', flat=True).first() - if not casecorr_uname: - return bad_request('Invalid username or password') # definately username issue + if casecorr_uname is None: + return bad_request('Invalid username or password') # definitely username issue # User model's __str__ is its username user = authenticate(username=casecorr_uname, password=password) @@ -353,54 +355,57 @@ def handle_post(self, request, *args, **kwargs): return bad_request('Invalid username or password') # most probably password issue -class ChangePassword(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - body = json.loads(request.body.decode('utf-8')) - if 'username' not in body or ('password' not in body and 'token' not in body): +class ChangePassword(APIView): + permissions_classes = [] + + def post(self, request): + username = request.data.get('username', None) + password = request.data.get('password', None) + new_pass = request.data.get('new_password', None) + token = request.data.get('token', None) + if username is None or password is None: return bad_request('Must include a `username` and either a `password` or `token`') - try: - user = User.objects.get(username__iexact=body['username']) - except ObjectDoesNotExist: + user = User.objects.filter(username__iexact=username).first() + if user is None: return bad_request('Could not authenticate') - if 'password' in body and not user.check_password(body['password']): + if password and not user.check_password(password): return bad_request('Could not authenticate') - elif 'token' in body: - try: - recovery = Recovery.objects.get(user=user) - except ObjectDoesNotExist: + elif token: + recovery = Recovery.objects.filter(user=user).first() + if recovery is None: return bad_request('Could not authenticate') - if recovery.token != body['token']: + if recovery.token != token: return bad_request('Could not authenticate') recovery.delete() # TODO validate password - if 'new_password' not in body: + if new_pass is None: return bad_request('Must include a `new_password` property') - user.set_password(body['new_password']) + user.set_password(new_pass) user.save() return JsonResponse({'status': 'ok'}) -class RecoverPassword(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - body = json.loads(request.body.decode('utf-8')) - if 'email' not in body: +class RecoverPassword(APIView): + permission_classes = [] + + def post(self, request): + email = request.data.get('email', None) + if email is None: return bad_request('Must include an `email` property') - try: - user = User.objects.get(email__iexact=body['email']) - except ObjectDoesNotExist: + user = User.objects.filter(email__iexact=email).first() + if user is None: return bad_request('That email is not associated with a user') token = get_random_string(length=32) Recovery.objects.filter(user=user).delete() - recovery = Recovery.objects.create(user=user, - token=token) + Recovery.objects.create(user=user, token=token) email_context = { 'frontend_url': frontend_url, 'username': user.username, @@ -414,13 +419,15 @@ def handle_post(self, request, *args, **kwargs): return JsonResponse({'status': 'ok'}) -class ShowUsername(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - body = json.loads(request.body.decode('utf-8')) - if 'email' not in body: +class ShowUsername(APIView): + permission_classes = [] + + def post(self, request): + email = request.data.get('email', None) + if email is None: return bad_request('Must include an `email` property') - user = User.objects.filter(email__iexact=body['email']).first() + user = User.objects.filter(email__iexact=email).first() if user is None: return bad_request('That email is not associated with a user') @@ -436,6 +443,8 @@ def handle_post(self, request, *args, **kwargs): class ResendValidation(APIView): + permission_classes = [] + def post(self, request): username = request.data.get('username', None) @@ -465,7 +474,7 @@ def post(self, request): template = 'email/registration/verify-outside-email.html' send_notification('Validate your account', - pending_user.user.email, + [pending_user.user.email], render_to_string(template, email_context), 'Validate account - ' + username) return Response({'data': 'Success'}) From 359f0ec75238fb6b4f868de89377fa40a370ea4c Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Tue, 11 Aug 2020 09:30:23 +0200 Subject: [PATCH 07/16] Refactor NewRegistration to use APIView and fixes --- registrations/views.py | 89 ++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/registrations/views.py b/registrations/views.py index 741176d8a..98211c011 100644 --- a/registrations/views.py +++ b/registrations/views.py @@ -9,10 +9,10 @@ from django.contrib.auth.models import User from django.http import JsonResponse, HttpResponse from django.template.loader import render_to_string +from rest_framework.views import APIView from api.views import ( bad_request, bad_http_request, - PublicJsonPostView, PublicJsonRequestView, ) from api.models import Country @@ -48,33 +48,33 @@ def get_valid_admins(contacts): return emails -def create_active_user(raw): - user = User.objects.create_user(username=raw['username'], - first_name=raw['firstname'], - last_name=raw['lastname'], - email=raw['email'], - password=raw['password']) +def create_inactive_user(username, firstname, lastname, email, password): + user = User.objects.create_user(username=username, + first_name=firstname, + last_name=lastname, + email=email, + password=password, + is_active=False) return user -def set_user_profile_inactive(user, raw): - user.is_active = False - user.profile.country = Country.objects.get(pk=raw['country']) - user.profile.org_type = raw['organizationType'] - user.profile.org = raw['organization'] - user.profile.city = raw['city'] if 'city' in raw else None - user.profile.department = raw['department'] if 'department' in raw else None - user.profile.position = raw['position'] if 'position' in raw else None - user.profile.phone_number = raw['phoneNumber'] if 'phoneNumber' in raw else None +def set_user_profile(user, country, organization_type, organization, city, department, position, phone_number): + user.profile.country = Country.objects.get(pk=country) + user.profile.org_type = organization_type + user.profile.org = organization + user.profile.city = city + user.profile.department = department + user.profile.position = position + user.profile.phone_number = phone_number user.save() return user -class NewRegistration(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - body = json.loads(request.body.decode('utf-8')) +class NewRegistration(APIView): + permission_classes = [] - required_fields = [ + def post(self, request): + required_fields = ( 'email', 'username', 'password', @@ -83,33 +83,48 @@ def handle_post(self, request, *args, **kwargs): 'organization', 'firstname', 'lastname', - ] + ) - missing_fields = [field for field in required_fields if field not in body] - if len(missing_fields): + missing_fields = [field for field in required_fields if field not in request.data] + if missing_fields: return bad_request('Could not complete request. Please submit %s' % ', '.join(missing_fields)) - is_staff = is_valid_domain(body['email']) - admins = True if is_staff else get_valid_admins(body['contact']) + email = request.data.get('email', None) + username = request.data.get('username', None) + password = request.data.get('password', None) + firstname = request.data.get('firstname', None) + lastname = request.data.get('lastname', None) + country = request.data.get('country', None) + city = request.data.get('city', None) + organization_type = request.data.get('organizationType', None) + organization = request.data.get('organization', None) + department = request.data.get('department', None) + position = request.data.get('position', None) + phone_number = request.data.get('phoneNumber', None) + contact = request.data.get('contact', None) + + is_staff = is_valid_domain(email) + admins = True if is_staff else get_valid_admins(contact) if not admins: return bad_request('Non-IFRC users must submit two valid admin emails.') - if User.objects.filter(email__iexact=body['email']).count() > 0: + if User.objects.filter(email__iexact=email).exists(): return bad_request('A user with that email address already exists.') - if User.objects.filter(username__iexact=body['username']).count() > 0: + if User.objects.filter(username__iexact=username).exists(): return bad_request('That username is taken, please choose a different one.') - if ' ' in body['username']: + if ' ' in username: return bad_request('Username can not contain spaces, please choose a different one.') + # Create the User object try: - user = create_active_user(body) - except: + user = create_inactive_user(username, firstname, lastname, email, password) + except Exception: return bad_request('Could not create user.') + # Set the User Profile properties try: - # Note, this also sets the user's active status to False - set_user_profile_inactive(user, body) - except: - User.objects.filter(username=body['username']).delete() + set_user_profile(user, country, organization_type, organization, city, department, position, phone_number) + except Exception: + User.objects.filter(username=username).delete() return bad_request('Could not create user profile.') pending = Pending.objects.create(user=user, @@ -128,7 +143,7 @@ def handle_post(self, request, *args, **kwargs): 'confirmation_link': 'https://%s/verify_email/?token=%s&user=%s' % ( settings.BASE_URL, # on PROD it should point to goadmin... pending.token, - body['username'], + username, ) } @@ -139,9 +154,9 @@ def handle_post(self, request, *args, **kwargs): template = 'email/registration/verify-outside-email.html' send_notification('Validate your account', - [body['email']], + [email], render_to_string(template, email_context), - 'Validate account - ' + body['username']) + 'Validate account - ' + username) return JsonResponse({'status': 'ok'}) From b8b8e9ec3ed34b28d0a90879096284facb702d64 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Tue, 11 Aug 2020 13:02:29 +0200 Subject: [PATCH 08/16] Refactor of PER PublicJsonPostViews to APIViews --- per/views.py | 658 ++++++++++++++++++++------------------------------- 1 file changed, 258 insertions(+), 400 deletions(-) diff --git a/per/views.py b/per/views.py index 2c36ae1e8..c17193227 100644 --- a/per/views.py +++ b/per/views.py @@ -1,70 +1,34 @@ -import json, datetime, pytz -from django.http import JsonResponse, HttpResponse -from api.views import ( - bad_request, - bad_http_request, - PublicJsonPostView, - PublicJsonRequestView, -) -from rest_framework import viewsets -from django.contrib.auth.models import User +from django.db import transaction +from django.http import JsonResponse +from django.utils import timezone +from rest_framework import authentication, permissions +from rest_framework.views import APIView from .models import ( Draft, Form, FormData, WorkPlan, Overview ) -from rest_framework.authtoken.models import Token - -def create_draft(raw): - Draft.objects.filter(code=raw['code'], country_id=raw['country_id'], user_id=raw['user_id']).delete() # If exists (a previous draft), delete it. - draft = Draft.objects.create(code = raw['code'], - user_id = raw['user_id'], - data = raw['data'], - country_id = raw['country_id'], - ) - draft.save() - return draft - -def create_form(raw): - - form = Form.objects.create(code = raw['code'], - name = raw['name'], - language = raw['language'], - user_id = raw['user_id'], - country_id = raw['country_id'], - ns = raw['ns'], - ip_address = raw['ip_address'], - started_at = raw['started_at'], - ended_at = raw['ended_at'], - submitted_at = raw['submitted_at'], - comment = raw['comment'], - validated = raw['validated'], - finalized = raw['finalized'], - # unique_id = raw['unique_id'], # only KoBo form provided - ) - form.save() - return form - -def create_form_data(raw, form): - form_data = FormData.objects.create(form= form, - question_id = raw['id'], - selected_option = raw['op'], - notes = raw['nt'], - ) - form_data.save() - return form_data - -class FormSent(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - - #if not request.user.is_authenticated: - # return bad_request('Could not insert PER data due to not logged in user.') - - body = json.loads(request.body.decode('utf-8')) - - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') - if x_forwarded_for: - body['ip_address'] = x_forwarded_for.split(',')[-1].strip() - else: - body['ip_address'] = request.META.get('REMOTE_ADDR') +from api.views import bad_request + + +def get_client_ip(request): + """ https://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django """ + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[-1].strip() + else: + ip = request.META.get('REMOTE_ADDR') + return ip + + +def get_now_str(): + return str(timezone.now()) + + +class FormSent(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) + + def post(self, request): + ip = get_client_ip(request) required_fields = [ 'user_id', @@ -73,406 +37,300 @@ def handle_post(self, request, *args, **kwargs): 'language', ] - currentDT = datetime.datetime.now(pytz.timezone('UTC')) - if 'started_at' not in body: - body['started_at'] = str(currentDT) - if 'ended_at' not in body: - body['ended_at'] = str(currentDT) - if 'submitted_at' not in body: - body['submitted_at'] = str(currentDT) - if 'comment' not in body: - body['comment'] = None - if 'validated' not in body: - body['validated'] = False - if 'finalized' not in body: - body['finalized'] = False - if 'user_id' not in body: - body['user_id'] = None - if 'country_id' not in body: - body['country_id'] = None - if 'ns' not in body: - body['ns'] = None - - - missing_fields = [field for field in required_fields if field not in body] + missing_fields = [field for field in required_fields if field not in request.data] if len(missing_fields): return bad_request('Could not complete request. Please submit %s' % ', '.join(missing_fields)) - if ' ' in body['code']: + name = request.data.get('name', None) + code = request.data.get('code', None) + language = request.data.get('language', None) + started_at = request.data.get('started_at', get_now_str()) + ended_at = request.data.get('ended_at', get_now_str()) + submitted_at = request.data.get('submitted_at', get_now_str()) + comment = request.data.get('comment', None) + validated = request.data.get('validated', False) + finalized = request.data.get('finalized', False) + user_id = comment = request.data.get('user_id', None) + country_id = request.data.get('country_id', None) + ns = request.data.get('ns', None) + data = request.data.get('data', None) + + if ' ' in code: return bad_request('Code can not contain spaces, please choose a different one.') + # Create the Form object try: - form = create_form(body) - except: + form = Form.objects.create(code=code, + name=name, + language=language, + user_id=user_id, + country_id=country_id, + ns=ns, + ip_address=ip, + started_at=started_at, + ended_at=ended_at, + submitted_at=submitted_at, + comment=comment, + # unique_id = raw['unique_id'], # only KoBo form provided + validated=validated, + finalized=finalized) + except Exception: return bad_request('Could not insert PER form record.') - if hasattr(form,'status_code') and form.status_code == 400: - return bad_request('Could not insert PER form record due to inner failure.') - - if 'data' in body: - for rubr in body['data']: - try: - form_data = create_form_data(rubr, form) - except: - return bad_request('Could not insert PER formdata record.') - - if hasattr(form_data,'status_code') and form_data.status_code == 400: - return bad_request('Could not insert PER form data record due to inner failure.') - - return JsonResponse({'status': 'ok'}) - - -def change_form(raw): - - if 'id' not in raw: - return bad_request('Id sending is mandatory') - - form = Form.objects.get(pk=int(raw['id'])) # If exists, get it - - if not form: - return bad_request('Could not find PER form record.') - - #form.code = raw['code'] # we do not change it - #form.user_id = raw['user_id'] # we do not change it - form.name = raw['name'] if 'name' in raw else form.name - form.language = raw['language'] if 'language' in raw else form.language - form.country_id = raw['country_id'] if 'country_id' in raw else form.country_id - form.ns = raw['ns'] if 'ns' in raw else form.ns - form.ip_address = raw['ip_address'] if 'ip_address' in raw else form.ip_address - form.started_at = raw['started_at'] if 'started_at' in raw else form.started_at - form.ended_at = raw['ended_at'] if 'ended_at' in raw else form.ended_at - form.submitted_at = raw['submitted_at'] if 'submitted_at' in raw else form.submitted_at - form.comment = raw['comment'] if 'comment' in raw else form.comment - form.validated = raw['validated'] if 'validated' in raw else form.validated - form.finalized = raw['finalized'] if 'finalized' in raw else form.finalized - form.save() - - return form - -def change_form_data(raw, form): - if 'form_id' not in raw: - return bad_request('Form_id sending is mandatory') - if 'id' not in raw: - return bad_request('Question_id sending is mandatory') - form_data = FormData.objects.filter(form_id=raw['form_id'], question_id=raw['id'])[0] # If exists data item, get the first. We keep only 1 answer per question_id - if not form_data: - return bad_request('Could not find PER form data record.') - - #form_data.question_id = raw['id'] # we do not change it - form_data.selected_option = raw['op'] if 'op' in raw else form_data.selected_option - form_data.notes = raw['nt'] if 'nt' in raw else form_data.notes - - form_data.save() - return form_data - -class FormEdit(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: + # Create FormData of the Form + if data: try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - else: - return bad_request('User token is not given.') - if not u: - return bad_request('User is not logged in or inactive.') - - body = json.loads(request.body.decode('utf-8')) + with transaction.atomic(): # all or nothing + for rubr in data: + FormData.objects.create(form=form, + question_id=rubr.id, + selected_option=rubr.op, + notes=rubr.nt) + except Exception: + return bad_request('Could not insert PER formdata record.') - required_fields = [ - 'id', - ] + return JsonResponse({'status': 'ok'}) - currentDT = datetime.datetime.now(pytz.timezone('UTC')) - if 'ended_at' not in body: - body['ended_at'] = str(currentDT) - if 'submitted_at' not in body: - body['submitted_at'] = str(currentDT) - - missing_fields = [field for field in required_fields if field not in body] - if len(missing_fields): - return bad_request('Could not complete request. Please submit %s' % ', '.join(missing_fields)) +class FormEdit(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) + + def post(self, request): + form_id = request.data.get('id', None) + if form_id: + return bad_request('Could not complete request. Please submit %s' % form_id) + + form = Form.objects.filter(pk=form_id).first() + if form is None: + return bad_request('Could not find PER form record.') + + started_at = request.data.get('started_at', form.started_at) + ended_at = request.data.get('ended_at', get_now_str()) + submitted_at = request.data.get('submitted_at', get_now_str()) + name = request.data.get('name', form.name) + language = request.data.get('language', form.language) + country_id = request.data.get('country_id', form.country_id) + ns = request.data.get('ns', form.ns) + ip = request.data.get('ip_address', form.ip_address) + comment = request.data.get('comment', form.comment) + validated = request.data.get('validated', form.validated) + finalized = request.data.get('finalized', form.finalized) + data = request.data.get('data', None) + + # Update the Form properties and try to save try: - form = change_form(body) - except: + form.name = name + form.language = language + form.country_id = country_id + form.ns = ns + form.ip_address = ip + form.started_at = started_at + form.ended_at = ended_at + form.submitted_at = submitted_at + form.comment = comment + form.validated = validated + form.finalized = finalized + form.save() + except Exception: return bad_request('Could not change PER form record.') - if hasattr(form,'status_code') and form.status_code == 400: - return bad_request('Could not change PER form record due to inner failure.') + # Update Form Data of the Form + if data: + try: + with transaction.atomic(): # all or nothing + for rubr in data: + if rubr.id is None: + raise Exception('PER Form Data ID was missing. Form ID: {}'.format(form_id)) - if 'data' in body: - for rubr in body['data']: - rubr['form_id'] = body['id'] - try: - form_data = change_form_data(rubr, form) - except: - return bad_request('Could not change PER formdata record.') + form_data = FormData.objects.filter(form_id=form_id, question_id=rubr.id).first() + if not form_data: + raise Exception('Could not find PER Form Data record. \ + Form ID: {} Form Data ID: {}'.format(form_id, rubr.id)) - if hasattr(form_data,'status_code') and form_data.status_code == 400: - return bad_request('Could not change PER form data record due to inner failure.') + form_data.selected_option = rubr.op if rubr.op else form_data.selected_option + form_data.notes = rubr.nt if rubr.nt else form_data.notes - return JsonResponse({'status': 'ok'}) + form_data.save() + except Exception as err: + return bad_request('Could not change PER formdata record. {}'.format(err)) + return JsonResponse({'status': 'ok'}) -class DraftSent(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: - try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - else: - return bad_request('User token is not given.') - if not u: - return bad_request('User is not logged in or inactive.') - body = json.loads(request.body.decode('utf-8')) +class DraftSent(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) - required_fields = [ - 'code', 'user_id', - ] - - missing_fields = [field for field in required_fields if field not in body] - if len(missing_fields): + def post(self, request): + required_fields = ('code', 'user_id') + missing_fields = [field for field in required_fields if field not in request.data] + if missing_fields: return bad_request('Could not complete request. Please submit %s' % ', '.join(missing_fields)) - if ' ' in body['code']: - return bad_request('Code can not contain spaces, please choose a different one.') + code = request.data.get('code', None) + if ' ' in code: + return bad_request('Draft "code" can not contain spaces.') + + country_id = request.data.get('country_id', None) + user_id = request.data.get('user_id', None) + data = request.data.get('data', None) try: - form = create_draft(body) - except: - return bad_request('Could not insert PER draft.') + # If exists (a previous draft), delete it. + Draft.objects.filter(code=code, country_id=country_id, user_id=user_id).delete() + Draft.objects.create(code=code, + user_id=user_id, + data=data, + country_id=country_id) + except Exception: + return bad_request('Could not create PER draft.') return JsonResponse({'status': 'ok'}) -def create_workplan(raw): - # WorkPlan.objects.filter(code=raw['code'], user_id=raw['user_id']).delete() # If exists (a previous WorkPlan), delete it. - workplan = WorkPlan.objects.create(prioritization = raw['prioritization'], - components = raw['components'], - benchmark = raw['benchmark'], - actions = raw['actions'], - comments = raw['comments'], - timeline = raw['timeline'] \ - if 'timeline' in raw else str(datetime.datetime.now(pytz.timezone('UTC'))), - status = raw['status'], - support_required = raw['support_required'], - focal_point = raw['focal_point'], - country_id = raw['country_id'], - code = raw['code'], - question_id = raw['question_id'], - user_id = raw['user_id'], - ) - workplan.save() - return workplan - -class WorkPlanSent(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: - try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - else: - return bad_request('User token is not given.') - if not u: - return bad_request('User is not logged in or inactive.') - - body = json.loads(request.body.decode('utf-8')) +class WorkPlanSent(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) - required_fields = [ - 'country_id', 'user_id', - ] - missing_fields = [field for field in required_fields if field not in body] - if len(missing_fields): + def handle_post(self, request): + required_fields = ('country_id', 'user_id') + missing_fields = [field for field in required_fields if field not in request.data] + if missing_fields: return bad_request('Could not complete request. Please submit %s' % ', '.join(missing_fields)) - if ' ' in body['question_id']: - return bad_request('Question_id can not contain spaces, please choose a different one.') + question_id = request.data.get('question_id', None) + if ' ' in question_id: + return bad_request('Question ID can not contain spaces.') + + prioritization = request.data.get('prioritization', None) + components = request.data.get('components', None) + benchmark = request.data.get('benchmark', None) + actions = request.data.get('actions', None) + comments = request.data.get('comments', None) + timeline = request.data.get('timeline', get_now_str()) + status = request.data.get('status', None) + support_required = request.data.get('support_required', None) + focal_point = request.data.get('focal_point', None) + country_id = request.data.get('country_id', None) + code = request.data.get('code', None) + user_id = request.data.get('user_id', None) try: - workplan = create_workplan(body) - except: + WorkPlan.objects.create(prioritization=prioritization, + components=components, + benchmark=benchmark, + actions=actions, + comments=comments, + timeline=timeline, + status=status, + support_required=support_required, + focal_point=focal_point, + country_id=country_id, + code=code, + question_id=question_id, + user_id=user_id) + except Exception: return bad_request('Could not insert PER WorkPlan.') return JsonResponse({'status': 'ok'}) -def create_overview(raw): - # Overview.objects.filter(code=raw['code'], user_id=raw['user_id']).delete() # If exists (a previous Overview), delete it. - overview = Overview.objects.create(country_id = raw['country_id'], - user_id = raw['user_id'], - date_of_current_capacity_assessment = raw['date_of_current_capacity_assessment'] \ - if 'date_of_current_capacity_assessment' in raw else str(datetime.datetime.now(pytz.timezone('UTC'))), - type_of_capacity_assessment = raw['type_of_capacity_assessment'], - branch_involved = raw['branch_involved'], - focal_point_name = raw['focal_point_name'], - focal_point_email = raw['focal_point_email'], - had_previous_assessment = raw['had_previous_assessment'], - focus = raw['focus'], - facilitated_by = raw['facilitated_by'], - facilitator_email = raw['facilitator_email'], - phone_number = raw['phone_number'], - skype_address = raw['skype_address'], - date_of_mid_term_review = raw['date_of_mid_term_review'] \ - if 'date_of_mid_term_review' in raw else str(datetime.datetime.now(pytz.timezone('UTC'))), - approximate_date_next_capacity_assmt = raw['approximate_date_next_capacity_assmt'] \ - if 'approximate_date_next_capacity_assmt' in raw else str(datetime.datetime.now(pytz.timezone('UTC'))), - ) - - overview.save() - return overview - -class OverviewSent(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: - try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - else: - return bad_request('User token is not given.') - if not u: - return bad_request('User is not logged in or inactive.') - - body = json.loads(request.body.decode('utf-8')) +class OverviewSent(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) - required_fields = [ - 'country_id', 'user_id', - ] - - missing_fields = [field for field in required_fields if field not in body] - if len(missing_fields): + def post(self, request): + required_fields = ('country_id', 'user_id') + missing_fields = [field for field in required_fields if field not in request.data] + if missing_fields: return bad_request('Could not complete request. Please submit %s' % ', '.join(missing_fields)) + country_id = request.data.get('country_id', None) + user_id = request.data.get('user_id', None) + country_id = request.data.get('country_id', None) + date_of_current_capacity_assessment = request.data.get('date_of_current_capacity_assessment', get_now_str()) + type_of_capacity_assessment = request.data.get('type_of_capacity_assessment', None) + branch_involved = request.data.get('branch_involved', None) + focal_point_name = request.data.get('focal_point_name', None) + focal_point_email = request.data.get('focal_point_email', None) + had_previous_assessment = request.data.get('had_previous_assessment', None) + focus = request.data.get('focus', None) + facilitated_by = request.data.get('facilitated_by', None) + facilitator_email = request.data.get('facilitator_email', None) + phone_number = request.data.get('phone_number', None) + skype_address = request.data.get('skype_address', None) + date_of_mid_term_review = request.data.get('date_of_mid_term_review', get_now_str()) + approximate_date_next_capacity_assmt = request.data.get('approximate_date_next_capacity_assmt', get_now_str()) + try: - overview = create_overview(body) - except: + Overview.objects.create(country_id=country_id, + user_id=user_id, + date_of_current_capacity_assessment=date_of_current_capacity_assessment, + type_of_capacity_assessment=type_of_capacity_assessment, + branch_involved=branch_involved, + focal_point_name=focal_point_name, + focal_point_email=focal_point_email, + had_previous_assessment=had_previous_assessment, + focus=focus, + facilitated_by=facilitated_by, + facilitator_email=facilitator_email, + phone_number=phone_number, + skype_address=skype_address, + date_of_mid_term_review=date_of_mid_term_review, + approximate_date_next_capacity_assmt=approximate_date_next_capacity_assmt) + except Exception: return bad_request('Could not insert PER Overview.') return JsonResponse({'status': 'ok'}) -def delete_workplan(raw): - WorkPlan.objects.filter(id=raw['id']).delete() - return -class DelWorkPlan(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: - try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - #origtoken = Token.objects.get_or_create(user=u) - else: - return bad_request('User token is not given.') - if not u: # or receivedtoken.key != origtoken: - return bad_request('User is not logged in or inactive.') - - # Did not work any of these... - # if not request.user.is_authenticated: - # return bad_request('Could not insert PER data due to not logged in user.') - # a = self.get_authenticated_user(self, request) - # auth_header = request.META.get('HTTP_AUTHORIZATION') - # parts = auth_header[6:].split(':') - - body = json.loads(request.body.decode('utf-8')) +class DelWorkPlan(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) + + def post(self, request): + workplan_id = request.data.get('id', None) + if workplan_id is None: + return bad_request('Need to provide WorkPlan ID.') + try: - workplan = delete_workplan(body) - except: + WorkPlan.objects.filter(id=workplan_id).delete() + except Exception: return bad_request('Could not delete PER WorkPlan.') return JsonResponse({'status': 'ok'}) -def delete_overview(raw): - Overview.objects.filter(id=raw['id']).delete() - return -class DelOverview(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: - try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - else: - return bad_request('User token is not given.') - if not u: - return bad_request('User is not logged in or inactive.') +class DelOverview(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) - body = json.loads(request.body.decode('utf-8')) + def post(self, request): + overview_id = request.data.get('id', None) + if overview_id is None: + return bad_request('Need to provide Overview ID.') try: - workplan = delete_overview(body) - except: + Overview.objects.filter(id=overview_id).delete() + except Exception: return bad_request('Could not delete PER Overview.') return JsonResponse({'status': 'ok'}) -def delete_draft(draftId): - if draftId: - Draft.objects.filter(id=draftId).delete() - return -class DelDraft(PublicJsonPostView): - def handle_post(self, request, *args, **kwargs): - u = None - richtokenstring = request.META.get('HTTP_AUTHORIZATION') - if richtokenstring: - try: - receivedtoken = Token.objects.get(key=richtokenstring[6:]) - except: - return bad_request('User token is not correct.') - u = User.objects.filter(is_active=True, pk=receivedtoken.user_id) - else: - return bad_request('User token is not given.') - if not u: - return bad_request('User is not logged in or inactive.') - - body = json.loads(request.body.decode('utf-8')) +class DelDraft(APIView): + authentication_classes = (authentication.TokenAuthentication,) + permissions_classes = (permissions.IsAuthenticated,) + + def post(self, request): + draft_id = request.data.get('id', None) + if draft_id is None: + return bad_request('Need to provide Draft ID.') try: - workplan = delete_draft(body['id']) - except: + Draft.objects.filter(id=draft_id).delete() + except Exception: return bad_request('Could not delete PER Draft.') - - return JsonResponse({'status': 'ok'}) -# *** Test script from bash (similar to test_views.py, but remains in db): -# curl --header "Content-Type: application/json" \ -# --request POST \ -# --data '{ -# "code": "A1", -# "name": "Nemo", -# "language": 1, -# "unique_id": "1aad9295-ceb9-4ad5-9b10-84cc423e93f4", -# "started_at": "2019-04-11 11:42:22.278796+00", -# "submitted_at": "2019-04-11 09:42:52.278796+00", -# "user_id": 1111, -# "country_id": 47, -# "ns": "Huhhhu vorikeri", -# "data": [{"id": "1.1", "op": 3, "nt": "notes here"}, {"id": "1.2", "op": 0, "nt": "notes here also"}] -# }' \ -# http://localhost:8000/sendperform -# *** Afterpurger: -# delete from per_formdata where notes like 'notes here%'; delete from per_form where name='Nemo'; + return JsonResponse({'status': 'ok'}) From 18cb7f31420a4bdf95ff6686b87702d680e3479a Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Tue, 11 Aug 2020 14:19:12 +0200 Subject: [PATCH 09/16] Remove PublicJsonPostView --- api/views.py | 41 ------------------------------------- deployments/drf_views.py | 1 - registrations/test_views.py | 1 - 3 files changed, 43 deletions(-) diff --git a/api/views.py b/api/views.py index 401b2c1f2..71d5315fe 100644 --- a/api/views.py +++ b/api/views.py @@ -273,47 +273,6 @@ def handle_get(self, request, *args, **kwargs): return JsonResponse(dict(aggregate=list(aggregate))) -@method_decorator(csrf_exempt, name='dispatch') -class PublicJsonPostView(View): - http_method_names = ['post'] - - def decode_auth_header(self, auth_header): - parts = auth_header[7:].split(':') - return parts[0], parts[1] - - def get_authenticated_user(self, request): - auth_header = request.META.get('HTTP_AUTHORIZATION') - if not auth_header: - return None - - # Parse the authorization header - username, key = self.decode_auth_header(auth_header) - if not username or not key: - return None - - # Query the user - try: - user = User.objects.get(username=username) - except ObjectDoesNotExist: - return None - - # Query the key - try: - Token.objects.get(user=user, key=key) - except ObjectDoesNotExist: - return None - - return user - - def handle_post(self, request, *args, **kwargs): - print(pretty_request(request)) - - def post(self, request, *args, **kwargs): - if request.META.get('CONTENT_TYPE').find('application/json') == -1: - return bad_request('Content-type must be `application/json`') - return self.handle_post(request, *args, **kwargs) - - class GetAuthToken(APIView): permission_classes = [] diff --git a/deployments/drf_views.py b/deployments/drf_views.py index 80793e24c..dc024751d 100644 --- a/deployments/drf_views.py +++ b/deployments/drf_views.py @@ -44,7 +44,6 @@ from api.views import ( bad_request, bad_http_request, - PublicJsonPostView, PublicJsonRequestView, ) diff --git a/registrations/test_views.py b/registrations/test_views.py index 0e6a18737..e3724d81a 100644 --- a/registrations/test_views.py +++ b/registrations/test_views.py @@ -17,7 +17,6 @@ from api.views import ( bad_request, bad_http_request, - PublicJsonPostView, PublicJsonRequestView, GetAuthToken, ) From 4520ab4dee2c4951b99f89fcb1e8ce68c8f4bc68 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Tue, 11 Aug 2020 14:22:23 +0200 Subject: [PATCH 10/16] Fix dict elements, fix PER test --- per/test_views.py | 1 + per/views.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/per/test_views.py b/per/test_views.py index 4f980799c..17c2bfa36 100644 --- a/per/test_views.py +++ b/per/test_views.py @@ -8,6 +8,7 @@ def test_simple_form(self): 'code': 'A1', 'name': 'Nemo', 'language': 1, + 'user_id': 1, 'unique_id': '1aad9295-ceb9-4ad5-9b10-84cc423e93f4', 'started_at': '2019-04-11 11:42:22.278796+00', 'submitted_at': '2019-04-11 09:42:52.278796+00', diff --git a/per/views.py b/per/views.py index c17193227..26ed31d7d 100644 --- a/per/views.py +++ b/per/views.py @@ -83,9 +83,9 @@ def post(self, request): with transaction.atomic(): # all or nothing for rubr in data: FormData.objects.create(form=form, - question_id=rubr.id, - selected_option=rubr.op, - notes=rubr.nt) + question_id=rubr['id'], + selected_option=rubr['op'], + notes=rubr['nt']) except Exception: return bad_request('Could not insert PER formdata record.') @@ -140,16 +140,16 @@ def post(self, request): try: with transaction.atomic(): # all or nothing for rubr in data: - if rubr.id is None: + if rubr['id'] is None: raise Exception('PER Form Data ID was missing. Form ID: {}'.format(form_id)) - form_data = FormData.objects.filter(form_id=form_id, question_id=rubr.id).first() + form_data = FormData.objects.filter(form_id=form_id, question_id=rubr['id']).first() if not form_data: raise Exception('Could not find PER Form Data record. \ - Form ID: {} Form Data ID: {}'.format(form_id, rubr.id)) + Form ID: {} Form Data ID: {}'.format(form_id, rubr['id'])) - form_data.selected_option = rubr.op if rubr.op else form_data.selected_option - form_data.notes = rubr.nt if rubr.nt else form_data.notes + form_data.selected_option = rubr['op'] if rubr['op'] else form_data.selected_option + form_data.notes = rubr['nt'] if rubr['nt'] else form_data.notes form_data.save() except Exception as err: From 6ec15593dfcfd486d067cff032e66a51036fc977 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Wed, 12 Aug 2020 08:58:35 +0200 Subject: [PATCH 11/16] Remove unnecessary imports, flake8 fixes --- registrations/test_views.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/registrations/test_views.py b/registrations/test_views.py index e3724d81a..eb5b623fc 100644 --- a/registrations/test_views.py +++ b/registrations/test_views.py @@ -6,20 +6,11 @@ # 6. Use the admin token and new user username to query views.ValidateUser # 7. Confirm that a user without an official email is activated. -from django.test import TestCase from rest_framework.test import APITestCase from django.contrib.auth.models import User -from django.core.exceptions import ObjectDoesNotExist -from django.utils.crypto import get_random_string from .models import Pending from api.models import Country -from api.views import ( - bad_request, - bad_http_request, - PublicJsonRequestView, - GetAuthToken, -) class TwoGatekeepersTest(APITestCase): def setUp(self): @@ -29,7 +20,7 @@ def setUp(self): user2 = User.objects.create(username='ke', email='ke@arcs.org.af') user2.set_password('12345678') user2.save() - country = Country.objects.create(name='country') + Country.objects.create(name='country') def test_two_gatekeepers(self): # 1. Created two users to function as gatekeepers (with checkable email) @@ -40,12 +31,12 @@ def test_two_gatekeepers(self): 'email': 'pe@doesnotexist.hu', 'username': newusr, 'password': '87654321', - 'country': country.pk, + 'country': country.pk, 'organizationType': 'OTHR', 'organization': 'Zoo', 'firstname': 'Peter', 'lastname': 'Falk', - 'contact': [{'email':'jo@arcs.org.af'}, {'email':'ke@arcs.org.af'}] + 'contact': [{'email': 'jo@arcs.org.af'}, {'email': 'ke@arcs.org.af'}] } headers = {'CONTENT_TYPE': 'application/json'} resp = self.client.post('/register', body, format='json', headers=headers) @@ -77,7 +68,7 @@ def test_two_gatekeepers(self): # 6a_1repeat. The first token should be unusable now to query views.ValidateUser again resp = self.client.get('/validate_user', body2, format='json', headers=headers) - #resp.content: You, as an administrator has already confirmed the registration of pe user + # resp.content: You, as an administrator has already confirmed the registration of pe user self.assertEqual(resp.status_code, 400) # 7a_1. Confirming that a user without an official email is STILL NOT activated From d2593c34a8a078936ba592dc54ae05677d1fa56c Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Wed, 12 Aug 2020 09:19:15 +0200 Subject: [PATCH 12/16] Removed unnecessary imports, flake8 fixes --- deployments/drf_views.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/deployments/drf_views.py b/deployments/drf_views.py index dc024751d..72e63022f 100644 --- a/deployments/drf_views.py +++ b/deployments/drf_views.py @@ -1,19 +1,11 @@ -import json, datetime, pytz from collections import defaultdict -from rest_framework.authentication import ( - TokenAuthentication, - BasicAuthentication, - SessionAuthentication, -) +from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.decorators import action from rest_framework.response import Response from django_filters import rest_framework as filters -from django.shortcuts import render -from django.contrib.postgres.aggregates.general import ArrayAgg from django.db.models import Q, Sum, Count, F, Subquery, OuterRef, IntegerField from django.db.models.functions import Coalesce -from django.http import JsonResponse from django.shortcuts import get_object_or_404 from rest_framework import viewsets from reversion.views import RevisionMixin @@ -41,11 +33,6 @@ RegionalProjectSerializer, ProjectSerializer, ) -from api.views import ( - bad_request, - bad_http_request, - PublicJsonRequestView, -) class ERUOwnerViewset(viewsets.ReadOnlyModelViewSet): @@ -55,32 +42,39 @@ class ERUOwnerViewset(viewsets.ReadOnlyModelViewSet): serializer_class = ERUOwnerSerializer ordering_fields = ('created_at', 'updated_at',) + class ERUFilter(filters.FilterSet): deployed_to__isnull = filters.BooleanFilter(field_name='deployed_to', lookup_expr='isnull') deployed_to__in = ListFilter(field_name='deployed_to__id') type = filters.NumberFilter(field_name='type', lookup_expr='exact') event = filters.NumberFilter(field_name='event', lookup_expr='exact') event__in = ListFilter(field_name='event') + class Meta: model = ERU fields = ('available',) + class ERUViewset(viewsets.ReadOnlyModelViewSet): authentication_classes = (TokenAuthentication,) - #permission_classes = (IsAuthenticated,) # Some figures are shown on the home page also, and not only authenticated users should see them. + # Some figures are shown on the home page also, and not only authenticated users should see them. + # permission_classes = (IsAuthenticated,) queryset = ERU.objects.all() serializer_class = ERUSerializer filter_class = ERUFilter ordering_fields = ('type', 'units', 'equipment_units', 'deployed_to', 'event', 'eru_owner', 'available',) + class PersonnelDeploymentFilter(filters.FilterSet): country_deployed_to = filters.NumberFilter(field_name='country_deployed_to', lookup_expr='exact') region_deployed_to = filters.NumberFilter(field_name='region_deployed_to', lookup_expr='exact') event_deployed_to = filters.NumberFilter(field_name='event_deployed_to', lookup_expr='exact') + class Meta: model = PersonnelDeployment fields = ('country_deployed_to', 'region_deployed_to', 'event_deployed_to',) + class PersonnelDeploymentViewset(viewsets.ReadOnlyModelViewSet): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) @@ -89,10 +83,12 @@ class PersonnelDeploymentViewset(viewsets.ReadOnlyModelViewSet): filter_class = PersonnelDeploymentFilter ordering_fields = ('country_deployed_to', 'region_deployed_to', 'event_deployed_to',) + class PersonnelFilter(filters.FilterSet): country_from = filters.NumberFilter(field_name='country_from', lookup_expr='exact') type = filters.CharFilter(field_name='type', lookup_expr='exact') event_deployed_to = filters.NumberFilter(field_name='deployment__event_deployed_to', lookup_expr='exact') + class Meta: model = Personnel fields = { @@ -100,6 +96,7 @@ class Meta: 'end_date': ('exact', 'gt', 'gte', 'lt', 'lte') } + class PersonnelViewset(viewsets.ReadOnlyModelViewSet): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) @@ -108,6 +105,7 @@ class PersonnelViewset(viewsets.ReadOnlyModelViewSet): filter_class = PersonnelFilter ordering_fields = ('start_date', 'end_date', 'name', 'role', 'type', 'country_from', 'deployment',) + class PartnerDeploymentFilterset(filters.FilterSet): parent_society = filters.NumberFilter(field_name='parent_society', lookup_expr='exact') country_deployed_to = filters.NumberFilter(field_name='country_deployed_to', lookup_expr='exact') @@ -115,6 +113,7 @@ class PartnerDeploymentFilterset(filters.FilterSet): parent_society__in = ListFilter(field_name='parent_society__id') country_deployed_to__in = ListFilter(field_name='country_deployed_to__id') district_deployed_to__in = ListFilter(field_name='district_deployed_to__id') + class Meta: model = PartnerSocietyDeployment fields = { @@ -122,6 +121,7 @@ class Meta: 'end_date': ('exact', 'gt', 'gte', 'lt', 'lte'), } + class PartnerDeploymentViewset(viewsets.ReadOnlyModelViewSet): queryset = PartnerSocietyDeployment.objects.all() serializer_class = PartnerDeploymentSerializer From 20d12f2577ee2bdf9f0dcc91eeb151363c3b2069 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Wed, 12 Aug 2020 09:19:35 +0200 Subject: [PATCH 13/16] EsPage Views to APIView --- api/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/views.py b/api/views.py index 71d5315fe..7b2a9445d 100644 --- a/api/views.py +++ b/api/views.py @@ -102,14 +102,14 @@ def get(self, request, *args, **kwargs): return self.handle_get(request, *args, **kwargs) -class EsPageHealth(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class EsPageHealth(APIView): + def get(self, request): health = ES_CLIENT.cluster.health() return JsonResponse(health) -class EsPageSearch(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class EsPageSearch(APIView): + def get(self, request): page_type = request.GET.get('type', None) phrase = request.GET.get('keyword', None) if phrase is None: From 4fd9dc3f8f5b2c5bf25daf614e403fdd16464bd4 Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Wed, 12 Aug 2020 16:29:19 +0200 Subject: [PATCH 14/16] Removed PublicJsonRequestView --- api/views.py | 22 ++++++---------------- registrations/views.py | 10 ++++------ 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/api/views.py b/api/views.py index 7b2a9445d..f8b2785d1 100644 --- a/api/views.py +++ b/api/views.py @@ -92,16 +92,6 @@ def post(self, request): return Response({'data': 'Success'}) -class PublicJsonRequestView(View): - http_method_names = ['get', 'head', 'options'] - - def handle_get(self, request, *args, **kwargs): - print(pretty_request(request)) - - def get(self, request, *args, **kwargs): - return self.handle_get(request, *args, **kwargs) - - class EsPageHealth(APIView): def get(self, request): health = ES_CLIENT.cluster.health() @@ -152,8 +142,8 @@ def get(self, request): return JsonResponse(results['hits']) -class AreaAggregate(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class AreaAggregate(APIView): + def get(self, request): region_type = request.GET.get('type', None) region_id = request.GET.get('id', None) @@ -169,8 +159,8 @@ def handle_get(self, request, *args, **kwargs): return JsonResponse(dict(aggregate)) -class AggregateByDtype(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class AggregateByDtype(APIView): + def get(self, request): models = { 'appeal': Appeal, 'event': Event, @@ -191,8 +181,8 @@ def handle_get(self, request, *args, **kwargs): return JsonResponse(dict(aggregate=list(aggregate))) -class AggregateByTime(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class AggregateByTime(APIView): + def get(self, request): models = { 'appeal': Appeal, 'event': Event, diff --git a/registrations/views.py b/registrations/views.py index 98211c011..130ee9766 100644 --- a/registrations/views.py +++ b/registrations/views.py @@ -1,4 +1,3 @@ -import json import pytz from django.db.models import Q from django.db.models.functions import Lower @@ -13,7 +12,6 @@ from api.views import ( bad_request, bad_http_request, - PublicJsonRequestView, ) from api.models import Country from .models import Pending, DomainWhitelist @@ -161,8 +159,8 @@ def post(self, request): return JsonResponse({'status': 'ok'}) -class VerifyEmail(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class VerifyEmail(APIView): + def get(self, request): token = request.GET.get('token', None) user = request.GET.get('user', None) if not token or not user: @@ -221,8 +219,8 @@ def handle_get(self, request, *args, **kwargs): return HttpResponse(render_to_string('registration/validation-sent.html')) -class ValidateUser(PublicJsonRequestView): - def handle_get(self, request, *args, **kwargs): +class ValidateUser(APIView): + def get(self, request): token = request.GET.get('token', None) user = request.GET.get('user', None) if not token or not user: From 48ea1ae8b7100c845fec7ba9c6f4b6cbe507159c Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Thu, 13 Aug 2020 15:38:34 +0200 Subject: [PATCH 15/16] Imports and flake8 fixes --- per/views.py | 107 ++++++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/per/views.py b/per/views.py index b9053d63f..747560562 100644 --- a/per/views.py +++ b/per/views.py @@ -1,3 +1,4 @@ +import logging from django.db import transaction from django.http import JsonResponse from django.utils import timezone @@ -10,6 +11,7 @@ logger = logging.getLogger(__name__) + def get_client_ip(request): """ https://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') @@ -61,20 +63,22 @@ def post(self, request): # Create the Form object try: - form = Form.objects.create(code=code, - name=name, - language=language, - user_id=user_id, - country_id=country_id, - ns=ns, - ip_address=ip, - started_at=started_at, - ended_at=ended_at, - submitted_at=submitted_at, - comment=comment, - # unique_id = raw['unique_id'], # only KoBo form provided - validated=validated, - finalized=finalized) + form = Form.objects.create( + code=code, + name=name, + language=language, + user_id=user_id, + country_id=country_id, + ns=ns, + ip_address=ip, + started_at=started_at, + ended_at=ended_at, + submitted_at=submitted_at, + comment=comment, + # unique_id = raw['unique_id'], # only KoBo form provided + validated=validated, + finalized=finalized + ) except Exception: logger.error('Could not insert PER form record.', exc_info=True) return bad_request('Could not insert PER form record.') @@ -89,7 +93,7 @@ def post(self, request): selected_option=rubr['op'], notes=rubr['nt']) except Exception: - logger.error('Could not insert PER formdata record.', exc_info=True) + logger.error('Could not insert PER formdata record.', exc_info=True) return bad_request('Could not insert PER formdata record.') return JsonResponse({'status': 'ok'}) @@ -157,7 +161,7 @@ def post(self, request): form_data.save() except Exception as err: - logger.error('Could not change PER formdata record.', exc_info=True) + logger.error('Could not change PER formdata record.', exc_info=True) return bad_request('Could not change PER formdata record. {}'.format(err)) return JsonResponse({'status': 'ok'}) @@ -184,10 +188,12 @@ def post(self, request): try: # If exists (a previous draft), delete it. Draft.objects.filter(code=code, country_id=country_id, user_id=user_id).delete() - Draft.objects.create(code=code, - user_id=user_id, - data=data, - country_id=country_id) + Draft.objects.create( + code=code, + user_id=user_id, + data=data, + country_id=country_id + ) except Exception: return bad_request('Could not create PER draft.') @@ -222,19 +228,21 @@ def post(self, request): user_id = request.data.get('user_id', None) try: - WorkPlan.objects.create(prioritization=prioritization, - components=components, - benchmark=benchmark, - actions=actions, - comments=comments, - timeline=timeline, - status=status, - support_required=support_required, - focal_point=focal_point, - country_id=country_id, - code=code, - question_id=question_id, - user_id=user_id) + WorkPlan.objects.create( + prioritization=prioritization, + components=components, + benchmark=benchmark, + actions=actions, + comments=comments, + timeline=timeline, + status=status, + support_required=support_required, + focal_point=focal_point, + country_id=country_id, + code=code, + question_id=question_id, + user_id=user_id + ) except Exception: return bad_request('Could not insert PER WorkPlan.') @@ -269,21 +277,23 @@ def post(self, request): approximate_date_next_capacity_assmt = request.data.get('approximate_date_next_capacity_assmt', get_now_str()) try: - Overview.objects.create(country_id=country_id, - user_id=user_id, - date_of_current_capacity_assessment=date_of_current_capacity_assessment, - type_of_capacity_assessment=type_of_capacity_assessment, - branch_involved=branch_involved, - focal_point_name=focal_point_name, - focal_point_email=focal_point_email, - had_previous_assessment=had_previous_assessment, - focus=focus, - facilitated_by=facilitated_by, - facilitator_email=facilitator_email, - phone_number=phone_number, - skype_address=skype_address, - date_of_mid_term_review=date_of_mid_term_review, - approximate_date_next_capacity_assmt=approximate_date_next_capacity_assmt) + Overview.objects.create( + country_id=country_id, + user_id=user_id, + date_of_current_capacity_assessment=date_of_current_capacity_assessment, + type_of_capacity_assessment=type_of_capacity_assessment, + branch_involved=branch_involved, + focal_point_name=focal_point_name, + focal_point_email=focal_point_email, + had_previous_assessment=had_previous_assessment, + focus=focus, + facilitated_by=facilitated_by, + facilitator_email=facilitator_email, + phone_number=phone_number, + skype_address=skype_address, + date_of_mid_term_review=date_of_mid_term_review, + approximate_date_next_capacity_assmt=approximate_date_next_capacity_assmt + ) except Exception: return bad_request('Could not insert PER Overview.') @@ -339,4 +349,3 @@ def post(self, request): return bad_request('Could not delete PER Draft.') return JsonResponse({'status': 'ok'}) - From fcff7071a6c49da5d1da6de8bdce970c7fa9cc3d Mon Sep 17 00:00:00 2001 From: GregoryHorvath Date: Thu, 13 Aug 2020 15:56:36 +0200 Subject: [PATCH 16/16] Fix form_id is None --- per/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/per/views.py b/per/views.py index 747560562..c5ec6e812 100644 --- a/per/views.py +++ b/per/views.py @@ -105,7 +105,7 @@ class FormEdit(APIView): def post(self, request): form_id = request.data.get('id', None) - if form_id: + if form_id is None: return bad_request('Could not complete request. Please submit %s' % form_id) form = Form.objects.filter(pk=form_id).first()