Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pybossa/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from .api_base import APIBase
from pybossa.model.project import Project
from pybossa.cache.categories import get_all as get_categories
from pybossa.util import is_reserved_name, description_from_long_description, validate_ownership_id
from pybossa.util import is_reserved_name, description_from_long_description, valid_ownership_id
from pybossa.core import auditlog_repo, result_repo, http_signer
from pybossa.auditlogger import AuditLogger
from pybossa.data_access import ensure_user_assignment_to_project, set_default_amp_store
Expand Down Expand Up @@ -149,7 +149,9 @@ def _validate_instance(self, project):
msg = "Project short_name is not valid, as it's used by the system."
raise ValueError(msg)
ensure_user_assignment_to_project(project)
validate_ownership_id(project.info.get('ownership_id'))
if not valid_ownership_id(project.info.get('ownership_id')):
ownership_id_title = current_app.config.get('OWNERSHIP_ID_TITLE', 'Ownership ID')
raise ValueError(f"{ownership_id_title} must be numeric and less than 20 characters.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems inconsistent behavior with the error is getting reported via api above and from fronted here as the error messages are different; line above missing Got :<input-number>. It would be nice to have a single place from where a consistent error message is reported back both for api or frontend. This can be achieved via existing similar implementations here


def _log_changes(self, old_project, new_project):
auditlogger.add_log_entry(old_project, new_project, current_user)
Expand Down
7 changes: 6 additions & 1 deletion pybossa/forms/dynamic_forms.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from flask import current_app
from flask_babel import lazy_gettext
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
from .validator import AmpPvfValidator, OwnershipIdValidator

import wtforms

Expand Down Expand Up @@ -38,6 +39,10 @@ def __init__(self, *args, **kwargs):
lazy_gettext('Annotation Store PVF'),
[AmpPvfValidator()])

ProjectFormExtraInputs.ownership_id = TextField(
lazy_gettext(current_app.config.get('OWNERSHIP_ID_TITLE', 'Ownership ID')),
[OwnershipIdValidator()])

generate_form = ProjectFormExtraInputs(form_data, obj=obj)
if data_access_levels and not form_data:
generate_form.amp_store.data = bool(not obj or obj.amp_store)
Expand Down
16 changes: 15 additions & 1 deletion pybossa/forms/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import re
import requests

from pybossa.util import is_reserved_name, check_password_strength
from pybossa.util import is_reserved_name, check_password_strength, valid_ownership_id
from pybossa.data_access import valid_user_type_based_data_access


Expand Down Expand Up @@ -254,3 +254,17 @@ def __call__(self, form, field):
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>.")


class OwnershipIdValidator(object):
"""Ensure Ownership ID is valid."""
def __init__(self, message=None):
self.ownership_id_title = current_app.config.get('OWNERSHIP_ID_TITLE', 'Ownership ID')
if not message:
message = lazy_gettext(f"Invalid {self.ownership_id_title}.")
self.message = message

def __call__(self, form, field):
o_id = form.ownership_id.data
if not valid_ownership_id(o_id):
raise ValidationError(f"{self.ownership_id_title} must be numeric and less than 20 characters. Got: {o_id}")
2 changes: 1 addition & 1 deletion pybossa/themes/default
8 changes: 4 additions & 4 deletions pybossa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,12 +373,12 @@ def datetime_filter(source, fmt):
return source.strftime(fmt)


def validate_ownership_id(o_id):
ownership_id_title = current_app.config.get('OWNERSHIP_ID_TITLE', 'Ownership ID')
def valid_ownership_id(o_id):
if o_id == None or len(o_id) == 0:
return
return True
if not (o_id.isnumeric() and len(o_id) <= 20):
raise ValueError(f"{ownership_id_title} must be numeric and less than 20 characters. Got: {o_id}")
return False
return True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Is it possible to pass numeric value to this function thereby causing isnumeric call to raise exception
  2. Can this function be refactored so that it returns True only when condition is satisfied and False otherwise
    ex
if not o_id or (isinstance(o_id, str) and o_id.isnumeric() and len(o_id) <= 20):
    return True
return False


class Pagination(object):

Expand Down
2 changes: 1 addition & 1 deletion pybossa/view/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
from copy import deepcopy
from pybossa.cache import delete_memoized
from sqlalchemy.orm.attributes import flag_modified
from pybossa.util import admin_or_project_owner, validate_ownership_id
from pybossa.util import admin_or_project_owner, valid_ownership_id
from pybossa.api.project import ProjectAPI

cors_headers = ['Content-Type', 'Authorization']
Expand Down
19 changes: 19 additions & 0 deletions test/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ def test_amp_pvf_validator_pass(self):
u = validator.AmpPvfValidator()
u.__call__(form, form.amp_pvf)

@with_context
@raises(ValidationError)
def test_ownership_id_validator_raises_error(self):
"""Test OwnershipIdValidator raise exception with invalid Ownership ID value """
form_data = dict()
form = dynamic_project_form(ProjectForm, form_data, [], {}, {})
form.ownership_id.data = 'xxx yyy'
u = validator.OwnershipIdValidator()
u.__call__(form, form.ownership_id)

@with_context
def test_ownership_id_validator_pass(self):
"""Test OwnershipIdValidator pass with correct format Ownership ID value """
form_data = dict()
form = dynamic_project_form(ProjectForm, form_data, [], {}, {})
form.ownership_id.data = '123123123'
u = validator.OwnershipIdValidator()
u.__call__(form, form.ownership_id)


class TestRegisterForm(Test):

Expand Down
10 changes: 5 additions & 5 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1930,19 +1930,19 @@ def test_map_locations_none(self):
assert mapped_locations['locations'] == expected_locations, (mapped_locations['locations'], expected_locations)

@with_context
def test_validate_ownership_id(self):
def test_valid_ownership_id(self):
# valid ownership_id
ownership_id = "1111"
util.validate_ownership_id(ownership_id)
assert util.valid_ownership_id(ownership_id) == True

# empty ownership_id
ownership_id = ""
util.validate_ownership_id(ownership_id)
assert util.valid_ownership_id(ownership_id) == True

# ownership_id too long (> 20 chars)
ownership_id = "123412341234123412341234"
assert_raises(ValueError, util.validate_ownership_id, ownership_id)
assert util.valid_ownership_id(ownership_id) == False

# ownership_id not numeric
ownership_id = "1234abcd1234"
assert_raises(ValueError, util.validate_ownership_id, ownership_id)
assert util.valid_ownership_id(ownership_id) == False