diff --git a/qiita_db/support_files/populate_test_db.sql b/qiita_db/support_files/populate_test_db.sql
index 12035c788..46c0aaed7 100644
--- a/qiita_db/support_files/populate_test_db.sql
+++ b/qiita_db/support_files/populate_test_db.sql
@@ -50,7 +50,7 @@ INSERT INTO qiita.user_level VALUES (7, 'wet-lab admin', 'Can access the private
-- Data for Name: qiita_user; Type: TABLE DATA; Schema: qiita; Owner: antoniog
--
-INSERT INTO qiita.qiita_user VALUES ('test@foo.bar', 4, '$2a$12$gnUi8Qg.0tvW243v889BhOBhWLIHyIJjjgaG6dxuRJkUM8nXG9Efe', 'Dude', 'Nowhere University', '123 fake st, Apt 0, Faketown, CO 80302', '111-222-3344', NULL, NULL, NULL, false);
+INSERT INTO qiita.qiita_user VALUES ('test@foo.bar', 4, '$2a$12$gnUi8Qg.0tvW243v889BhOBhWLIHyIJjjgaG6dxuRJkUM8nXG9Efe', 'Dude', 'Nowhere University', '123 fake st, Apt 0, Faketown, CO 80302', '111-222-3344', NULL, NULL, NULL, false, '0000-0002-0975-9019', 'Rob-Knight', '_e3QL94AAAAJ');
INSERT INTO qiita.qiita_user VALUES ('shared@foo.bar', 4, '$2a$12$gnUi8Qg.0tvW243v889BhOBhWLIHyIJjjgaG6dxuRJkUM8nXG9Efe', 'Shared', 'Nowhere University', '123 fake st, Apt 0, Faketown, CO 80302', '111-222-3344', NULL, NULL, NULL, false);
INSERT INTO qiita.qiita_user VALUES ('admin@foo.bar', 1, '$2a$12$gnUi8Qg.0tvW243v889BhOBhWLIHyIJjjgaG6dxuRJkUM8nXG9Efe', 'Admin', 'Owner University', '312 noname st, Apt K, Nonexistantown, CO 80302', '222-444-6789', NULL, NULL, NULL, false);
INSERT INTO qiita.qiita_user VALUES ('demo@microbio.me', 4, '$2a$12$gnUi8Qg.0tvW243v889BhOBhWLIHyIJjjgaG6dxuRJkUM8nXG9Efe', 'Demo', 'Qiita Dev', '1345 Colorado Avenue', '303-492-1984', NULL, NULL, NULL, false);
diff --git a/qiita_db/support_files/qiita-db-unpatched.sql b/qiita_db/support_files/qiita-db-unpatched.sql
index 1ce86de39..a61b4645d 100644
--- a/qiita_db/support_files/qiita-db-unpatched.sql
+++ b/qiita_db/support_files/qiita-db-unpatched.sql
@@ -1888,7 +1888,10 @@ CREATE TABLE qiita.qiita_user (
user_verify_code character varying,
pass_reset_code character varying,
pass_reset_timestamp timestamp without time zone,
- receive_processing_job_emails boolean DEFAULT false
+ receive_processing_job_emails boolean DEFAULT false,
+ social_orcid character varying DEFAULT NULL,
+ social_researchgate character varying DEFAULT NULL,
+ social_googlescholar character varying DEFAULT NULL
);
diff --git a/qiita_db/test/test_user.py b/qiita_db/test/test_user.py
index 2dced88d9..666746c36 100644
--- a/qiita_db/test/test_user.py
+++ b/qiita_db/test/test_user.py
@@ -72,7 +72,10 @@ def setUp(self):
'pass_reset_code': None,
'pass_reset_timestamp': None,
'user_verify_code': None,
- 'receive_processing_job_emails': True
+ 'receive_processing_job_emails': True,
+ 'social_orcid': None,
+ 'social_researchgate': None,
+ 'social_googlescholar': None
}
def tearDown(self):
@@ -125,7 +128,10 @@ def test_create_user(self):
'address': None,
'user_level_id': 5,
'receive_processing_job_emails': False,
- 'email': 'testcreateuser@test.bar'}
+ 'email': 'testcreateuser@test.bar',
+ 'social_orcid': None,
+ 'social_researchgate': None,
+ 'social_googlescholar': None}
self._check_correct_info(obs, exp)
# Make sure new system messages are linked to user
@@ -162,7 +168,10 @@ def test_create_user_info(self):
'user_verify_code': '',
'user_level_id': 5,
'receive_processing_job_emails': True,
- 'email': 'testcreateuserinfo@test.bar'}
+ 'email': 'testcreateuserinfo@test.bar',
+ 'social_orcid': None,
+ 'social_researchgate': None,
+ 'social_googlescholar': None}
self._check_correct_info(obs, exp)
def test_create_user_column_not_allowed(self):
@@ -229,7 +238,10 @@ def test_get_info(self):
'pass_reset_timestamp': None,
'user_verify_code': None,
'receive_processing_job_emails': False,
- 'phone': '222-444-6789'
+ 'phone': '222-444-6789',
+ 'social_orcid': None,
+ 'social_researchgate': None,
+ 'social_googlescholar': None
}
self.assertEqual(self.user.info, expinfo)
diff --git a/qiita_db/test/test_util.py b/qiita_db/test/test_util.py
index a8df7ed3c..26fff3004 100644
--- a/qiita_db/test/test_util.py
+++ b/qiita_db/test/test_util.py
@@ -92,7 +92,8 @@ def test_get_table_cols(self):
obs = qdb.util.get_table_cols("qiita_user")
exp = {"email", "user_level_id", "password", "name", "affiliation",
"address", "phone", "user_verify_code", "pass_reset_code",
- "pass_reset_timestamp", "receive_processing_job_emails"}
+ "pass_reset_timestamp", "receive_processing_job_emails",
+ "social_orcid", "social_researchgate", "social_googlescholar"}
self.assertEqual(set(obs), exp)
def test_exists_table(self):
diff --git a/qiita_pet/handlers/user_handlers.py b/qiita_pet/handlers/user_handlers.py
index 116c69432..d75316a80 100644
--- a/qiita_pet/handlers/user_handlers.py
+++ b/qiita_pet/handlers/user_handlers.py
@@ -6,8 +6,11 @@
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
+import re
+
from tornado.web import authenticated, HTTPError
from wtforms import Form, StringField, BooleanField, validators
+from wtforms.validators import ValidationError
from qiita_pet.handlers.base_handlers import BaseHandler
from qiita_pet.handlers.api_proxy import user_jobs_get_req
@@ -21,6 +24,157 @@
class UserProfile(Form):
+ def validate_general(value: str, infomsg: str, url_prefix: str):
+ """Validate basic user inputs, i.e. check for leading/trailing
+ whitespaces and leading URL prefix, like http://scholar.google.com/
+
+ Parameters
+ ----------
+ value : str
+ The WTform user input string.
+ infomsg : str
+ An error message to inform the user how to extract the correct
+ value.
+ url_prefix : str
+ The URL prefix of the social network
+
+ Returns
+ -------
+ None in case of empty input, otherwise the input value
+
+ Raises
+ ------
+ ValidationError if
+ a) input has leading or trailing whitespaces
+ b) input starts with the given url_prefix
+ """
+ if (value is None) or (value == ""):
+ # nothing to complain, as input is empty
+ return None
+
+ if value != value.strip():
+ raise ValidationError(
+ 'Please remove all leading and trailing whitespaces from your '
+ 'input.
%s' % infomsg)
+
+ if len(url_prefix) > 0:
+ isPrefix = re.search("^%s" % url_prefix, value)
+ if isPrefix is not None:
+ raise ValidationError(
+ 'Please remove the "%s" part from your input.
%s' % (
+ isPrefix[0], infomsg))
+
+ # if there is still no error raised, we return the actual value of the
+ # user input
+ return value
+
+ def validator_orcid_id(form: Form, field: StringField):
+ """A WTForm validator to check if user input follows ORCID syntax.
+
+ Parameters
+ ----------
+ form : wtforms.Form
+ The WTform form enclosing the user input field.
+ field : wtforms.StringField
+ The WTform user input field.
+
+ Returns
+ -------
+ True, if user input is OK.
+
+ Raises
+ ------
+ ValidationError if user input is not valid
+ """
+ infomsg = ('Enter only your 16 digit numerical ORCID identifier, where'
+ ' every four digits are separated with a dash "-". An '
+ 'example is: 0000-0002-0975-9019')
+ value = UserProfile.validate_general(
+ field.data, infomsg, 'https://orcid.org')
+ if value is None:
+ return True
+
+ if re.search(r"^\d{4}-\d{4}-\d{4}-\d{4}$", value) is None:
+ raise ValidationError(
+ "Your input does not follow the required format.
%s" %
+ infomsg)
+
+ def validator_gscholar_id(form, field):
+ """A WTForm validator to check if user input follows google scholar ID
+ syntax.
+
+ Parameters
+ ----------
+ form : wtforms.Form
+ The WTform form enclosing the user input field.
+ field : wtforms.StringField
+ The WTform user input field.
+
+ Returns
+ -------
+ True, if user input is OK.
+
+ Raises
+ ------
+ ValidationError if user input is not valid
+ """
+ infomsg = ('To retrieve your google scholar ID, surf to your profile '
+ 'and copy the URL in your browser. It might read like '
+ 'https://scholar.google.com/citations?user=_e3QL94AAAAJ&'
+ 'hl=en
Ignore everything left of the "?". The right '
+ 'part is a set of key=value pairs, separated by "&" '
+ 'characters. Find the key "user=", the right part up to '
+ 'the next "&" is your google scholar ID, in the example: '
+ '"_e3QL94AAAAJ"')
+ # we need a regex here, since we don't know the TLD the user is
+ # presenting to us
+ value = UserProfile.validate_general(
+ field.data, infomsg, r'https://scholar.google.\w{1,3}/citations\?')
+ if value is None:
+ return True
+
+ if '&' in value:
+ raise ValidationError(
+ 'Your input contains multiple key=value pairs (we found at '
+ 'least one "&" character).
%s' % infomsg)
+ if 'user=' in value:
+ raise ValidationError(
+ 'Please remove the key "user" and the "=" character from '
+ 'your input.
%s' % infomsg)
+ if value.startswith('='):
+ raise ValidationError(
+ 'Please remove leading "=" characters from your input.'
+ '
%s' % infomsg)
+
+ def validator_rgate_id(form, field):
+ """A WTForm validator to check if user input follows ResearchGate
+ user names.
+
+ Parameters
+ ----------
+ form : wtforms.Form
+ The WTform form enclosing the user input field.
+ field : wtforms.StringField
+ The WTform user input field.
+
+ Returns
+ -------
+ True, if user input is OK.
+
+ Raises
+ ------
+ ValidationError if user input is not valid
+ """
+ infomsg = ('To retrieve your ResearchGate ID, surf to your profile '
+ 'and copy the URL in your browser. It might read like '
+ 'https://www.researchgate.net/profile/Rob-Knight
'
+ 'Your ID is the part right of the last "/", in the example:'
+ ' "Rob-Knight"')
+ value = UserProfile.validate_general(
+ field.data, infomsg, 'https://www.researchgate.net/profile/')
+ if value is None:
+ return True
+
name = StringField("Name", [validators.required()])
affiliation = StringField("Affiliation")
address = StringField("Address")
@@ -28,6 +182,13 @@ class UserProfile(Form):
receive_processing_job_emails = BooleanField(
"Receive Processing Job Emails?")
+ social_orcid = StringField(
+ "ORCID", [validator_orcid_id], description="0000-0002-0975-9019")
+ social_googlescholar = StringField(
+ "Google Scholar", [validator_gscholar_id], description="_e3QL94AAAAJ")
+ social_researchgate = StringField(
+ "ResearchGate", [validator_rgate_id], description="Rob-Knight")
+
class UserProfileHandler(BaseHandler):
"""Displays user profile page and handles profile updates"""
@@ -44,11 +205,11 @@ def post(self):
msg = ""
user = self.current_user
action = self.get_argument("action")
+ form_data = UserProfile()
if action == "profile":
- # tuple of colmns available for profile
+ # tuple of columns available for profile
# FORM INPUT NAMES MUST MATCH DB COLUMN NAMES
not_str_fields = ('receive_processing_job_emails')
- form_data = UserProfile()
form_data.process(data=self.request.arguments)
profile = {name: data[0].decode('ascii')
if name not in not_str_fields else
@@ -59,16 +220,19 @@ def post(self):
for field in form_data:
if field.name not in not_str_fields:
field.data = field.data[0].decode('ascii')
- try:
- user.info = profile
- msg = "Profile updated successfully"
- except Exception as e:
- msg = "ERROR: profile could not be updated"
- LogEntry.create('Runtime', "Cound not update profile: %s" %
- str(e), info={'User': user.id})
+ if form_data.validate() is False:
+ msg = ("ERROR: profile could not be updated"
+ " as some of your above inputs must be corrected.")
+ else:
+ try:
+ user.info = profile
+ msg = "Profile updated successfully"
+ except Exception as e:
+ msg = "ERROR: profile could not be updated"
+ LogEntry.create('Runtime', "Cound not update profile: %s" %
+ str(e), info={'User': user.id})
elif action == "password":
- form_data = UserProfile()
form_data.process(data=user.info)
oldpass = self.get_argument("oldpass")
newpass = self.get_argument("newpass")
diff --git a/qiita_pet/static/img/logo_social_googlescholar.png b/qiita_pet/static/img/logo_social_googlescholar.png
new file mode 100644
index 000000000..e9bd65a3a
Binary files /dev/null and b/qiita_pet/static/img/logo_social_googlescholar.png differ
diff --git a/qiita_pet/static/img/logo_social_orcid.png b/qiita_pet/static/img/logo_social_orcid.png
new file mode 100644
index 000000000..5f4a5352a
Binary files /dev/null and b/qiita_pet/static/img/logo_social_orcid.png differ
diff --git a/qiita_pet/static/img/logo_social_researchgate.png b/qiita_pet/static/img/logo_social_researchgate.png
new file mode 100644
index 000000000..23c283b5d
Binary files /dev/null and b/qiita_pet/static/img/logo_social_researchgate.png differ
diff --git a/qiita_pet/templates/user_profile.html b/qiita_pet/templates/user_profile.html
index b83efffad..66da290d9 100644
--- a/qiita_pet/templates/user_profile.html
+++ b/qiita_pet/templates/user_profile.html
@@ -14,9 +14,17 @@