66# The full license is in the file LICENSE, distributed with this software.
77# -----------------------------------------------------------------------------
88
9+ import re
10+
911from tornado .web import authenticated , HTTPError
1012from wtforms import Form , StringField , BooleanField , validators
13+ from wtforms .validators import ValidationError
1114
1215from qiita_pet .handlers .base_handlers import BaseHandler
1316from qiita_pet .handlers .api_proxy import user_jobs_get_req
2124
2225
2326class UserProfile (Form ):
27+ def validate_general (value : str , infomsg : str , url_prefix : str ):
28+ """Validate basic user inputs, i.e. check for leading/trailing
29+ whitespaces and leading URL prefix, like http://scholar.google.com/
30+
31+ Parameters
32+ ----------
33+ value : str
34+ The WTform user input string.
35+ infomsg : str
36+ An error message to inform the user how to extract the correct
37+ value.
38+ url_prefix : str
39+ The URL prefix of the social network
40+
41+ Returns
42+ -------
43+ None in case of empty input, otherwise the input value
44+
45+ Raises
46+ ------
47+ ValidationError if
48+ a) input has leading or trailing whitespaces
49+ b) input starts with the given url_prefix
50+ """
51+ if (value is None ) or (value == "" ):
52+ # nothing to complain, as input is empty
53+ return None
54+
55+ if value != value .strip ():
56+ raise ValidationError (
57+ 'Please remove all leading and trailing whitespaces from your '
58+ 'input.<br/>%s' % infomsg )
59+
60+ if len (url_prefix ) > 0 :
61+ isPrefix = re .search ("^%s" % url_prefix , value )
62+ if isPrefix is not None :
63+ raise ValidationError (
64+ 'Please remove the "%s" part from your input.<br/>%s' % (
65+ isPrefix [0 ], infomsg ))
66+
67+ # if there is still no error raised, we return the actual value of the
68+ # user input
69+ return value
70+
71+ def validator_orcid_id (form : Form , field : StringField ):
72+ """A WTForm validator to check if user input follows ORCID syntax.
73+
74+ Parameters
75+ ----------
76+ form : wtforms.Form
77+ The WTform form enclosing the user input field.
78+ field : wtforms.StringField
79+ The WTform user input field.
80+
81+ Returns
82+ -------
83+ True, if user input is OK.
84+
85+ Raises
86+ ------
87+ ValidationError if user input is not valid
88+ """
89+ infomsg = ('Enter only your 16 digit numerical ORCID identifier, where'
90+ ' every four digits are separated with a dash "-". An '
91+ 'example is: 0000-0002-0975-9019' )
92+ value = UserProfile .validate_general (
93+ field .data , infomsg , 'https://orcid.org' )
94+ if value is None :
95+ return True
96+
97+ if re .search (r"^\d{4}-\d{4}-\d{4}-\d{4}$" , value ) is None :
98+ raise ValidationError (
99+ "Your input does not follow the required format.<br/>%s" %
100+ infomsg )
101+
102+ def validator_gscholar_id (form , field ):
103+ """A WTForm validator to check if user input follows google scholar ID
104+ syntax.
105+
106+ Parameters
107+ ----------
108+ form : wtforms.Form
109+ The WTform form enclosing the user input field.
110+ field : wtforms.StringField
111+ The WTform user input field.
112+
113+ Returns
114+ -------
115+ True, if user input is OK.
116+
117+ Raises
118+ ------
119+ ValidationError if user input is not valid
120+ """
121+ infomsg = ('To retrieve your google scholar ID, surf to your profile '
122+ 'and copy the URL in your browser. It might read like '
123+ 'https://scholar.google.com/citations?user=_e3QL94AAAAJ&'
124+ 'hl=en<br/>Ignore everything left of the "?". The right '
125+ 'part is a set of key=value pairs, separated by "&" '
126+ 'characters. Find the key "user=", the right part up to '
127+ 'the next "&" is your google scholar ID, in the example: '
128+ '"_e3QL94AAAAJ"' )
129+ # we need a regex here, since we don't know the TLD the user is
130+ # presenting to us
131+ value = UserProfile .validate_general (
132+ field .data , infomsg , r'https://scholar.google.\w{1,3}/citations\?' )
133+ if value is None :
134+ return True
135+
136+ if '&' in value :
137+ raise ValidationError (
138+ 'Your input contains multiple key=value pairs (we found at '
139+ 'least one "&" character).<br/>%s' % infomsg )
140+ if 'user=' in value :
141+ raise ValidationError (
142+ 'Please remove the key "user" and the "=" character from '
143+ 'your input.<br/>%s' % infomsg )
144+ if value .startswith ('=' ):
145+ raise ValidationError (
146+ 'Please remove leading "=" characters from your input.'
147+ '<br/>%s' % infomsg )
148+
149+ def validator_rgate_id (form , field ):
150+ """A WTForm validator to check if user input follows ResearchGate
151+ user names.
152+
153+ Parameters
154+ ----------
155+ form : wtforms.Form
156+ The WTform form enclosing the user input field.
157+ field : wtforms.StringField
158+ The WTform user input field.
159+
160+ Returns
161+ -------
162+ True, if user input is OK.
163+
164+ Raises
165+ ------
166+ ValidationError if user input is not valid
167+ """
168+ infomsg = ('To retrieve your ResearchGate ID, surf to your profile '
169+ 'and copy the URL in your browser. It might read like '
170+ 'https://www.researchgate.net/profile/Rob-Knight<br/>'
171+ 'Your ID is the part right of the last "/", in the example:'
172+ ' "Rob-Knight"' )
173+ value = UserProfile .validate_general (
174+ field .data , infomsg , 'https://www.researchgate.net/profile/' )
175+ if value is None :
176+ return True
177+
24178 name = StringField ("Name" , [validators .required ()])
25179 affiliation = StringField ("Affiliation" )
26180 address = StringField ("Address" )
27181 phone = StringField ("Phone" )
28182 receive_processing_job_emails = BooleanField (
29183 "Receive Processing Job Emails?" )
30184
185+ social_orcid = StringField (
186+ "ORCID" , [validator_orcid_id ], description = "0000-0002-0975-9019" )
187+ social_googlescholar = StringField (
188+ "Google Scholar" , [validator_gscholar_id ], description = "_e3QL94AAAAJ" )
189+ social_researchgate = StringField (
190+ "ResearchGate" , [validator_rgate_id ], description = "Rob-Knight" )
191+
31192
32193class UserProfileHandler (BaseHandler ):
33194 """Displays user profile page and handles profile updates"""
@@ -44,11 +205,11 @@ def post(self):
44205 msg = ""
45206 user = self .current_user
46207 action = self .get_argument ("action" )
208+ form_data = UserProfile ()
47209 if action == "profile" :
48- # tuple of colmns available for profile
210+ # tuple of columns available for profile
49211 # FORM INPUT NAMES MUST MATCH DB COLUMN NAMES
50212 not_str_fields = ('receive_processing_job_emails' )
51- form_data = UserProfile ()
52213 form_data .process (data = self .request .arguments )
53214 profile = {name : data [0 ].decode ('ascii' )
54215 if name not in not_str_fields else
@@ -59,16 +220,19 @@ def post(self):
59220 for field in form_data :
60221 if field .name not in not_str_fields :
61222 field .data = field .data [0 ].decode ('ascii' )
62- try :
63- user .info = profile
64- msg = "Profile updated successfully"
65- except Exception as e :
66- msg = "ERROR: profile could not be updated"
67- LogEntry .create ('Runtime' , "Cound not update profile: %s" %
68- str (e ), info = {'User' : user .id })
223+ if form_data .validate () is False :
224+ msg = ("ERROR: profile could not be updated"
225+ " as some of your above inputs must be corrected." )
226+ else :
227+ try :
228+ user .info = profile
229+ msg = "Profile updated successfully"
230+ except Exception as e :
231+ msg = "ERROR: profile could not be updated"
232+ LogEntry .create ('Runtime' , "Cound not update profile: %s" %
233+ str (e ), info = {'User' : user .id })
69234
70235 elif action == "password" :
71- form_data = UserProfile ()
72236 form_data .process (data = user .info )
73237 oldpass = self .get_argument ("oldpass" )
74238 newpass = self .get_argument ("newpass" )
0 commit comments