WIP: general: switch map field value to be a dictionary (#46617) #1051
|
@ -1028,7 +1028,7 @@ def test_settings_geolocation(pub):
|
|||
resp = resp.click('Geolocation')
|
||||
assert 'value="1.234;-1.234' in resp.text
|
||||
pub.reload_cfg()
|
||||
assert pub.cfg['misc']['default-position'] == '1.234;-1.234'
|
||||
assert pub.cfg['misc']['default-position'] == {'lat': 1.234, 'lon': -1.234}
|
||||
|
||||
assert pub.cfg['misc']['default-zoom-level'] == '13'
|
||||
resp = resp.click('Geolocation')
|
||||
|
|
|
@ -1204,7 +1204,7 @@ def test_carddef_submit_with_varname(pub, local_user):
|
|||
|
||||
assert data_class.get(resp.json['data']['id']).data['4'].orig_filename == 'test.txt'
|
||||
assert data_class.get(resp.json['data']['id']).data['4'].get_content() == b'test'
|
||||
assert data_class.get(resp.json['data']['id']).data['5'] == '1.5;2.25'
|
||||
assert data_class.get(resp.json['data']['id']).data['5'] == {'lat': 1.5, 'lon': 2.25}
|
||||
# test bijectivity
|
||||
assert (
|
||||
carddef.fields[3].get_json_value(data_class.get(resp.json['data']['id']).data['3'])
|
||||
|
@ -1282,7 +1282,7 @@ def test_carddef_submit_from_wscall(pub, local_user):
|
|||
'2_display': 'bar',
|
||||
'3': time.strptime('1970-01-01', '%Y-%m-%d'),
|
||||
'4': upload,
|
||||
'5': '1.5;2.25',
|
||||
'5': {'lat': 1.5, 'lon': 2.25},
|
||||
'bo1': 'backoffice field',
|
||||
}
|
||||
carddata.just_created()
|
||||
|
|
|
@ -824,7 +824,7 @@ def test_formdef_submit_with_varname(pub, local_user):
|
|||
|
||||
assert data_class.get(resp.json['data']['id']).data['4'].orig_filename == 'test.txt'
|
||||
assert data_class.get(resp.json['data']['id']).data['4'].get_content() == b'test'
|
||||
assert data_class.get(resp.json['data']['id']).data['5'] == '1.5;2.25'
|
||||
assert data_class.get(resp.json['data']['id']).data['5'] == {'lat': 1.5, 'lon': 2.25}
|
||||
assert data_class.get(resp.json['data']['id']).data['8'] == []
|
||||
assert data_class.get(resp.json['data']['id']).data['9'] is False
|
||||
assert data_class.get(resp.json['data']['id']).data['10'] is True
|
||||
|
@ -876,32 +876,33 @@ def test_formdef_submit_from_wscall(pub, local_user):
|
|||
'2_display': 'bar',
|
||||
'3': time.strptime('1970-01-01', '%Y-%m-%d'),
|
||||
'4': upload,
|
||||
'5': '1.5;2.25',
|
||||
'bo1': 'backoffice field',
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.evolution[-1].status = 'wf-new'
|
||||
formdata.store()
|
||||
|
||||
payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
|
||||
signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
|
||||
url = signed_url[len('http://example.net') :]
|
||||
for map_value in ('1.5;2.25', {'lat': 1.5, 'lon': 2.25}):
|
||||
formdata.data['5'] = map_value
|
||||
payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
|
||||
signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
|
||||
url = signed_url[len('http://example.net') :]
|
||||
|
||||
resp = get_app(pub).post_json(url, payload)
|
||||
assert resp.json['err'] == 0
|
||||
new_formdata = formdef.data_class().get(resp.json['data']['id'])
|
||||
assert new_formdata.data['0'] == formdata.data['0']
|
||||
assert new_formdata.data['1'] == formdata.data['1']
|
||||
assert new_formdata.data['1_display'] == formdata.data['1_display']
|
||||
assert new_formdata.data['1_structured'] == formdata.data['1_structured']
|
||||
assert new_formdata.data['2'] == formdata.data['2']
|
||||
assert new_formdata.data['2_display'] == formdata.data['2_display']
|
||||
assert new_formdata.data['3'] == formdata.data['3']
|
||||
assert new_formdata.data['4'].get_content() == formdata.data['4'].get_content()
|
||||
assert new_formdata.data['5'] == formdata.data['5']
|
||||
assert new_formdata.data['bo1'] == formdata.data['bo1']
|
||||
assert not new_formdata.data.get('6')
|
||||
assert new_formdata.user_id is None
|
||||
resp = get_app(pub).post_json(url, payload)
|
||||
assert resp.json['err'] == 0
|
||||
new_formdata = formdef.data_class().get(resp.json['data']['id'])
|
||||
assert new_formdata.data['0'] == formdata.data['0']
|
||||
assert new_formdata.data['1'] == formdata.data['1']
|
||||
assert new_formdata.data['1_display'] == formdata.data['1_display']
|
||||
assert new_formdata.data['1_structured'] == formdata.data['1_structured']
|
||||
assert new_formdata.data['2'] == formdata.data['2']
|
||||
assert new_formdata.data['2_display'] == formdata.data['2_display']
|
||||
assert new_formdata.data['3'] == formdata.data['3']
|
||||
assert new_formdata.data['4'].get_content() == formdata.data['4'].get_content()
|
||||
assert new_formdata.data['5'] == {'lat': 1.5, 'lon': 2.25}
|
||||
assert new_formdata.data['bo1'] == formdata.data['bo1']
|
||||
assert not new_formdata.data.get('6')
|
||||
assert new_formdata.user_id is None
|
||||
|
||||
# add an extra attribute
|
||||
payload['extra'] = {'foobar6': 'YYY'}
|
||||
|
|
|
@ -489,7 +489,7 @@ def test_backoffice_cards_import_data_from_csv(pub):
|
|||
'"value",'
|
||||
'"id1|id2|...",'
|
||||
'"value"'
|
||||
'\r\n' % (pub.get_default_position(), today)
|
||||
'\r\n' % ('%(lat)s;%(lon)s' % pub.get_default_position(), today)
|
||||
)
|
||||
|
||||
# missing file
|
||||
|
@ -521,7 +521,7 @@ def test_backoffice_cards_import_data_from_csv(pub):
|
|||
assert 'Importing data into cards' in resp
|
||||
assert carddef.data_class().count() == 149
|
||||
card1, card2 = carddef.data_class().select(order_by='id')[:2]
|
||||
assert card1.data['1'] == '48.81;2.37'
|
||||
assert card1.data['1'] == {'lat': 48.81, 'lon': 2.37}
|
||||
assert card1.data['2'] == 'data1'
|
||||
assert card1.data['3'] is True
|
||||
assert card1.data['5'].tm_mday == 2
|
||||
|
|
|
@ -982,9 +982,8 @@ def test_inspect_page_map_field(pub, local_user):
|
|||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.data = {
|
||||
'1': '1.2345;6.789', # valid value
|
||||
'1': {'lat': 1.2345, 'lon': 6.789}, # valid value
|
||||
'2': None, # empty value
|
||||
'3': 'XXX;YYY', # invalid value
|
||||
}
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
@ -997,9 +996,6 @@ def test_inspect_page_map_field(pub, local_user):
|
|||
assert resp.pyquery('[title="form_var_map2"]')
|
||||
assert not resp.pyquery('[title="form_var_map2_lat"]')
|
||||
assert not resp.pyquery('[title="form_var_map2_lon"]')
|
||||
assert resp.pyquery('[title="form_var_map3"]')
|
||||
assert not resp.pyquery('[title="form_var_map3_lat"]')
|
||||
assert not resp.pyquery('[title="form_var_map3_lon"]')
|
||||
|
||||
|
||||
def test_inspect_page_lazy_list(pub):
|
||||
|
|
|
@ -90,7 +90,7 @@ def test_form_map_field_back_and_submit(pub):
|
|||
assert formdef.data_class().count() == 1
|
||||
data_id = formdef.data_class().select()[0].id
|
||||
data = formdef.data_class().get(data_id)
|
||||
assert data.data == {'1': 'bla', '0': '1.234;-1.234'}
|
||||
assert data.data == {'0': {'lat': 1.234, 'lon': -1.234}, '1': 'bla'}
|
||||
|
||||
|
||||
def test_form_map_initial_zoom_level(pub):
|
||||
|
@ -191,7 +191,7 @@ def test_form_map_multi_page(pub):
|
|||
assert formdef.data_class().count() == 1
|
||||
data_id = formdef.data_class().select()[0].id
|
||||
data = formdef.data_class().get(data_id)
|
||||
assert data.data == {'1': '1.234;-1.234', '3': 'bar'}
|
||||
assert data.data == {'1': {'lat': 1.234, 'lon': -1.234}, '3': 'bar'}
|
||||
|
||||
|
||||
def test_form_map_field_default_position(pub):
|
||||
|
|
|
@ -442,7 +442,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'a',
|
||||
'8': ['b'],
|
||||
'8_display': 'b',
|
||||
'9': '1.5;2.25',
|
||||
'9': {'lat': 1.5, 'lon': 2.25},
|
||||
'10': {'cleartext': 'foo'},
|
||||
'11': 'computed',
|
||||
'12': {
|
||||
|
@ -458,7 +458,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'b',
|
||||
'8': ['a', 'b'],
|
||||
'8_display': 'a, b',
|
||||
'9': '1.6;2.26',
|
||||
'9': {'lat': 1.6, 'lon': 2.26},
|
||||
'10': {'cleartext': 'bar'},
|
||||
},
|
||||
],
|
||||
|
@ -483,7 +483,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'a',
|
||||
'8': ['a', 'b'], # changed
|
||||
'8_display': 'a, b',
|
||||
'9': '1.5;2.25',
|
||||
'9': {'lat': 1.5, 'lon': 2.25},
|
||||
'10': {'cleartext': 'fooo'}, # changed
|
||||
'11': 'computed',
|
||||
'12': {
|
||||
|
@ -499,7 +499,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'b',
|
||||
'8': ['a', 'b'],
|
||||
'8_display': 'a, b',
|
||||
'9': '1.6;2.27', # changed
|
||||
'9': {'lat': 1.6, 'lon': 2.27}, # changed
|
||||
'10': {'cleartext': 'barr'}, # changed
|
||||
},
|
||||
{ # new element
|
||||
|
@ -513,7 +513,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'b',
|
||||
'8': ['a', 'b'],
|
||||
'8_display': 'a, b',
|
||||
'9': '1.6;2.26',
|
||||
'9': {'lat': 1.6, 'lon': 2.26},
|
||||
'10': {'cleartext': 'bar'},
|
||||
},
|
||||
],
|
||||
|
@ -534,7 +534,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'b',
|
||||
'8': ['a', 'b'],
|
||||
'8_display': 'a, b',
|
||||
'9': '1.5;2.26', # changed
|
||||
'9': {'lat': 1.5, 'lon': 2.26}, # changed
|
||||
'10': {'cleartext': 'fooo'},
|
||||
'11': 'computed',
|
||||
'12': {
|
||||
|
@ -572,7 +572,7 @@ def test_backoffice_show_history(pub, user, formdef_class):
|
|||
'7_display': 'b',
|
||||
'8': ['a', 'b'],
|
||||
'8_display': 'a, b',
|
||||
'9': '1.5;2.26',
|
||||
'9': {'lat': 1.5, 'lon': 2.26},
|
||||
'10': {'cleartext': 'fooo'},
|
||||
'11': 'computed',
|
||||
'12': {
|
||||
|
|
|
@ -480,11 +480,9 @@ def test_comment(pub):
|
|||
|
||||
|
||||
def test_map():
|
||||
assert fields.MapField().get_json_value('42.2;10.2') == {'lat': 42.2, 'lon': 10.2}
|
||||
assert fields.MapField().get_json_value('-42.2;10.2') == {'lat': -42.2, 'lon': 10.2}
|
||||
assert fields.MapField().get_json_value(' 42.2 ; 10.2 ') == {'lat': 42.2, 'lon': 10.2}
|
||||
assert fields.MapField().get_json_value('') is None
|
||||
assert fields.MapField().get_json_value('foobar') is None
|
||||
assert fields.MapField().get_json_value({'lat': 42.2, 'lon': 10.2}) == {'lat': 42.2, 'lon': 10.2}
|
||||
assert fields.MapField().get_json_value({'lat': -42.2, 'lon': 10.2}) == {'lat': -42.2, 'lon': 10.2}
|
||||
assert fields.MapField().get_json_value(None) is None
|
||||
|
||||
|
||||
def test_map_migrate():
|
||||
|
@ -511,7 +509,7 @@ def test_map_set_value(pub):
|
|||
formdata.data = {}
|
||||
|
||||
formdef.fields[0].set_value(formdata.data, '42;10')
|
||||
assert formdata.data['5'] == '42;10'
|
||||
assert formdata.data['5'] == {'lat': 42, 'lon': 10}
|
||||
substvars = CompatibilityNamesDict()
|
||||
substvars.update(formdata.get_substitution_variables())
|
||||
keys = substvars.get_flat_keys()
|
||||
|
@ -530,13 +528,8 @@ def test_map_set_value(pub):
|
|||
# set invalid value, it is ignored
|
||||
with pytest.raises(fields.SetValueError):
|
||||
formdef.fields[0].set_value(formdata.data, 'XXX;YYY')
|
||||
|
||||
# set invalid value without using set_value()
|
||||
formdata.data['5'] = 'XXX;YYY'
|
||||
substvars = CompatibilityNamesDict()
|
||||
substvars.update(formdata.get_substitution_variables())
|
||||
keys = substvars.get_flat_keys()
|
||||
assert 'form_var_map_lon' not in keys
|
||||
with pytest.raises(fields.SetValueError):
|
||||
formdef.fields[0].set_value(formdata.data, {'lat': 'XXX', 'lon': 'YYY'})
|
||||
|
||||
|
||||
def test_item_render():
|
||||
|
|
|
@ -808,7 +808,7 @@ def variable_test_data(pub):
|
|||
'4_display': 'aa, ac',
|
||||
'5': PicklableUpload('test.txt', 'text/plain'),
|
||||
'6': 'other',
|
||||
'7': '2;4', # map
|
||||
'7': {'lat': 2, 'lon': 4}, # map
|
||||
'8': time.strptime('2018-08-31', '%Y-%m-%d'),
|
||||
'9': '2018-07-31',
|
||||
'10': '3',
|
||||
|
@ -2317,14 +2317,13 @@ def test_lazy_map_variable(pub, variable_test_data):
|
|||
pub.substitutions.reset()
|
||||
pub.substitutions.feed(formdef)
|
||||
with pub.substitutions.temporary_feed(formdata, force_mode=mode):
|
||||
assert WorkflowStatusItem.compute('=form_var_map', raises=True) == '2;4'
|
||||
assert WorkflowStatusItem.compute('=form_var_map["lat"]', raises=True) == 2
|
||||
assert WorkflowStatusItem.compute('{{ form_var_map }}', raises=True) == '2;4'
|
||||
assert WorkflowStatusItem.compute('=form_var_map.split(";")[0]', raises=True) == '2'
|
||||
assert WorkflowStatusItem.compute('{{ form_var_map|split:";"|first }}', raises=True) == '2'
|
||||
assert WorkflowStatusItem.compute('=form_var_map_lat', raises=True) == 2
|
||||
assert WorkflowStatusItem.compute('{{ form_var_map_lat }}', raises=True) == '2.0'
|
||||
assert WorkflowStatusItem.compute('{{ form_var_map_lat }}', raises=True) == '2'
|
||||
assert WorkflowStatusItem.compute('=form_var_map_lon', raises=True) == 4
|
||||
assert WorkflowStatusItem.compute('{{ form_var_map_lon }}', raises=True) == '4.0'
|
||||
assert WorkflowStatusItem.compute('{{ form_var_map_lon }}', raises=True) == '4'
|
||||
|
||||
assert (
|
||||
WorkflowStatusItem.compute(
|
||||
|
@ -5550,7 +5549,7 @@ def test_rst_form_details_all_fields(pub):
|
|||
'4': 'string',
|
||||
'5': 'foo@localhost',
|
||||
'6': 'para1\npara2',
|
||||
'7': '2;4', # map
|
||||
'7': {'lat': 2, 'lon': 4}, # map
|
||||
'8': False,
|
||||
'9': upload,
|
||||
'10': time.strptime('2015-05-12', '%Y-%m-%d'),
|
||||
|
|
|
@ -186,7 +186,7 @@ def test_register_cronjobs():
|
|||
|
||||
|
||||
def test_get_default_position():
|
||||
assert pub.get_default_position() == '50.84;4.36'
|
||||
assert pub.get_default_position() == {'lat': 50.84, 'lon': 4.36}
|
||||
|
||||
|
||||
def test_import_config_zip():
|
||||
|
|
|
@ -2917,3 +2917,55 @@ def test_sql_data_views(pub_with_views, formdef_class):
|
|||
assert column_exists_in_table(cur, f'{prefix}_test', 'geoloc_base_x')
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
|
||||
def test_migration_99_map_data_type(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test map migration'
|
||||
formdef.fields = [
|
||||
fields.MapField(id='1', label='map'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata1 = formdef.data_class(mode='sql')()
|
||||
formdata1.just_created()
|
||||
formdata1.store()
|
||||
|
||||
formdata2 = formdef.data_class(mode='sql')()
|
||||
formdata2.just_created()
|
||||
formdata2.store()
|
||||
|
||||
formdata3 = formdef.data_class(mode='sql')()
|
||||
formdata3.just_created()
|
||||
formdata3.store()
|
||||
|
||||
conn, cur = sql.get_connection_and_cursor()
|
||||
cur.execute('UPDATE wcs_meta SET value = 42 WHERE key = %s', ('sql_level',))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
conn, cur = sql.get_connection_and_cursor()
|
||||
cur.execute('ALTER TABLE %s DROP COLUMN f1 CASCADE' % sql.get_formdef_table_name(formdef))
|
||||
cur.execute('ALTER TABLE %s ADD COLUMN f1 VARCHAR' % sql.get_formdef_table_name(formdef))
|
||||
cur.execute(
|
||||
'UPDATE ' + sql.get_formdef_table_name(formdef) + ' SET f1 = %s WHERE id = %s', ('1;2', formdata1.id)
|
||||
)
|
||||
cur.execute(
|
||||
'UPDATE ' + sql.get_formdef_table_name(formdef) + ' SET f1 = %s WHERE id = %s', ('', formdata2.id)
|
||||
)
|
||||
cur.execute(
|
||||
'UPDATE ' + sql.get_formdef_table_name(formdef) + ' SET f1 = %s WHERE id = %s', (None, formdata3.id)
|
||||
)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
sql.migrate()
|
||||
|
||||
conn, cur = sql.get_connection_and_cursor()
|
||||
assert migration_level(cur) >= 99
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
assert formdef.data_class(mode='sql').get(formdata1.id).data['1'] == {'lat': 1, 'lon': 2}
|
||||
assert formdef.data_class(mode='sql').get(formdata2.id).data['1'] is None
|
||||
assert formdef.data_class(mode='sql').get(formdata3.id).data['1'] is None
|
||||
|
|
|
@ -1465,7 +1465,7 @@ def test_map_widget():
|
|||
widget = MapWidget('test', title='Map')
|
||||
mock_form_submission(req, widget, hidden_html_vars={'test$latlng': '1.23;2.34'})
|
||||
assert not widget.has_error()
|
||||
assert widget.parse() == '1.23;2.34'
|
||||
assert widget.parse() == {'lat': 1.23, 'lon': 2.34}
|
||||
|
||||
assert '<label' in str(widget.render())
|
||||
assert '<label ' not in str(widget.render_widget_content())
|
||||
|
|
|
@ -203,7 +203,7 @@ def test_set_backoffice_field_map(http_requests, pub):
|
|||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': '42;10', '2': None}
|
||||
formdata.data = {'1': {'lat': 42, 'lon': 10}, '2': None}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
pub.substitutions.feed(formdata)
|
||||
|
@ -214,7 +214,7 @@ def test_set_backoffice_field_map(http_requests, pub):
|
|||
item.fields = [{'field_id': 'bo1', 'value': '{{ form_var_map1|default:"" }}'}]
|
||||
item.perform(formdata)
|
||||
formdata = formdef.data_class().get(formdata.id)
|
||||
assert formdata.data.get('bo1') == '42;10'
|
||||
assert formdata.data.get('bo1') == {'lat': 42, 'lon': 10}
|
||||
|
||||
item.fields = [{'field_id': 'bo1', 'value': '{{ form_var_map2|default:"" }}'}]
|
||||
item.perform(formdata)
|
||||
|
|
|
@ -384,7 +384,7 @@ def test_create_carddata_with_map_field(pub):
|
|||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert carddef.data_class().count() == 1
|
||||
assert carddef.data_class().select()[0].data.get('1') == '1;2'
|
||||
assert carddef.data_class().select()[0].data.get('1') == {'lat': 1, 'lon': 2}
|
||||
|
||||
# invalid value
|
||||
create.mappings[0].expression = 'plop'
|
||||
|
@ -409,12 +409,12 @@ def test_create_carddata_with_map_field(pub):
|
|||
formdef.refresh_from_storage()
|
||||
carddef.data_class().wipe()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': '2;3'}
|
||||
formdata.data = {'1': {'lat': 2, 'lon': 3}}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert carddef.data_class().count() == 1
|
||||
assert carddef.data_class().select()[0].data.get('1') == '2;3'
|
||||
assert carddef.data_class().select()[0].data.get('1') == {'lat': 2, 'lon': 3}
|
||||
|
||||
|
||||
def test_create_carddata_user_association(pub):
|
||||
|
|
|
@ -253,7 +253,7 @@ def test_geolocate_map(pub):
|
|||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'2': '48.8337085;2.3233693'}
|
||||
formdata.data = {'2': {'lat': 48.8337085, 'lon': 2.3233693}}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
pub.substitutions.feed(formdata)
|
||||
|
@ -294,7 +294,7 @@ def test_geolocate_overwrite(pub):
|
|||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'2': '48.8337085;2.3233693'}
|
||||
formdata.data = {'2': {'lat': 48.8337085, 'lon': 2.3233693}}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
pub.substitutions.feed(formdata)
|
||||
|
@ -307,12 +307,12 @@ def test_geolocate_overwrite(pub):
|
|||
assert int(formdata.geolocations['base']['lat']) == 48
|
||||
assert int(formdata.geolocations['base']['lon']) == 2
|
||||
|
||||
formdata.data = {'2': '48.8337085;3.3233693'}
|
||||
formdata.data = {'2': {'lat': 48.8337085, 'lon': 3.3233693}}
|
||||
item.perform(formdata)
|
||||
assert int(formdata.geolocations['base']['lat']) == 48
|
||||
assert int(formdata.geolocations['base']['lon']) == 3
|
||||
|
||||
formdata.data = {'2': '48.8337085;4.3233693'}
|
||||
formdata.data = {'2': {'lat': 48.8337085, 'lon': 4.3233693}}
|
||||
item.overwrite = False
|
||||
item.perform(formdata)
|
||||
assert int(formdata.geolocations['base']['lat']) == 48
|
||||
|
|
|
@ -204,7 +204,7 @@ class CardPage(FormPage):
|
|||
elif isinstance(f, fields.EmailField):
|
||||
value = 'foo@example.com'
|
||||
elif isinstance(f, fields.MapField):
|
||||
value = get_publisher().get_default_position()
|
||||
value = '%(lat)s;%(lon)s' % get_publisher().get_default_position()
|
||||
elif isinstance(f, fields.ItemsField):
|
||||
value = 'id1|id2|...'
|
||||
else:
|
||||
|
|
|
@ -3668,10 +3668,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
r += htmltext('<h3>%s</h3>') % formdata.formdef.geolocations[geoloc_key]
|
||||
geoloc_value = formdata.geolocations[geoloc_key]
|
||||
map_widget = MapWidget(
|
||||
'geoloc_%s' % geoloc_key,
|
||||
readonly=True,
|
||||
value='%(lat)s;%(lon)s' % geoloc_value,
|
||||
render_br=False,
|
||||
'geoloc_%s' % geoloc_key, readonly=True, value=geoloc_value, render_br=False
|
||||
)
|
||||
r += map_widget.render()
|
||||
r += htmltext('</div>')
|
||||
|
|
|
@ -196,7 +196,7 @@ class MapField(WidgetField, MapOptionsMixin):
|
|||
# otherwise it will be interpreted as an address that will be geocoded.
|
||||
prefill_value, explicit_lock = super().get_prefill_value()
|
||||
if re.match(r'-?\d+(\.\d+)?;-?\d+(\.\d+)?$', prefill_value):
|
||||
return (prefill_value, explicit_lock)
|
||||
return (self.convert_value_from_str(prefill_value), explicit_lock)
|
||||
|
||||
from wcs.wf.geolocate import GeolocateWorkflowStatusItem
|
||||
|
||||
|
@ -204,55 +204,57 @@ class MapField(WidgetField, MapOptionsMixin):
|
|||
geolocate.method = 'address_string'
|
||||
geolocate.address_string = prefill_value
|
||||
coords = geolocate.geolocate_address_string(None, compute_template=False)
|
||||
if not coords:
|
||||
return (None, False)
|
||||
return ('%(lat)s;%(lon)s' % coords, False)
|
||||
return (coords, False)
|
||||
|
||||
def get_view_value(self, value, **kwargs):
|
||||
widget = self.widget_class('x%s' % random.random(), value, readonly=True)
|
||||
return widget.render_widget_content()
|
||||
|
||||
def get_rst_view_value(self, value, indent=''):
|
||||
return indent + value
|
||||
try:
|
||||
return indent + '%(lat)s;%(lon)s' % value
|
||||
except TypeError:
|
||||
return ''
|
||||
|
||||
def convert_value_from_str(self, value):
|
||||
try:
|
||||
dummy, dummy = (float(x) for x in value.split(';'))
|
||||
lat, lon = (float(x) for x in value.split(';'))
|
||||
except (AttributeError, ValueError):
|
||||
return None
|
||||
return value
|
||||
|
||||
def get_json_value(self, value, **kwargs):
|
||||
if not value or ';' not in value:
|
||||
return None
|
||||
lat, lon = value.split(';')
|
||||
try:
|
||||
lat = float(lat)
|
||||
lon = float(lon)
|
||||
except ValueError:
|
||||
return None
|
||||
return {'lat': lat, 'lon': lon}
|
||||
|
||||
def get_json_value(self, value, **kwargs):
|
||||
return value
|
||||
|
||||
def from_json_value(self, value):
|
||||
if 'lat' in value and 'lon' in value:
|
||||
return '%s;%s' % (float(value['lat']), float(value['lon']))
|
||||
else:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
# backward compatibility
|
||||
return self.convert_value_from_str(value)
|
||||
return value
|
||||
|
||||
def get_structured_value(self, data):
|
||||
return self.get_json_value(data.get(self.id))
|
||||
|
||||
def set_value(self, data, value, raise_on_error=False):
|
||||
if value == '':
|
||||
if isinstance(value, dict):
|
||||
try:
|
||||
value = {
|
||||
'lat': float(value['lat']),
|
||||
'lon': float(value['lon']),
|
||||
}
|
||||
except (KeyError, ValueError, TypeError):
|
||||
raise SetValueError(_('invalid coordinates %r (field id: %s)') % (value, self.id))
|
||||
elif value == '':
|
||||
value = None
|
||||
elif value and ';' not in value:
|
||||
raise SetValueError(_('invalid coordinates %r (missing ;) (field id: %s)') % (value, self.id))
|
||||
elif value:
|
||||
try:
|
||||
dummy, dummy = (float(x) for x in value.split(';'))
|
||||
lat, lon = (float(x) for x in value.split(';'))
|
||||
except ValueError:
|
||||
# will catch both "too many values to unpack" and invalid float values
|
||||
raise SetValueError(_('invalid coordinates %r (field id: %s)') % (value, self.id))
|
||||
value = {'lat': lat, 'lon': lon}
|
||||
super().set_value(data, value)
|
||||
|
||||
|
||||
|
|
|
@ -1053,7 +1053,7 @@ class FormDef(StorableObject):
|
|||
def get_field_data(cls, field, widget, raise_on_error=False):
|
||||
d = {}
|
||||
d[field.id] = widget.parse()
|
||||
if d.get(field.id) is not None and field.convert_value_from_str:
|
||||
if isinstance(d.get(field.id), str) and field.convert_value_from_str:
|
||||
d[field.id] = field.convert_value_from_str(d[field.id])
|
||||
field.set_value(d, d[field.id], raise_on_error=raise_on_error)
|
||||
if getattr(widget, 'cleanup', None):
|
||||
|
|
|
@ -3600,7 +3600,12 @@ class MapWidget(CompositeWidget):
|
|||
|
||||
def __init__(self, name, value=None, **kwargs):
|
||||
CompositeWidget.__init__(self, name, value, **kwargs)
|
||||
self.add(HiddenWidget, 'latlng', value=value)
|
||||
latlng_value = None
|
||||
if isinstance(value, str): # legacy data type
|
||||
latlng_value = value
|
||||
elif value:
|
||||
latlng_value = '%s;%s' % (value['lat'], value['lon'])
|
||||
self.add(HiddenWidget, 'latlng', value=latlng_value)
|
||||
self.readonly = kwargs.pop('readonly', False)
|
||||
self.init_map_attributes(value, **kwargs)
|
||||
|
||||
|
@ -3651,9 +3656,19 @@ class MapWidget(CompositeWidget):
|
|||
self.map_attributes['data-def-lng'] = '%.8f' % coords['lon']
|
||||
self.map_attributes['data-def-template'] = 'true'
|
||||
|
||||
def point2str(self, value):
|
||||
if not value:
|
||||
return None
|
||||
return '%s;%s' % (value['lat'], value['lon'])
|
||||
|
||||
def transfer_form_value(self, request):
|
||||
request.form[self.get_widget('latlng').name] = self.point2str(self.value)
|
||||
|
||||
def initial_position(self):
|
||||
if self.value and ';' in self.value:
|
||||
if isinstance(self.value, str) and ';' in self.value:
|
||||
return {'lat': self.value.split(';')[0], 'lng': self.value.split(';')[1]}
|
||||
if isinstance(self.value, dict):
|
||||
return {'lat': self.value['lat'], 'lng': self.value['lon']}
|
||||
return None
|
||||
|
||||
def add_media(self):
|
||||
|
@ -3666,10 +3681,10 @@ class MapWidget(CompositeWidget):
|
|||
try:
|
||||
lat, lon = self.value.split(';')
|
||||
except ValueError:
|
||||
self.value = None
|
||||
self.set_error_code('bad_input')
|
||||
else:
|
||||
lat_lon = misc.normalize_geolocation({'lat': lat, 'lon': lon})
|
||||
self.value = '%s;%s' % (lat_lon['lat'], lat_lon['lon']) if lat_lon else None
|
||||
self.value = misc.normalize_geolocation({'lat': lat, 'lon': lon})
|
||||
|
||||
def set_value(self, value):
|
||||
super().set_value(value)
|
||||
|
@ -3695,6 +3710,9 @@ class MapMarkerSelectionWidget(MapWidget):
|
|||
def initial_position(self):
|
||||
return None
|
||||
|
||||
def transfer_form_value(self, request):
|
||||
request.form[self.name] = self.value
|
||||
|
||||
def _parse(self, request):
|
||||
CompositeWidget._parse(self, request)
|
||||
self.value = self.get('marker_id')
|
||||
|
|
|
@ -795,9 +795,14 @@ class QommonPublisher(Publisher):
|
|||
def get_default_position(self):
|
||||
default_position = self.cfg.get('misc', {}).get('default-position', None)
|
||||
if not default_position:
|
||||
default_position = self.get_site_option('default_position')
|
||||
if not default_position:
|
||||
default_position = '50.84;4.36'
|
||||
default_position = self.get_site_option('default_position') or '50.84;4.36'
|
||||
|
||||
if isinstance(default_position, str):
|
||||
default_position = {
|
||||
'lat': float(default_position.split(';')[0]),
|
||||
'lon': float(default_position.split(';')[1]),
|
||||
}
|
||||
|
||||
return default_position
|
||||
|
||||
def get_default_zoom_level(self):
|
||||
|
@ -805,7 +810,9 @@ class QommonPublisher(Publisher):
|
|||
|
||||
def get_map_attributes(self):
|
||||
attrs = {}
|
||||
attrs['data-def-lat'], attrs['data-def-lng'] = self.get_default_position().split(';')
|
||||
default_position = self.get_default_position()
|
||||
attrs['data-def-lat'] = default_position['lat']
|
||||
attrs['data-def-lng'] = default_position['lon']
|
||||
if self.get_site_option('map-bounds-top-left'):
|
||||
attrs['data-max-bounds-lat1'], attrs['data-max-bounds-lng1'] = self.get_site_option(
|
||||
'map-bounds-top-left'
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
{% extends "qommon/forms/widget.html" %}
|
||||
|
||||
{% block widget-control %}
|
||||
<input type="hidden" name="{{widget.name}}$latlng" {% if widget.value %}value="{{widget.value}}"{% endif %}>
|
||||
<div id="map-{{widget.get_name_for_id}}" class="qommon-map"
|
||||
{% if widget.readonly %}data-readonly="true"{% endif %}
|
||||
{% if widget.sync_map_and_address_fields %}data-address-sync="true"{% endif %}
|
||||
{% for key, value in widget.map_attributes.items %}{{key}}="{{value}}" {% endfor %}
|
||||
{% if widget.initial_position %}
|
||||
data-init-lat="{{ widget.initial_position.lat }}"
|
||||
data-init-lng="{{ widget.initial_position.lng }}"
|
||||
{% endif %}
|
||||
{% if not widget.readonly %}data-search-url="{% url 'api-geocoding' %}"{% endif %}
|
||||
{% block widget-control-attributes %}{% endblock %}
|
||||
></div>
|
||||
{% localize off %}
|
||||
<input type="hidden" name="{{widget.name}}$latlng" {% if widget.value %}value="{{widget.value.lat}};{{widget.value.lon}}"{% endif %}>
|
||||
<div id="map-{{widget.get_name_for_id}}" class="qommon-map"
|
||||
{% if widget.readonly %}data-readonly="true"{% endif %}
|
||||
{% if widget.sync_map_and_address_fields %}data-address-sync="true"{% endif %}
|
||||
{% for key, value in widget.map_attributes.items %}{{key}}="{{value}}" {% endfor %}
|
||||
{% if widget.initial_position %}
|
||||
data-init-lat="{{ widget.initial_position.lat }}"
|
||||
data-init-lng="{{ widget.initial_position.lng }}"
|
||||
{% endif %}
|
||||
{% if not widget.readonly %}data-search-url="{% url 'api-geocoding' %}"{% endif %}
|
||||
{% block widget-control-attributes %}{% endblock %}
|
||||
></div>
|
||||
{% endlocalize %}
|
||||
{% endblock %}
|
||||
|
|
60
wcs/sql.py
60
wcs/sql.py
|
@ -82,6 +82,7 @@ SQL_TYPE_MAPPING = {
|
|||
'numeric': 'numeric',
|
||||
'file': 'bytea',
|
||||
'date': 'date',
|
||||
'map': 'jsonb',
|
||||
'items': 'text[]',
|
||||
'table': 'text[][]',
|
||||
'table-select': 'text[][]',
|
||||
|
@ -5120,7 +5121,60 @@ def get_period_total(
|
|||
# latest migration, number + description (description is not used
|
||||
# programmaticaly but will make sure git conflicts if two migrations are
|
||||
# separately added with the same number)
|
||||
SQL_LEVEL = (106, 'add context column to logged_errors table')
|
||||
SQL_LEVEL = (107, 'change map columns to jsonb')
|
||||
|
||||
|
||||
@atomic
|
||||
def migrate_map_data_type():
|
||||
conn, cur = get_connection_and_cursor()
|
||||
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
|
||||
had_changes = False
|
||||
for formdef in FormDef.select() + CardDef.select():
|
||||
table_name = get_formdef_table_name(formdef)
|
||||
cur.execute(
|
||||
'''SELECT column_name, udt_name FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = %s''',
|
||||
(table_name,),
|
||||
)
|
||||
existing_fields = {x[0]: x[1] for x in cur.fetchall()}
|
||||
|
||||
for field in formdef.get_all_fields():
|
||||
if field.key != 'map':
|
||||
continue
|
||||
database_field_id = get_field_id(field)
|
||||
if existing_fields.get(database_field_id) == 'jsonb':
|
||||
# already ok
|
||||
continue
|
||||
sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar')
|
||||
cur.execute(
|
||||
'''ALTER TABLE %s ADD COLUMN %s %s''' % (table_name, 'tmp_' + database_field_id, sql_type)
|
||||
)
|
||||
cur.execute(
|
||||
'''UPDATE %(table_name)s
|
||||
SET tmp_%(column)s = jsonb_build_object(
|
||||
'lat', split_part(%(column)s, ';', 1)::float,
|
||||
'lon', split_part(%(column)s, ';', 2)::float)
|
||||
WHERE %(column)s IS NOT NULL
|
||||
AND %(column)s != '' '''
|
||||
% {'table_name': table_name, 'column': database_field_id}
|
||||
)
|
||||
cur.execute('''ALTER TABLE %s DROP COLUMN %s CASCADE''' % (table_name, database_field_id))
|
||||
cur.execute(
|
||||
'''ALTER TABLE %s RENAME COLUMN tmp_%s TO %s'''
|
||||
% (table_name, database_field_id, database_field_id)
|
||||
)
|
||||
had_changes = True
|
||||
|
||||
if had_changes:
|
||||
# views have to be recreated
|
||||
migrate_views(conn, cur)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
|
||||
def migrate_global_views(conn, cur):
|
||||
|
@ -5321,6 +5375,9 @@ def migrate():
|
|||
# 84: add application tables
|
||||
Application.do_table()
|
||||
ApplicationElement.do_table()
|
||||
if sql_level < 107:
|
||||
# 107: migrate map field data type
|
||||
migrate_map_data_type()
|
||||
if sql_level < 52:
|
||||
# 2: introduction of formdef_id in views
|
||||
# 5: add concerned_roles_array, is_at_endpoint and fts to views
|
||||
|
@ -5421,7 +5478,6 @@ def migrate():
|
|||
init_global_table(conn, cur)
|
||||
for formdef in FormDef.select():
|
||||
do_formdef_tables(formdef, rebuild_views=False, rebuild_global_views=False)
|
||||
|
||||
if sql_level < 71:
|
||||
# 71: python datasource migration
|
||||
set_reindex('python_ds_migration', 'needed', conn=conn, cur=cur)
|
||||
|
|
|
@ -1684,6 +1684,13 @@ class LazyFieldVarMap(LazyFieldVarStructured):
|
|||
def inspect_keys(self):
|
||||
return ['lat', 'lon'] if self.get_field_var_value() else []
|
||||
|
||||
def __str__(self):
|
||||
# backward compatibility
|
||||
value = self._data.get(self._field.id)
|
||||
if not value:
|
||||
return ''
|
||||
return '%(lat)s;%(lon)s' % value
|
||||
|
||||
|
||||
class LazyFieldVarLiveSequenceItem(LazyFieldVarLiveCardMixin):
|
||||
def __init__(self, field, data, idx):
|
||||
|
|
Loading…
Reference in New Issue