Skip to content

Commit

Permalink
add changes from master
Browse files Browse the repository at this point in the history
  • Loading branch information
ciaracian committed Sep 10, 2020
2 parents 1dfd539 + a63cb60 commit 5e521b5
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 3 deletions.
3 changes: 2 additions & 1 deletion pybossa/forms/dynamic_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from flask_wtf import FlaskForm as Form
from wtforms import SelectField, validators, TextField, BooleanField
from pybossa.forms.fields.select_two import Select2Field
from validator import AmpPvfValidator

import wtforms

Expand Down Expand Up @@ -35,7 +36,7 @@ def __init__(self, *args, **kwargs):
lazy_gettext('Opt in to store annotations on Annotation Management Platform'))
ProjectFormExtraInputs.amp_pvf = TextField(
lazy_gettext('Annotation Store PVF'),
[validators.Regexp('^([A-Z]{3,4}\s\d+)?$')]) #[validators.Regexp('^$|[A-Z]\s\d')])
[AmpPvfValidator()])

generate_form = ProjectFormExtraInputs(form_data, obj=obj)
if data_access_levels and not form_data:
Expand Down
14 changes: 14 additions & 0 deletions pybossa/forms/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,17 @@ def __init__(self, message="Date cannot be greater than today's date."):
def __call__(self, form, field):
if field.data > date.today():
raise ValidationError(self.message)

class AmpPvfValidator(object):
"""Apply correct pvf as per data access level."""
def __init__(self, message=None):
if not message:
message = lazy_gettext("Invalid PVF.")
self.message = message
self.pvf_format = re.compile('^([A-Z]{3,4}\s\d+)?$')

def __call__(self, form, field):
amp_pvf = form.amp_pvf.data
amp_store = form.amp_store.data
if amp_store and not(amp_pvf and self.pvf_format.match(amp_pvf)):
raise ValidationError("Invalid PVF format. Must contain <PVF name> <PVF val>.")
21 changes: 21 additions & 0 deletions pybossa/repositories/project_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@
import pandas.io.sql as sqlio
import pandas as pd
from flask import current_app
import re


class ProjectRepository(Repository):

pvf_format = re.compile('^([A-Z]{3,4}\s\d+)?$')

# Methods for Project objects
def get(self, id):
return self.db.session.query(Project).get(id)
Expand All @@ -60,6 +63,7 @@ def save(self, project):
self._creator_is_owner(project)
self._verify_has_password(project)
self._verify_data_classification(project)
self._verify_annotation_config(project)
self._verify_required_fields(project)
self._verify_product_subproduct(project)
try:
Expand All @@ -75,6 +79,7 @@ def update(self, project):
self._creator_is_owner(project)
self._verify_has_password(project)
self._verify_data_classification(project)
self._verify_annotation_config(project)
try:
self.db.session.merge(project)
self.db.session.commit()
Expand Down Expand Up @@ -181,6 +186,22 @@ def _verify_data_classification(self, project):
if data_access:
project.info['data_access'] = [data_access]

def _verify_annotation_config(self, project):
data_access = project.info.get('data_access', [])[0]
valid_data_access = current_app.config.get('VALID_ACCESS_LEVELS', [])
if data_access not in valid_data_access:
raise BadRequest('Project must have valid data classification')

# L3, L4 projects to have default opted in for amp storage w/ pvf gig 200
amp_store = project.info.get('annotation_config', {}).get('amp_store', False)
if amp_store and data_access in ['L3', 'L4']:
project.info['annotation_config']['amp_pvf'] = 'GIG 200'

# L1, L2 projects with opted in for amp storage to have pvf set
amp_pvf = project.info.get('annotation_config', {}).get('amp_pvf')
if amp_store and not(amp_pvf and ProjectRepository.pvf_format.match(amp_pvf)):
raise BadRequest('Invalid PVF format. Must contain <PVF name> <PVF val>.')

def _verify_product_subproduct(self, project):
products_subproducts = current_app.config.get('PRODUCTS_SUBPRODUCTS', {})
product = project.info.get("product")
Expand Down
2 changes: 2 additions & 0 deletions settings_test.py.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ WIZARD_STEPS = OrderedDict([
])
DATA_CLASSIFICATION = [
('L1 - internal', False),
('L1 - internal valid', True),
('L2 - propriertary', False),
('L2 - propriertary valid', True),
('L3 - community', True),
('L4 - public', True)
]
Expand Down
56 changes: 55 additions & 1 deletion test/test_api/test_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from mock import patch, call, MagicMock
from default import db, with_context, with_context_settings, flask_app
from nose.tools import assert_equal, assert_raises
import copy
from test_api import TestAPI
from helper.gig_helper import make_subadmin, make_admin

Expand Down Expand Up @@ -1838,4 +1839,57 @@ def test_project_post_from_sync(self):
res = self.app.post('/api/project', headers=headers,
data=json.dumps(data))
res_data = json.loads(res.data)
assert res.status_code == 200
assert res.status_code == 200

@with_context
def test_project_post_amp_pvf(self):
user = UserFactory.create()
CategoryFactory.create()
headers = [('Authorization', user.api_key)]
# post empty pvf for L1 to result into error
data = dict(
name='got',
short_name='gameofthrones',
description='winter is coming',
password = "dragonglass",
info=dict(
data_classification=dict(input_data="L1 - internal valid", output_data="L3 - community"),
kpi=0.5,
product="abc",
subproduct="def",
annotation_config=dict(amp_store=True, amp_pvf='')
))
res = self.app.post('/api/project', headers=headers,
data=json.dumps(data))
res_data = json.loads(res.data)
assert res.status_code == 400
error_msg = res_data['exception_msg']
assert error_msg == "Invalid PVF format. Must contain <PVF name> <PVF val>.", error_msg

# post bad pvf for L1 to result into failure
data["info"]["annotation_config"]["amp_pvf"] = "xxxx yyyy zzzz"
res = self.app.post('/api/project', headers=headers,
data=json.dumps(data))
res_data = json.loads(res.data)
assert res.status_code == 400
error_msg = res_data['exception_msg']
assert error_msg == "Invalid PVF format. Must contain <PVF name> <PVF val>.", error_msg

# post valid pvf for L1 to result into success
data["info"]["annotation_config"]["amp_pvf"] = "XXX 123"
res = self.app.post('/api/project', headers=headers,
data=json.dumps(data))
res_data = json.loads(res.data)
assert res.status_code == 200, "POST project api should be successful"
assert res_data["info"]["annotation_config"]["amp_pvf"] == "XXX 123", "Project PVF should be set to XXX 123"

# project w/ public data access and optin checked to have "GIG 200" pvf configured
data2 = copy.deepcopy(data)
data2["name"] = "got2"
data2["short_name"] = "got s2"
data2["info"]["data_classification"]["input_data"] = "L3 - community"
res = self.app.post('/api/project', headers=headers,
data=json.dumps(data2))
res_data = json.loads(res.data)
assert res.status_code == 200, "POST project api should be successful"
assert res_data["info"]["annotation_config"]["amp_pvf"] == "GIG 200", "Project PVF should be set to GIG 200 for public data."
32 changes: 31 additions & 1 deletion test/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
from pybossa.forms.forms import (RegisterForm, LoginForm, EMAIL_MAX_LENGTH,
USER_NAME_MAX_LENGTH, USER_FULLNAME_MAX_LENGTH, BulkTaskLocalCSVImportForm,
RegisterFormWithUserPrefMetadata, UserPrefMetadataForm,
ProjectReportForm)
ProjectReportForm, ProjectForm)
from pybossa.forms import validator
from pybossa.repositories import UserRepository
from factories import UserFactory
from mock import patch, MagicMock
from werkzeug.datastructures import MultiDict
import six
from pybossa.forms.dynamic_forms import dynamic_project_form

user_repo = UserRepository(db)

Expand Down Expand Up @@ -138,6 +139,35 @@ def test_today_passes_not_in_future(self):
u = validator.NotInFutureValidator()
u(form, form.end_date)

@with_context
@raises(ValidationError)
def test_amp_pvf_validator_raises_error(self):
"""Test AmpPvfValidator raise exception with invalid pvf value """
data_classes = [(data_class, data_class, {} if enabled else dict(disabled='disabled'))
for data_class, enabled in current_app.config.get('DATA_CLASSIFICATION', [('', False)])
]
data_access_levels = dict(valid_access_levels=['L1', 'L2', 'L3', 'L4'])
form_data = dict()
prodsubprod = dict(abc='def')
form = dynamic_project_form(ProjectForm, form_data, data_access_levels, prodsubprod, data_classes)
form.amp_pvf.data = 'xxx yyy'
u = validator.AmpPvfValidator()
u.__call__(form, form.amp_pvf)

@with_context
def test_amp_pvf_validator_pass(self):
"""Test AmpPvfValidator pass with correct format pvf value """
data_classes = [(data_class, data_class, {} if enabled else dict(disabled='disabled'))
for data_class, enabled in current_app.config.get('DATA_CLASSIFICATION', [('', False)])
]
data_access_levels = dict(valid_access_levels=['L1', 'L2', 'L3', 'L4'])
form_data = dict()
prodsubprod = dict(abc='def')
form = dynamic_project_form(ProjectForm, form_data, data_access_levels, prodsubprod, data_classes)
form.amp_pvf.data = 'XYZ 123'
u = validator.AmpPvfValidator()
u.__call__(form, form.amp_pvf)


class TestRegisterForm(Test):

Expand Down

0 comments on commit 5e521b5

Please sign in to comment.