From 771dfebf18ced88d5e53eed31e3e70dee379f4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Mon, 6 Jun 2016 23:12:05 +0200 Subject: [PATCH] general: support prefilling with "verified" profile fields (#12366) --- tests/test_form_pages.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/test_prefill.py | 56 +++++++++++++++++++++++++++++++------------- wcs/fields.py | 13 ++++++----- wcs/forms/root.py | 40 +++++++++++++++++++++++-------- wcs/sql.py | 18 ++++++++++---- wcs/users.py | 5 ++++ 6 files changed, 156 insertions(+), 37 deletions(-) diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index fa90df1..1dfdae5 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -1387,6 +1387,20 @@ def test_form_page_string_prefill(pub): assert 'widget-prefilled' in resp.body assert not 'Value has been automatically prefilled.' in resp.body +def test_form_page_profile_prefill(pub): + user = create_user(pub) + formdef = create_formdef() + formdef.data_class().wipe() + formdef.fields = [fields.StringField(id='0', label='string', + prefill={'type': 'user', 'value': 'email'})] + formdef.store() + + resp = get_app(pub).get('/test/') + assert resp.forms[0]['f0'].value == '' + + resp = login(get_app(pub), username='foo', password='foo').get('/test/') + assert resp.forms[0]['f0'].value == 'foo@localhost' + def test_form_page_formula_prefill(pub): user = create_user(pub) formdef = create_formdef() @@ -2890,3 +2904,50 @@ def test_session_cookie_flags(pub): assert resp.headers['Set-Cookie'].startswith('wcs-') assert 'httponly' in resp.headers['Set-Cookie'] assert 'secure' in resp.headers['Set-Cookie'] + +def test_form_page_profile_verified_prefill(pub): + user = create_user(pub) + formdef = create_formdef() + formdef.data_class().wipe() + formdef.fields = [fields.StringField(id='0', label='string', + prefill={'type': 'user', 'value': 'email'})] + formdef.store() + + resp = get_app(pub).get('/test/') + assert resp.form['f0'].value == '' + + resp = login(get_app(pub), username='foo', password='foo').get('/test/') + assert resp.form['f0'].value == 'foo@localhost' + assert not 'readonly' in resp.form['f0'].attrs + resp.form['f0'].value = 'Hello' + resp = resp.form.submit('submit') + assert 'Check values then click submit.' in resp.body + assert resp.form['f0'].value == 'Hello' + + user.verified_fields = ['email'] + user.store() + + resp = login(get_app(pub), username='foo', password='foo').get('/test/') + assert resp.form['f0'].value == 'foo@localhost' + assert 'readonly' in resp.form['f0'].attrs + + resp.form['f0'].value = 'Hello' # try changing the value + resp = resp.form.submit('submit') + assert 'Check values then click submit.' in resp.body + assert resp.form['f0'].value == 'foo@localhost' # it is reverted + + resp.form['f0'].value = 'Hello' # try again changing the value + resp = resp.form.submit('submit') + + assert formdef.data_class().count() == 1 + assert formdef.data_class().select()[0].data['0'] == 'foo@localhost' + + resp = login(get_app(pub), username='foo', password='foo').get('/test/') + assert resp.form['f0'].value == 'foo@localhost' + resp = resp.form.submit('submit') + assert 'Check values then click submit.' in resp.body + resp.form['f0'].value = 'Hello' # try changing + resp = resp.form.submit('previous') + assert 'readonly' in resp.form['f0'].attrs + assert not 'Check values then click submit.' in resp.body + assert resp.form['f0'].value == 'foo@localhost' diff --git a/tests/test_prefill.py b/tests/test_prefill.py index aaa5a81..c0c720a 100644 --- a/tests/test_prefill.py +++ b/tests/test_prefill.py @@ -1,6 +1,8 @@ import sys import shutil +import pytest + from quixote import cleanup, get_request from wcs.qommon.http_request import HTTPRequest from wcs import fields @@ -17,12 +19,14 @@ def setup_module(module): req = HTTPRequest(None, {}) pub._set_request(req) +@pytest.fixture +def user(request): + pub.user_class.wipe() user = pub.user_class(name='user') user.id = 'user' user.email = 'test@example.net' - - req._user = user - + get_request()._user = user + return user def teardown_module(module): shutil.rmtree(pub.APP_DIR) @@ -31,34 +35,54 @@ def teardown_module(module): def test_prefill_string(): field = fields.Field() field.prefill = {'type': 'string', 'value': 'test'} + assert field.get_prefill_value() == ('test', False) - assert field.get_prefill_value() == 'test' +def test_prefill_user(user): + field = fields.Field() + field.prefill = {'type': 'user', 'value': 'email'} + assert field.get_prefill_value(user=get_request().user) == ('test@example.net', False) +def test_prefill_user_attribute(user): + from wcs.admin.settings import UserFieldsFormDef + formdef = UserFieldsFormDef(pub) + formdef.fields = [fields.StringField(id='3', label='test', type='string', varname='plop')] + formdef.store() -def test_prefill_user(): field = fields.Field() - field.prefill = {'type': 'user', 'value': 'email'} + field.prefill = {'type': 'user', 'value': '3'} + assert field.get_prefill_value(user=get_request().user) == (None, False) - assert field.get_prefill_value(user=get_request().user) == 'test@example.net' + user.form_data = {'3': 'Plop'} + user.store() + assert field.get_prefill_value(user=get_request().user) == ('Plop', False) +def test_prefill_verified_user_attribute(user): + from wcs.admin.settings import UserFieldsFormDef + formdef = UserFieldsFormDef(pub) + formdef.fields = [fields.StringField(id='3', label='test', type='string', varname='plop')] + formdef.store() -def test_prefill_formula(): field = fields.Field() - field.prefill = {'type': 'formula', 'value': 'str(2+5)'} + field.prefill = {'type': 'user', 'value': '3'} + assert field.get_prefill_value(user=get_request().user) == (None, False) - assert field.get_prefill_value() == '7' + user.form_data = {'3': 'Plop'} + user.verified_fields = ['3'] + user.store() + assert field.get_prefill_value(user=get_request().user) == ('Plop', True) +def test_prefill_formula(): + field = fields.Field() + field.prefill = {'type': 'formula', 'value': 'str(2+5)'} + assert field.get_prefill_value() == ('7', False) def test_prefill_formula_with_error(): field = fields.Field() field.prefill = {'type': 'formula', 'value': 'foobar'} + assert field.get_prefill_value() == (None, False) - assert field.get_prefill_value() is None - - -def test_prefill_formula_substition_variable(): +def test_prefill_formula_substitution_variable(): pub.substitutions.get_context_variables = lambda: {'test': 'value'} field = fields.Field() field.prefill = {'type': 'formula', 'value': 'test'} - - assert field.get_prefill_value() == 'value' + assert field.get_prefill_value() == ('value', False) diff --git a/wcs/fields.py b/wcs/fields.py index cbdfaa2..b66bcf4 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -276,17 +276,18 @@ class Field(object): def get_prefill_value(self, user=None): t = self.prefill.get('type') if t == 'string': - return self.prefill.get('value') + return (self.prefill.get('value'), False) elif t == 'user' and user: x = self.prefill.get('value') if x == 'email': - return user.email + return (user.email, 'email' in (user.verified_fields or [])) elif user.form_data: userform = user.get_formdef() for userfield in userform.fields: if userfield.id == x: - return user.form_data.get(x) + return (user.form_data.get(x), + str(userfield.id) in (user.verified_fields or [])) elif t == 'formula': formula = self.prefill.get('value') @@ -295,14 +296,14 @@ class Field(object): get_publisher().get_global_eval_dict(), get_publisher().substitutions.get_context_variables()) if ret: - return str(ret) + return (str(ret), False) except: pass elif t == 'geolocation': - return None + return (None, False) - return None + return (None, False) def get_prefill_attributes(self): t = self.prefill.get('type') diff --git a/wcs/forms/root.py b/wcs/forms/root.py index 67baf98..84f81c3 100644 --- a/wcs/forms/root.py +++ b/wcs/forms/root.py @@ -379,14 +379,22 @@ class FormPage(Directory): k = field.id v = None prefilled = False - if data.has_key(k): - v = data[k] - elif field.prefill: + verified = False + + if field.prefill: prefill_user = get_request().user if get_request().is_in_backoffice(): prefill_user = get_publisher().substitutions.get_context_variables( ).get('form_user') - v = field.get_prefill_value(user=prefill_user) + v, verified = field.get_prefill_value(user=prefill_user) + + # always set additional attributes as they will be used for + # "live prefill", regardless of existing data. + form.get_widget('f%s' % k).prefill_attributes = field.get_prefill_attributes() + + if data.has_key(k): + v = data[k] + elif field.prefill: if get_request().is_in_backoffice() and ( field.prefill and field.prefill.get('type') == 'geoloc'): # turn off prefilling from geolocation attributes if @@ -394,7 +402,7 @@ class FormPage(Directory): v = None if v: prefilled = True - if field.prefill and field.prefill.get('type') != 'string': + if field.prefill and field.prefill.get('type') != 'string' and not verified: # unless we prefilled with a fixed string, we # inform the user the field value has been # prefilled. @@ -402,11 +410,6 @@ class FormPage(Directory): _('Value has been automatically prefilled.')) form.get_widget('f%s' % k).prefilled = True - if field.prefill: - # always set additional attributes as they will be used for - # "live prefill", regardless of existing data. - form.get_widget('f%s' % k).prefill_attributes = field.get_prefill_attributes() - if not prefilled and form.get_widget('f%s' % k): form.get_widget('f%s' % k).clear_error() @@ -414,6 +417,9 @@ class FormPage(Directory): if not isinstance(v, str) and field.convert_value_to_str: v = field.convert_value_to_str(v) form.get_widget('f%s' % k).set_value(v) + if verified: + form.get_widget('f%s' % k).readonly = 'readonly' + form.get_widget('f%s' % k).attrs['readonly'] = 'readonly' req.form['f%s' % k] = v one = True @@ -622,6 +628,20 @@ class FormPage(Directory): except TypeError: step = 0 + # reset verified fields, making sure the user cannot alter them. + prefill_user = get_request().user + if get_request().is_in_backoffice(): + prefill_user = get_publisher().substitutions.get_context_variables().get('form_user') + if prefill_user: + for field in self.formdef.fields: + if not field.prefill: + continue + if not 'f%s' % field.id in get_request().form: + continue + v, verified = field.get_prefill_value(user=prefill_user) + if verified: + get_request().form['f%s' % field.id] = v + if step == 0: try: page_no = int(form.get_widget('page').parse()) diff --git a/wcs/sql.py b/wcs/sql.py index 7e409cb..04b4f15 100644 --- a/wcs/sql.py +++ b/wcs/sql.py @@ -447,6 +447,7 @@ def do_user_table(): roles text[], is_admin bool, anonymous bool, + verified_fields text[], name_identifiers text[], lasso_dump text, last_seen timestamp)''' % table_name) @@ -455,7 +456,7 @@ def do_user_table(): existing_fields = set([x[0] for x in cur.fetchall()]) needed_fields = set(['id', 'name', 'email', 'roles', 'is_admin', - 'anonymous', 'name_identifiers', + 'anonymous', 'name_identifiers', 'verified_fields', 'lasso_dump', 'last_seen', 'fts']) from admin.settings import UserFieldsFormDef @@ -489,6 +490,9 @@ def do_user_table(): cur.execute('''CREATE INDEX %s_fts ON %s USING gin(fts)''' % ( table_name, table_name)) + if not 'verified_fields' in existing_fields: + cur.execute('ALTER TABLE %s ADD COLUMN verified_fields text[]' % table_name) + # delete obsolete fields for field in (existing_fields - needed_fields): cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field)) @@ -1395,6 +1399,7 @@ class SqlUser(SqlMixin, wcs.users.User): ('is_admin', 'bool'), ('anonymous', 'bool'), ('name_identifiers', 'varchar[]'), + ('verified_fields', 'varchar[]'), ('lasso_dump', 'text'), ('last_seen', 'timestamp') ] @@ -1404,6 +1409,7 @@ class SqlUser(SqlMixin, wcs.users.User): def __init__(self, name=None): self.name = name self.name_identifiers = [] + self.verified_fields = [] self.roles = [] @guard_postgres @@ -1415,6 +1421,7 @@ class SqlUser(SqlMixin, wcs.users.User): 'is_admin': self.is_admin, 'anonymous': self.anonymous, 'name_identifiers': self.name_identifiers, + 'verified_fields': self.verified_fields, 'lasso_dump': self.lasso_dump, 'last_seen': None, } @@ -1480,8 +1487,8 @@ class SqlUser(SqlMixin, wcs.users.User): def _row2ob(cls, row): o = cls() (o.id, o.name, o.email, o.roles, o.is_admin, o.anonymous, - o.name_identifiers, o.lasso_dump, - o.last_seen) = tuple(row[:9]) + o.name_identifiers, o.verified_fields, o.lasso_dump, + o.last_seen) = tuple(row[:10]) if o.last_seen: o.last_seen = time.mktime(o.last_seen.timetuple()) if o.roles: @@ -1843,7 +1850,7 @@ def get_yearly_totals(period_start=None, period_end=None, criterias=None): return result -SQL_LEVEL = 15 +SQL_LEVEL = 16 def migrate_global_views(conn, cur): cur.execute('''SELECT COUNT(*) FROM information_schema.tables @@ -1889,10 +1896,11 @@ def migrate(): # 14: add criticality_level to tables & views # 15: add geolocation to formdata migrate_views(conn, cur) - if sql_level < 12: + if sql_level < 16: # 3: introduction of _structured for user fields # 4: removal of identification_token # 12: (first part) add fts to users + # 16: add verified_fields to users do_user_table() if sql_level < 6: # 6: add actions_roles_array to tables and views diff --git a/wcs/users.py b/wcs/users.py index 9a14f60..9d1c2a2 100644 --- a/wcs/users.py +++ b/wcs/users.py @@ -33,6 +33,7 @@ class User(StorableObject): anonymous = False form_data = None # dumping ground for custom fields + verified_fields = None name_identifiers = None lasso_dump = None @@ -42,6 +43,7 @@ class User(StorableObject): StorableObject.__init__(self) self.name = name self.name_identifiers = [] + self.verified_fields = [] self.roles = [] def get_hash(self): @@ -71,6 +73,9 @@ class User(StorableObject): changed = True break + if not self.verified_fields: + self.verified_fields = [] + if changed: self.store() -- 2.8.1