Merge branch 'feature/signup-validation' into 'develop'

Feature/signup validation

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!18
This commit is contained in:
Aarni Halinen
2020-06-16 16:18:58 +00:00
8 changed files with 190 additions and 49 deletions
+16 -4
View File
@@ -1,12 +1,24 @@
stages:
- setup
- lint
- test
- publish
- deploy
install:
image: node:12
stage: setup
script:
- npm ci
artifacts:
paths:
- node_modules
expire_in: 1 week
test:
image: python:3.7
stage: test
needs: []
services:
- postgres:12
variables:
@@ -25,6 +37,7 @@ test:
lint:pycodestyle:
image: python:3.7
stage: lint
needs: []
script:
- pip install pycodestyle
- pycodestyle --config=setup.cfg --count .
@@ -32,22 +45,21 @@ lint:pycodestyle:
lint:eslint:
image: node:alpine
stage: lint
before_script:
- npm install
needs: ["install"]
script:
- npm run eslint
lint:remark:
image: node:alpine
stage: lint
before_script:
- npm install
needs: ["install"]
script:
- npm run remark
publish:
stage: publish
image: docker:stable
needs: ["test", "lint:pycodestyle", "lint:eslint", "lint:remark"]
services:
- docker:stable-dind
only:
+2 -1
View File
@@ -29,7 +29,7 @@ django-auditlog==0.4.5
phonenumbers==8.11.4
django-phonenumber-field[phonenumbers]==4.0.0
django-autocomplete-light==3.4.1
six==1.10.0
six==1.11.0
django-suit==0.2.26
telepot==12.3
pyexcel==0.5.14
@@ -39,3 +39,4 @@ openpyxl==2.6.4
django-app-namespace-template-loader==0.4.1
django-filter==2.0.0
whitenoise==4.1.4
jsonschema==3.2.0
+48
View File
@@ -100,6 +100,54 @@ class SignupForm(models.Model):
def __str__(self):
return _('#{} {}').format(self.id, self.title)
@property
def schema(self):
questions = self.questions
properties = {}
for q in questions:
id = q["id"]
question_type = q["type"]
if question_type == "text":
properties[id] = {
"type": "string"
}
elif question_type == "radiobutton":
options = q["options"]
regexes = map(lambda x: f"^{x}$", options)
pattern = "|".join(regexes)
properties[id] = {
"type": "string",
# Remove last regex or
"pattern": pattern,
}
elif question_type == "checkbox":
options = q["options"]
regexes = map(lambda x: f"^{x}$", options)
pattern = "|".join(regexes)
properties[id] = {
"type": "array",
"uniqueItems": True,
"maxItems": len(options),
"items": {
"type": "string",
"pattern": pattern
}
}
else:
raise Exception("invalid question type!")
ids = list(map(lambda x: x["id"], questions))
return {
"type": "object",
"required": ids,
"minProperties": len(ids),
"maxProperties": len(ids),
"properties": properties
}
class Meta:
verbose_name = _('Signup form')
verbose_name_plural = _('Signup forms')
+3 -3
View File
@@ -3,7 +3,7 @@ from webapp.models import *
class SignupFormSerializer(serializers.HyperlinkedModelSerializer):
questions = serializers.JSONField(binary=True)
questions = serializers.JSONField()
class Meta:
model = SignupForm
@@ -56,7 +56,7 @@ class SignupSerializer(serializers.ModelSerializer):
source="signupForm",
queryset=SignupForm.objects.all()
)
answer = serializers.JSONField(binary=True)
answer = serializers.JSONField()
class Meta:
model = Signup
@@ -69,7 +69,7 @@ class SignupSerializer(serializers.ModelSerializer):
class SavedQuestionsSerializer(serializers.ModelSerializer):
question = serializers.JSONField(binary=True)
question = serializers.JSONField()
class Meta:
model = TemplateQuestion
+3 -3
View File
@@ -10,7 +10,7 @@ ALL_QUESTION_TYPES = [
{"id": "i10d426d5", "name": "Asd3", "type": "checkbox", "options": ["A", "B", "C"]}
]
ALL_QUESTION_TYPES_ANSWER = {"j5CeRZDvl": "Testi", "RHJhSoaLD": "maybe", "i10d426d5": ["B"]}
ALL_QUESTION_TYPES_ANSWER = {"j5CeRZDvl": "Testi", "RHJhSoaLD": "maybe", "i10d426d5": ["B", "C"]}
def createSignupForm(name="Form1", start_time=timezone.now(), end_time=month_from_now(), questions=ALL_QUESTION_TYPES, visible=True):
@@ -30,8 +30,8 @@ def createSignupObject(form, answer):
)
def createSignupJSON(form_id, answer):
def createSignupRequest(form_id, answer):
return {
"signupForm_id": form_id,
"answer": json.dumps(answer)
"answer": answer
}
+7 -33
View File
@@ -1,14 +1,10 @@
from django.test import TestCase
from unittest import skip
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, force_authenticate
from webapp.serializers import SignupSerializer, SignupFormSerializer
from webapp.serializers import SignupSerializer
from webapp.models import Signup
from webapp.tests.event_fixture import createEventObject
from webapp.tests.signup_fixture import createSignupForm, createSignupObject, createSignupJSON, ALL_QUESTION_TYPES, ALL_QUESTION_TYPES_ANSWER
from webapp.tests.signup_fixture import createSignupForm, createSignupObject, createSignupRequest, ALL_QUESTION_TYPES, ALL_QUESTION_TYPES_ANSWER
URL = "/api/signup/"
@@ -17,10 +13,13 @@ class SignupTestCase(APITestCase):
def setUp(self):
self.signupForm = createSignupForm()
self.signupFormText = createSignupForm(name="Form2", questions=[ALL_QUESTION_TYPES[0]])
self.signupFormRadio = createSignupForm(name="Form3", questions=[ALL_QUESTION_TYPES[1]])
self.signupFormCheck = createSignupForm(name="Form4", questions=[ALL_QUESTION_TYPES[2]])
self.hiddenForm = createSignupForm(visible=False)
self.signup1 = createSignupObject(self.signupForm, ALL_QUESTION_TYPES)
self.signup2 = createSignupObject(self.signupForm, [])
self.signup2 = createSignupObject(self.signupForm, ALL_QUESTION_TYPES)
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
@@ -30,10 +29,6 @@ class SignupTestCase(APITestCase):
self.signupForm.signup_set.all(),
many=True
)
# Unauthorized
response = self.client.get(URL, format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.get(URL, format="json")
@@ -45,10 +40,6 @@ class SignupTestCase(APITestCase):
expected = SignupSerializer(
Signup.objects.get(id=id)
)
# Unauthorized
response = self.client.get(f"{URL}{id}/", format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
# Authenticate
self.client.force_authenticate(user=self.authClient)
response = self.client.get(f"{URL}{id}/", format="json")
@@ -56,32 +47,15 @@ class SignupTestCase(APITestCase):
self.assertEqual(response.data, expected.data)
def test_create_signup(self):
new = createSignupJSON(self.signupForm.id, ALL_QUESTION_TYPES_ANSWER)
new = createSignupRequest(self.signupForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
print(response.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Signup.objects.count(), 3)
def test_create_signup_404_or_hidden(self):
new = createSignupJSON(3001, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Signup.objects.count(), 2)
new = createSignupJSON(self.hiddenForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Signup.objects.count(), 2)
@skip("NotImplemented")
def test_get_hidden_forms_admin(self):
pass
@skip("NotImplemented")
def test_create_malformed_answer(self):
response = self.client.post(URL, createSignupJSON(self.signupForm.id, []), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
# Update and Delete are available for super admin (Django Admin)
# and to the user that signed up (uid token)
@skip("NotImplemented")
+95
View File
@@ -0,0 +1,95 @@
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase, force_authenticate
from webapp.serializers import SignupSerializer
from webapp.models import Signup
from webapp.tests.signup_fixture import createSignupForm, createSignupObject, createSignupRequest, ALL_QUESTION_TYPES, ALL_QUESTION_TYPES_ANSWER
URL = "/api/signup/"
class SignupErrorTestCase(APITestCase):
def setUp(self):
self.signupForm = createSignupForm()
self.signupFormText = createSignupForm(name="Form2", questions=[ALL_QUESTION_TYPES[0]])
self.signupFormRadio = createSignupForm(name="Form3", questions=[ALL_QUESTION_TYPES[1]])
self.signupFormCheck = createSignupForm(name="Form4", questions=[ALL_QUESTION_TYPES[2]])
self.hiddenForm = createSignupForm(visible=False)
self.signup1 = createSignupObject(self.signupForm, ALL_QUESTION_TYPES)
self.signup2 = createSignupObject(self.signupForm, ALL_QUESTION_TYPES)
username, password = "test_admin", "password123"
self.authClient = User.objects.create_superuser(username, "myemail@test.com", password)
def test_get_all_unauthorized(self):
response = self.client.get(URL, format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_get_single_unauthorized(self):
id = self.signup1.id
expected = SignupSerializer(
Signup.objects.get(id=id)
)
response = self.client.get(f"{URL}{id}/", format="json")
self.assertTrue(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_create_signup_404(self):
new = createSignupRequest(3001, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(Signup.objects.count(), 2)
def test_create_signup_hidden(self):
new = createSignupRequest(self.hiddenForm.id, ALL_QUESTION_TYPES_ANSWER)
response = self.client.post(URL, new, format="json")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(Signup.objects.count(), 2)
def test_create_empty_body(self):
response = self.client.post(URL, createSignupRequest(self.signupForm.id, {}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_array_body(self):
response = self.client.post(URL, createSignupRequest(self.signupForm.id, []), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_extra_body(self):
testInput = ALL_QUESTION_TYPES_ANSWER.copy()
testInput["newId"] = "Oon extraa"
response = self.client.post(URL, createSignupRequest(self.signupForm.id, testInput), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_id(self):
response = self.client.post(URL, createSignupRequest(self.signupFormText.id, {"malformed": "Tekstiä"}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_type_text(self):
response = self.client.post(URL, createSignupRequest(self.signupFormText.id, {"j5CeRZDvl": 123}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_data_checkbox(self):
response = self.client.post(URL, createSignupRequest(self.signupFormCheck.id, {
"i10d426d5": ["D"]
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_type_checkbox(self):
response = self.client.post(URL, createSignupRequest(self.signupFormCheck.id, {
"i10d426d5": {"j5CeRZDvl": {"asd": "123"}}
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_radio(self):
response = self.client.post(URL, createSignupRequest(self.signupFormRadio.id, {
"RHJhSoaLD": []
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_bad_type_radio(self):
response = self.client.post(URL, createSignupRequest(self.signupFormRadio.id, {
"RHJhSoaLD": {"asd": "123"}
}), format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Signup.objects.count(), 2)
+16 -5
View File
@@ -1,19 +1,24 @@
"""Webapp views."""
import jwt
import json
from django.utils import timezone
from dealer.git import git
from django.conf import settings
from django.contrib.auth import authenticate
from django.http import HttpResponseBadRequest, HttpResponse
from django.http import HttpResponseBadRequest, HttpResponse, JsonResponse
from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
from django_filters import rest_framework as filters
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import permissions, routers, viewsets
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework.reverse import reverse
from jsonschema import validate
from jsonschema.exceptions import ValidationError
import logging
from webapp.models import Event, SignupForm, Signup, TemplateQuestion, Feed, Committee, Occupation, Tag
from webapp.serializers import (EventSerializer, SignupFormSerializer, SignupSerializer,
@@ -78,13 +83,19 @@ class SignupViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
try:
form = SignupForm.objects.get(id=request.data["signupForm_id"])
id = request.data["signupForm_id"]
answer = request.data["answer"]
form = SignupForm.objects.get(id=id)
if (form.visible):
# Throws ValidationError if not valid
validate(instance=answer, schema=form.schema)
return super().create(request, *args, **kwargs)
except:
return HttpResponseBadRequest()
except ValidationError as inst:
return JsonResponse(status=400, data={"error": inst.message})
except ObjectDoesNotExist:
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
else:
return HttpResponseBadRequest()
return JsonResponse(status=404, data={"error": f"SignupForm {id} not found"})
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)