Skip to content

Commit

Permalink
Merge pull request #1179 from ScilifelabDataCentre/dev
Browse files Browse the repository at this point in the history
Allow more characters in Project Description
  • Loading branch information
i-oden authored May 19, 2022
2 parents 897afcc + 51cdc25 commit ea16a87
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe
- Bug: Do not remove the bucket when emptying the project ([#1172](https://github.com/ScilifelabDataCentre/dds_web/pull/1172))
- New `add-missing-buckets` argument option to the `lost-files` flask command ([#1174](https://github.com/ScilifelabDataCentre/dds_web/pull/1174))
- Bug: Corrected `lost-files` logic and message ([#1176](https://github.com/ScilifelabDataCentre/dds_web/pull/1176))

## Sprint (2022-05-18 - 2022-06-01)

- Allow all characters but unicode (e.g. emojis) in project description ([#1178](https://github.com/ScilifelabDataCentre/dds_web/pull/1178))
2 changes: 1 addition & 1 deletion dds_web/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ def post(self):
try:
new_project = project_schemas.CreateProjectSchema().load(p_info)
db.session.add(new_project)
except sqlalchemy.exc.OperationalError as err:
except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err:
raise DatabaseError(message=str(err), alt_message="Unexpected database error.")

if not new_project:
Expand Down
13 changes: 3 additions & 10 deletions dds_web/api/schemas/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ class Meta:
description = marshmallow.fields.String(
required=True,
allow_none=False,
validate=marshmallow.validate.Length(min=1),
validate=marshmallow.validate.And(
marshmallow.validate.Length(min=1), dds_web.utils.contains_unicode_emojis
),
error_messages={
"required": {"message": "A project description is required."},
"null": {"message": "A project description is required."},
Expand Down Expand Up @@ -123,15 +125,6 @@ def validate_all_fields(self, data, **kwargs):
):
raise marshmallow.ValidationError("Missing fields!")

@marshmallow.validates("description")
def validate_description(self, value):
"""Verify that description only has words, spaces and . / ,."""
disallowed = re.findall(r"[^(\w\s.,)]+", value)
if disallowed:
raise marshmallow.ValidationError(
message="The description can only contain letters, spaces, period and commas."
)

def generate_bucketname(self, public_id, created_time):
"""Create bucket name for the given project."""
return "{pid}-{tstamp}-{rstring}".format(
Expand Down
24 changes: 24 additions & 0 deletions dds_web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ def contains_disallowed_characters(indata):
)


def contains_unicode_emojis(indata):
"""Find unicode emojis in string - cause SQLAlchemyErrors."""
# Ref: https://gist.github.com/Alex-Just/e86110836f3f93fe7932290526529cd1#gistcomment-3208085
# Ref: https://en.wikipedia.org/wiki/Unicode_block
EMOJI_PATTERN = re.compile(
"(["
"\U0001F1E0-\U0001F1FF" # flags (iOS)
"\U0001F300-\U0001F5FF" # symbols & pictographs
"\U0001F600-\U0001F64F" # emoticons
"\U0001F680-\U0001F6FF" # transport & map symbols
"\U0001F700-\U0001F77F" # alchemical symbols
"\U0001F780-\U0001F7FF" # Geometric Shapes Extended
"\U0001F800-\U0001F8FF" # Supplemental Arrows-C
"\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs
"\U0001FA00-\U0001FA6F" # Chess Symbols
"\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A
"\U00002702-\U000027B0" # Dingbats
"])"
)
emojis = re.findall(EMOJI_PATTERN, indata)
if emojis:
raise marshmallow.ValidationError(f"This input is not allowed: {''.join(emojis)}")


def email_not_taken(indata):
"""Validator - verify that email is not taken.
Expand Down
59 changes: 59 additions & 0 deletions tests/test_project_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import unittest
import time
import os
from urllib import response

# Installed
import pytest
Expand Down Expand Up @@ -690,3 +691,61 @@ def test_create_project_with_unsuitable_roles(client, boto3_session):
assert response.json and response.json.get("user_addition_statuses")
for x in response.json.get("user_addition_statuses"):
assert "User Role should be either 'Project Owner' or 'Researcher'" in x


def test_create_project_valid_characters(client, boto3_session):
"""Create a project with no unicode."""
# Project info with valid characters
proj_data_val_chars = proj_data.copy()
proj_data_val_chars["description"] = "A longer project description !#¤%&/()=?¡@£$€¥{[]}\\"

create_unit_admins(num_admins=2)

current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count()
assert current_unit_admins == 3

response = client.post(
tests.DDSEndpoint.PROJECT_CREATE,
headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client),
json=proj_data_val_chars,
)
assert response.status_code == http.HTTPStatus.OK

new_project = (
db.session.query(models.Project)
.filter(models.Project.description == proj_data_val_chars["description"])
.first()
)
assert new_project


def test_create_project_invalid_characters(client, boto3_session):
"""Create a project with unicode characters."""
# Project info with invalid characters
proj_data_inval_chars = proj_data.copy()
proj_data_inval_chars["description"] = "A longer project description \U0001F300 \U0001F601"

create_unit_admins(num_admins=2)

current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count()
assert current_unit_admins == 3

response = client.post(
tests.DDSEndpoint.PROJECT_CREATE,
headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client),
json=proj_data_inval_chars,
)
assert response.status_code == http.HTTPStatus.BAD_REQUEST
assert (
response.json
and response.json.get("description")
and isinstance(response.json.get("description"), list)
)
assert response.json["description"][0] == "This input is not allowed: \U0001F300\U0001F601"

new_project = (
db.session.query(models.Project)
.filter(models.Project.description == proj_data_inval_chars["description"])
.first()
)
assert not new_project

0 comments on commit ea16a87

Please sign in to comment.