Merge branch 'feature/signup-validation' into 'develop'
Feature/signup validation See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!18
This commit is contained in:
+16
-4
@@ -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
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user