0001-fields-add-marker-selection-on-map-field-47066.patch
tests/test_form_pages.py | ||
---|---|---|
10 | 10 |
import time |
11 | 11 |
import zipfile |
12 | 12 |
import base64 |
13 |
from webtest import Upload, Hidden |
|
13 |
from webtest import Upload, Hidden, Radio
|
|
14 | 14 |
import mock |
15 | 15 |
import xml.etree.ElementTree as ET |
16 | 16 | |
... | ... | |
4427 | 4427 |
assert formdef.data_class().count() == 1 |
4428 | 4428 |
data_id = formdef.data_class().select()[0].id |
4429 | 4429 |
data = formdef.data_class().get(data_id) |
4430 |
assert data.data == {'0': {'lat': 1.234, 'lon': -1.234}, '1': 'bla'} |
|
4430 |
assert data.data == {'0': {'lat': 1.234, 'lon': -1.234}, '0_display': '1.234;-1.234', '1': 'bla'}
|
|
4431 | 4431 | |
4432 | 4432 | |
4433 | 4433 |
def test_map_field_migration(pub): |
... | ... | |
4497 | 4497 |
assert formdef.data_class().count() == 1 |
4498 | 4498 |
data_id = formdef.data_class().select()[0].id |
4499 | 4499 |
data = formdef.data_class().get(data_id) |
4500 |
assert data.data == {'1': {'lat': 1.234, 'lon': -1.234}, '3': 'bar'} |
|
4500 |
assert data.data == {'1': {'lat': 1.234, 'lon': -1.234}, '1_display': '1.234;-1.234', '3': 'bar'} |
|
4501 | ||
4502 | ||
4503 |
def test_form_map_data_source(pub, http_requests): |
|
4504 |
NamedDataSource.wipe() |
|
4505 |
data_source = NamedDataSource(name='foobar') |
|
4506 |
data_source.data_source = { |
|
4507 |
'type': 'geojson', |
|
4508 |
'value': 'http://remote.example.net/geojson', |
|
4509 |
} |
|
4510 |
data_source.id_property = 'id' |
|
4511 |
data_source.label_template_property = '{{ text }}' |
|
4512 |
data_source.store() |
|
4513 | ||
4514 |
formdef = create_formdef() |
|
4515 |
formdef.fields = [ |
|
4516 |
fields.MapField(id='1', label='map', data_source={'type': 'foobar'}), |
|
4517 |
] |
|
4518 |
formdef.store() |
|
4519 |
formdef.data_class().wipe() |
|
4520 |
resp = get_app(pub).get('/test/') |
|
4521 |
assert resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-url'] == '/api/geojson/foobar' |
|
4522 |
assert resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-radio-name'] == 'f1$marker_id' |
|
4523 |
# simulate qommon.map.js that will create radio inputs |
|
4524 |
resp.form.fields['f1$marker_id'] = [Radio(form=resp.form, tag='input', name='f1$marker_id', pos=5)] |
|
4525 |
resp.form.fields['f1$marker_id'][0].options.append(('1', False, None)) |
|
4526 |
resp.form.fields['f1$marker_id'][0].options.append(('2', False, None)) |
|
4527 |
resp.form.fields['f1$marker_id'][0].optionPositions.append(5) |
|
4528 |
resp.form.fields['f1$marker_id'][0].optionPositions.append(6) |
|
4529 |
resp.form.field_order.append(('f1$marker_id', resp.form.fields['f1$marker_id'][0])) |
|
4530 |
resp.form['f1$marker_id'].value = '1' # click on marker |
|
4531 |
resp.form['f1$latlng'] = '1;2' # set via js |
|
4532 |
resp = resp.form.submit('submit') |
|
4533 |
assert 'Check values then click submit.' in resp |
|
4534 |
# selected option is displayed as readonly: |
|
4535 |
assert resp.pyquery('input[type=text][value=foo][readonly]') |
|
4536 |
assert resp.pyquery('input[type=hidden][name="f1$marker_id"][value="1"]') |
|
4537 |
assert int(float(resp.pyquery('.qommon-map')[0].attrib['data-init-lat'])) == 1 |
|
4538 |
assert int(float(resp.pyquery('.qommon-map')[0].attrib['data-init-lng'])) == 2 |
|
4539 |
resp = resp.form.submit('submit') |
|
4540 |
resp = resp.follow() |
|
4541 |
assert 'The form has been recorded' in resp |
|
4542 |
assert resp.pyquery('input[type=text][value=foo][readonly]') |
|
4543 |
assert int(float(resp.pyquery('.qommon-map')[0].attrib['data-init-lat'])) == 1 |
|
4544 |
assert int(float(resp.pyquery('.qommon-map')[0].attrib['data-init-lng'])) == 2 |
|
4545 |
assert formdef.data_class().count() == 1 |
|
4546 |
data_id = formdef.data_class().select()[0].id |
|
4547 |
data = formdef.data_class().get(data_id) |
|
4548 |
assert data.data == {'1': {'lat': 1.0, 'lon': 2.0, 'marker_id': '1'}, '1_display': 'foo'} |
|
4501 | 4549 | |
4502 | 4550 | |
4503 | 4551 |
def test_form_middle_session_change(pub): |
tests/test_formdata.py | ||
---|---|---|
20 | 20 |
from wcs.carddef import CardDef |
21 | 21 |
from wcs.categories import Category |
22 | 22 |
from wcs.conditions import Condition |
23 |
from wcs.data_sources import NamedDataSource |
|
23 | 24 |
from wcs.formdef import FormDef |
24 | 25 |
from wcs.formdata import Evolution |
25 | 26 |
from wcs.roles import Role |
... | ... | |
628 | 629 |
}, |
629 | 630 |
} |
630 | 631 |
formdata.data['5'].receive([b'hello world']) |
632 |
formdata.data['7_display'] = formdef.fields[7].store_display_value(formdata.data, '7') |
|
631 | 633 |
formdata.geolocations = {'base': {'lat': 1, 'lon': 2}} |
632 | 634 |
formdata.store() |
633 | 635 |
pub.substitutions.feed(pub) |
... | ... | |
762 | 764 |
assert lazy_formdata.tracking_code == tracking_code.id |
763 | 765 | |
764 | 766 | |
767 |
def test_lazy_formdata_map_item_field(pub, http_requests): |
|
768 |
NamedDataSource.wipe() |
|
769 |
data_source = NamedDataSource(name='foobar') |
|
770 |
data_source.data_source = { |
|
771 |
'type': 'geojson', |
|
772 |
'value': 'http://remote.example.net/geojson', |
|
773 |
} |
|
774 |
data_source.id_property = 'id' |
|
775 |
data_source.label_template_property = '{{ text }}' |
|
776 |
data_source.store() |
|
777 | ||
778 |
FormDef.wipe() |
|
779 |
formdef = FormDef() |
|
780 |
formdef.name = 'foobar map item' |
|
781 |
formdef.fields = [ |
|
782 |
fields.MapField(id='14', label='map-item', varname='map_item', data_source={'type': 'foobar'}), |
|
783 |
] |
|
784 |
formdef.store() |
|
785 |
formdef.data_class().wipe() |
|
786 |
formdata = formdef.data_class()() |
|
787 |
formdata.just_created() |
|
788 |
formdata.data = { |
|
789 |
'14': {'lat': 1, 'lon': 2, 'marker_id': '1'}, # map-item |
|
790 |
} |
|
791 |
formdata.data['14_display'] = formdef.fields[0].store_display_value(formdata.data, '14') |
|
792 |
formdata.store() |
|
793 |
pub.substitutions.feed(pub) |
|
794 |
pub.substitutions.feed(formdef) |
|
795 |
pub.substitutions.feed(formdata) |
|
796 | ||
797 |
context = pub.substitutions.get_context_variables(mode='lazy') |
|
798 |
assert Template('{{form_var_map_item}}').render(context) == 'foo' |
|
799 |
assert Template('{{form_var_map_item_lat}}').render(context) == '1' |
|
800 |
assert Template('{{form_var_map_item_lon}}').render(context) == '2' |
|
801 |
assert Template('{{form_var_map_item_marker_id}}').render(context) == '1' |
|
802 | ||
803 | ||
765 | 804 |
def test_lazy_formdata_duplicated_varname(pub, variable_test_data): |
766 | 805 |
formdef = FormDef.select()[0] |
767 | 806 |
formdata = FormDef.select()[0].data_class().select()[0] |
... | ... | |
1148 | 1187 |
pub.substitutions.reset() |
1149 | 1188 |
pub.substitutions.feed(formdef) |
1150 | 1189 |
with pub.substitutions.temporary_feed(formdata, force_mode=mode): |
1151 |
assert WorkflowStatusItem.compute('=form_var_map["lat"]', raises=True) == 2 |
|
1152 | 1190 |
assert WorkflowStatusItem.compute('{{ form_var_map }}', raises=True) == '2;4' |
1153 | 1191 |
assert WorkflowStatusItem.compute('{{ form_var_map|split:";"|first }}', raises=True) == '2' |
1154 | 1192 |
assert WorkflowStatusItem.compute('=form_var_map_lat', raises=True) == 2 |
... | ... | |
1166 | 1204 |
assert WorkflowStatusItem.compute('{{ form_var_map|distance:form|floatformat }}', raises=True) == '248515.5' |
1167 | 1205 | |
1168 | 1206 |
formdata.data['7'] = None |
1207 |
formdata.data['7_display'] = None |
|
1169 | 1208 |
formdata.store() |
1170 | 1209 |
pub.substitutions.reset() |
1171 | 1210 |
pub.substitutions.feed(formdef) |
tests/test_workflows.py | ||
---|---|---|
2965 | 2965 | |
2966 | 2966 |
formdata = formdef.data_class()() |
2967 | 2967 |
formdata.data = {'2': {'lat': 48.8337085, 'lon': 2.3233693}} |
2968 |
formdata.data['2_display'] = formdef.fields[0].store_display_value(formdata.data, '2') |
|
2968 | 2969 |
formdata.just_created() |
2969 | 2970 |
formdata.store() |
2970 | 2971 |
pub.substitutions.feed(formdata) |
... | ... | |
2995 | 2996 | |
2996 | 2997 |
formdata = formdef.data_class()() |
2997 | 2998 |
formdata.data = {'2': {'lat': 48.8337085, 'lon': 2.3233693}} |
2999 |
formdata.data['2_display'] = formdef.fields[0].store_display_value(formdata.data, '2') |
|
2998 | 3000 |
formdata.just_created() |
2999 | 3001 |
formdata.store() |
3000 | 3002 |
pub.substitutions.feed(formdata) |
... | ... | |
3008 | 3010 |
assert int(formdata.geolocations['base']['lon']) == 2 |
3009 | 3011 | |
3010 | 3012 |
formdata.data = {'2': {'lat': 48.8337085, 'lon': 3.3233693}} |
3013 |
formdata.data['2_display'] = formdef.fields[0].store_display_value(formdata.data, '2') |
|
3011 | 3014 |
item.perform(formdata) |
3012 | 3015 |
assert int(formdata.geolocations['base']['lat']) == 48 |
3013 | 3016 |
assert int(formdata.geolocations['base']['lon']) == 3 |
3014 | 3017 | |
3015 | 3018 |
formdata.data = {'2': {'lat': 48.8337085, 'lon': 4.3233693}} |
3019 |
formdata.data['2_display'] = formdef.fields[0].store_display_value(formdata.data, '2') |
|
3016 | 3020 |
item.overwrite = False |
3017 | 3021 |
item.perform(formdata) |
3018 | 3022 |
assert int(formdata.geolocations['base']['lat']) == 48 |
tests/utilities.py | ||
---|---|---|
1 | 1 |
import email.header |
2 | 2 |
import email.parser |
3 |
import json |
|
3 | 4 |
import os |
4 | 5 |
import tempfile |
5 | 6 |
import random |
... | ... | |
326 | 327 | |
327 | 328 |
with open(os.path.join(os.path.dirname(__file__), 'idp_metadata.xml')) as fd: |
328 | 329 |
metadata = fd.read() |
330 |
geojson = { |
|
331 |
'features': [ |
|
332 |
{'properties': {'id': '1', 'text': 'foo'}, |
|
333 |
'geometry': {'type': 'Point', 'coordinates': [1, 2]} |
|
334 |
}, |
|
335 |
{'properties': {'id': '2', 'text': 'bar'}, |
|
336 |
'geometry': {'type': 'Point', 'coordinates': [3, 4]} |
|
337 |
} |
|
338 |
] |
|
339 |
} |
|
340 | ||
329 | 341 |
status, data, headers = { |
330 | 342 |
'http://remote.example.net/204': (204, None, None), |
331 | 343 |
'http://remote.example.net/400': (400, 'bad request', None), |
... | ... | |
339 | 351 |
'http://remote.example.net/json-err0': (200, '{"data": "foo", "err": 0}', None), |
340 | 352 |
'http://remote.example.net/json-err1': (200, '{"data": "", "err": 1}', None), |
341 | 353 |
'http://remote.example.net/json-list-err1': (200, '{"data": [{"id": "a", "text": "b"}], "err": 1}', None), |
354 |
'http://remote.example.net/geojson': (200, json.dumps(geojson), None), |
|
342 | 355 |
'http://remote.example.net/json-errstr': (200, '{"data": "", "err": "bug"}', None), |
343 | 356 |
'http://remote.example.net/json-errheader0': (200, '{"foo": "bar"}', |
344 | 357 |
{'x-error-code': '0'}), |
wcs/api.py | ||
---|---|---|
37 | 37 |
from wcs.conditions import Condition, ValidationError |
38 | 38 |
from wcs.carddef import CardDef |
39 | 39 |
from wcs.formdef import FormDef |
40 |
from wcs.data_sources import get_object as get_data_source_object |
|
40 | 41 |
from wcs.roles import Role, logged_users_role |
41 | 42 |
from wcs.forms.common import FormStatusPage |
42 | 43 |
import wcs.qommon.storage as st |
... | ... | |
839 | 840 |
return misc.urlopen(url).read() |
840 | 841 | |
841 | 842 | |
843 |
class GeoJsonDirectory(Directory): |
|
844 |
def _q_lookup(self, component): |
|
845 |
try: |
|
846 |
data_source = get_data_source_object({'type': component}) |
|
847 |
except KeyError: |
|
848 |
raise TraversalError() |
|
849 |
get_response().set_content_type('application/json') |
|
850 |
return json.dumps(data_source.get_geojson_data()) |
|
851 | ||
852 | ||
842 | 853 |
class ApiDirectory(Directory): |
843 | 854 |
_q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'), |
844 | 855 |
'formdefs', 'categories', 'user', 'users', 'code', 'autocomplete', |
845 |
'cards'] |
|
856 |
'cards', 'geojson']
|
|
846 | 857 | |
847 | 858 |
cards = ApiCardsDirectory() |
848 | 859 |
forms = ApiFormsDirectory() |
... | ... | |
852 | 863 |
users = ApiUsersDirectory() |
853 | 864 |
code = ApiTrackingCodeDirectory() |
854 | 865 |
autocomplete = AutocompleteDirectory() |
866 |
geojson = GeoJsonDirectory() |
|
855 | 867 | |
856 | 868 |
def roles(self): |
857 | 869 |
get_response().set_content_type('application/json') |
wcs/data_sources.py | ||
---|---|---|
52 | 52 | |
53 | 53 |
class DataSourceSelectionWidget(CompositeWidget): |
54 | 54 |
def __init__(self, name, value=None, allow_jsonp=True, |
55 |
allow_geojson=False, allow_named_sources=True, **kwargs): |
|
55 |
allow_geojson=False, allow_named_sources=True, |
|
56 |
require_configured_geographic_source=False, **kwargs): |
|
56 | 57 |
CompositeWidget.__init__(self, name, value, **kwargs) |
57 | 58 | |
58 | 59 |
if not value: |
... | ... | |
60 | 61 | |
61 | 62 |
options = [] |
62 | 63 |
if allow_named_sources: |
63 |
options.extend([(x.slug, x.name, x.slug) for x in NamedDataSource.select()]) |
|
64 |
from wcs.carddef import CardDef |
|
65 |
options.extend(list(CardDef.get_as_data_source_options())) |
|
64 |
selected_sources = NamedDataSource.select() |
|
65 |
if require_configured_geographic_source: |
|
66 |
selected_sources = [x for x in selected_sources if x.type == 'geojson'] |
|
67 |
options.extend([(x.slug, x.name, x.slug) for x in selected_sources]) |
|
68 |
if not require_configured_geographic_source: |
|
69 |
from wcs.carddef import CardDef |
|
70 |
options.extend(list(CardDef.get_as_data_source_options())) |
|
66 | 71 |
options.sort(key=lambda x: misc.simplify(x[1])) |
67 | 72 | |
68 | 73 |
options.insert(0, (None, _('None'), None)) |
69 |
options.append(('json', _('JSON URL'), 'json')) |
|
70 |
if allow_jsonp: |
|
71 |
options.append(('jsonp', _('JSONP URL'), 'jsonp')) |
|
72 |
if allow_geojson: |
|
73 |
options.append(('geojson', _('GeoJSON URL'), 'geojson')) |
|
74 |
options.append(('formula', _('Python Expression'), 'python')) |
|
74 |
if not require_configured_geographic_source: |
|
75 |
options.append(('json', _('JSON URL'), 'json')) |
|
76 |
if allow_jsonp: |
|
77 |
options.append(('jsonp', _('JSONP URL'), 'jsonp')) |
|
78 |
if allow_geojson: |
|
79 |
options.append(('geojson', _('GeoJSON URL'), 'geojson')) |
|
80 |
options.append(('formula', _('Python Expression'), 'python')) |
|
75 | 81 | |
76 | 82 |
self.add(SingleSelectWidget, 'type', options=options, value=value.get('type'), |
77 | 83 |
attrs={'data-dynamic-display-parent': 'true'}) |
... | ... | |
116 | 122 |
return tupled_items |
117 | 123 | |
118 | 124 | |
119 |
def request_json_items(url, data_source):
|
|
125 |
def get_json_from_url(url, data_source):
|
|
120 | 126 |
url = sign_url_auto_orig(url) |
121 | 127 |
geojson = data_source.get('type') == 'geojson' |
122 | 128 |
try: |
... | ... | |
141 | 147 |
else: |
142 | 148 |
get_logger().warning('Error reading JSON data source output (%s)' % str(e)) |
143 | 149 |
return None |
150 |
return entries |
|
151 | ||
152 | ||
153 |
def request_json_items(url, data_source): |
|
154 |
geojson = data_source.get('type') == 'geojson' |
|
155 |
entries = get_json_from_url(url, data_source) |
|
156 |
if entries is None: |
|
157 |
return None |
|
144 | 158 |
items = [] |
145 | 159 |
if geojson: |
146 | 160 |
id_property = data_source.get('id_property') or 'id' |
... | ... | |
419 | 433 |
get_session().get_data_source_query_url_token(self.get_json_query_url())) |
420 | 434 |
return None |
421 | 435 | |
436 |
def get_geojson_url(self): |
|
437 |
assert self.type == 'geojson' |
|
438 |
return '/api/geojson/%s' % self.slug |
|
439 | ||
440 |
def get_geojson_data(self): |
|
441 |
url = self.data_source.get('value').strip() |
|
442 |
if Template.is_template_string(url): |
|
443 |
vars = get_publisher().substitutions.get_context_variables(mode='lazy') |
|
444 |
url = get_variadic_url(url, vars) |
|
445 | ||
446 |
request = get_request() |
|
447 |
if hasattr(request, 'datasources_cache') and url in request.datasources_cache: |
|
448 |
return request.datasources_cache[url] |
|
449 | ||
450 |
cache_duration = 0 |
|
451 |
if self.cache_duration: |
|
452 |
cache_duration = int(self.cache_duration) |
|
453 | ||
454 |
if cache_duration: |
|
455 |
cache_key = 'geojson-data-source-%s' % force_str(hashlib.md5(force_bytes(url)).hexdigest()) |
|
456 |
from django.core.cache import cache |
|
457 |
data = cache.get(cache_key) |
|
458 |
if data is not None: |
|
459 |
return data |
|
460 | ||
461 |
data = get_json_from_url(url, self.data_source) |
|
462 |
id_property = self.id_property or 'id' |
|
463 |
label_template_property = self.label_template_property or '{{ text }}' |
|
464 | ||
465 |
for feature in data['features']: |
|
466 |
feature['properties']['_id'] = feature['properties'][id_property] |
|
467 |
try: |
|
468 |
feature['properties']['_text'] = Template( |
|
469 |
label_template_property).render(feature['properties']) |
|
470 |
except (TemplateSyntaxError, VariableDoesNotExist): |
|
471 |
pass |
|
472 |
if not feature['properties'].get('_text'): |
|
473 |
feature['properties']['_text'] = feature['properties']['_id'] |
|
474 | ||
475 |
if hasattr(request, 'datasources_cache'): |
|
476 |
request.datasources_cache[url] = data |
|
477 |
if cache_duration: |
|
478 |
cache.set(cache_key, data, cache_duration) |
|
479 | ||
480 |
return data |
|
481 | ||
422 | 482 |
def get_value_by_id(self, param_name, param_value): |
423 | 483 |
url = self.data_source.get('value').strip() |
424 | 484 |
if Template.is_template_string(url): |
... | ... | |
465 | 525 |
if self.type == 'json' and self.id_parameter: |
466 | 526 |
value = self.get_value_by_id(self.id_parameter, option_id) |
467 | 527 |
else: |
468 |
structured_items = get_structured_items(self.data_source, mode='lazy') |
|
528 |
structured_items = get_structured_items(self.extended_data_source, mode='lazy')
|
|
469 | 529 |
for item in structured_items: |
470 | 530 |
if str(item['id']) == str(option_id): |
471 | 531 |
value = item |
wcs/fields.py | ||
---|---|---|
544 | 544 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
545 | 545 |
pass |
546 | 546 | |
547 | ||
548 |
def add_to_view_form(self, form, value = None): |
|
549 |
kwargs = {'render_br': False} |
|
547 |
def add_to_view_form(self, form, value=None, display_value=None, **kwargs): |
|
548 |
widget_kwargs = {'render_br': False} |
|
550 | 549 | |
551 | 550 |
self.field_key = 'f%s' % self.id |
552 |
self.perform_more_widget_changes(form, kwargs, False) |
|
551 |
self.perform_more_widget_changes(form, widget_kwargs, False)
|
|
553 | 552 | |
554 | 553 |
for k in self.extra_attributes: |
555 | 554 |
if hasattr(self, k): |
556 |
kwargs[k] = getattr(self, k) |
|
555 |
widget_kwargs[k] = getattr(self, k)
|
|
557 | 556 | |
558 |
if self.widget_class is StringWidget and not 'size' in kwargs and value: |
|
557 |
if self.widget_class is StringWidget and not 'size' in widget_kwargs and value:
|
|
559 | 558 |
# set a size if there is not one already defined, this will be for |
560 | 559 |
# example the case with ItemField |
561 |
kwargs['size'] = len(value) |
|
560 |
widget_kwargs['size'] = len(value)
|
|
562 | 561 | |
563 |
form.add(self.widget_class, self.field_key, title = self.label, |
|
564 |
value = value, readonly = 'readonly', **kwargs) |
|
562 |
if display_value and getattr(self.widget_class, 'allow_display_value', True): |
|
563 |
widget_kwargs['display_value'] = display_value |
|
564 | ||
565 |
form.add(self.widget_class, self.field_key, title=self.label, |
|
566 |
value=value, readonly='readonly', **widget_kwargs) |
|
565 | 567 |
widget = form.get_widget(self.field_key) |
566 | 568 |
widget.transfer_form_value(get_request()) |
567 | 569 |
widget.field = self |
... | ... | |
570 | 572 |
widget.extra_css_class = '%s %s' % (widget.extra_css_class, self.extra_css_class) |
571 | 573 |
else: |
572 | 574 |
widget.extra_css_class = self.extra_css_class |
575 |
return widget |
|
573 | 576 | |
574 | 577 |
def get_display_locations_options(self): |
575 | 578 |
return [('validation', _('Validation Page')), |
... | ... | |
1370 | 1373 |
value = self.convert_value_to_str(value) |
1371 | 1374 |
return WidgetField.add_to_form(self, form, value=value) |
1372 | 1375 | |
1373 |
def add_to_view_form(self, form, value=None): |
|
1376 |
def add_to_view_form(self, form, value=None, **kwargs):
|
|
1374 | 1377 |
value = strftime(misc.date_format(), value) |
1375 |
return super().add_to_view_form(form, value=value) |
|
1378 |
return super().add_to_view_form(form, value=value, **kwargs)
|
|
1376 | 1379 | |
1377 | 1380 |
def get_view_value(self, value, **kwargs): |
1378 | 1381 |
try: |
... | ... | |
1582 | 1585 |
span.text = od_clean_text(force_text(value)) |
1583 | 1586 |
return span |
1584 | 1587 | |
1585 |
def add_to_view_form(self, form, value = None):
|
|
1588 |
def add_to_view_form(self, form, value=None, **kwargs):
|
|
1586 | 1589 |
real_value = value |
1587 | 1590 |
label_value = '' |
1588 | 1591 |
if value is not None: |
... | ... | |
2406 | 2409 |
max_zoom = None |
2407 | 2410 |
default_position = None |
2408 | 2411 |
init_with_geoloc = False |
2412 |
data_source = {} |
|
2409 | 2413 | |
2410 | 2414 |
widget_class = MapWidget |
2411 | 2415 |
extra_attributes = ['initial_zoom', 'min_zoom', 'max_zoom', |
... | ... | |
2434 | 2438 |
form.add(CheckboxWidget, 'init_with_geoloc', |
2435 | 2439 |
title=_('Initialize position using device geolocation'), |
2436 | 2440 |
value=self.init_with_geoloc, required=False) |
2441 |
form.add(data_sources.DataSourceSelectionWidget, 'data_source', |
|
2442 |
value=self.data_source, |
|
2443 |
title=_('Markers data source'), |
|
2444 |
hint=_('This will fill the map with markers from an external source.'), |
|
2445 |
required=False, |
|
2446 |
require_configured_geographic_source=True, |
|
2447 |
advanced=False) |
|
2437 | 2448 | |
2438 | 2449 |
def check_admin_form(self, form): |
2439 | 2450 |
initial_zoom = form.get_widget('initial_zoom').parse() |
... | ... | |
2451 | 2462 |
def get_admin_attributes(self): |
2452 | 2463 |
return WidgetField.get_admin_attributes(self) + ['initial_zoom', |
2453 | 2464 |
'min_zoom', 'max_zoom', 'default_position', |
2454 |
'init_with_geoloc'] |
|
2465 |
'init_with_geoloc', 'data_source'] |
|
2466 | ||
2467 |
def add_to_view_form(self, form, value=None, **kwargs): |
|
2468 |
widget = super().add_to_view_form(form, value=value, **kwargs) |
|
2469 |
if self.data_source: |
|
2470 |
data_source = data_sources.get_object(self.data_source) |
|
2471 |
widget.add_geojson_markers(data_source.get_geojson_url()) |
|
2472 |
return widget |
|
2473 | ||
2474 |
def add_to_form(self, form, value=None): |
|
2475 |
widget = super().add_to_form(form, value=value) |
|
2476 |
if self.data_source: |
|
2477 |
data_source = data_sources.get_object(self.data_source) |
|
2478 |
widget.add_geojson_markers(data_source.get_geojson_url()) |
|
2479 |
return widget |
|
2455 | 2480 | |
2456 | 2481 |
def get_prefill_value(self, user=None, force_string=True): |
2457 | 2482 |
if self.prefill.get('type') != 'string' or not self.prefill.get('value'): |
... | ... | |
2466 | 2491 |
return (coords, False) |
2467 | 2492 | |
2468 | 2493 |
def get_view_value(self, value, **kwargs): |
2469 |
widget = self.widget_class('x%s' % random.random(), value, readonly=True) |
|
2494 |
widget_kwargs = {} |
|
2495 |
if 'value_id' in kwargs: # get raw value |
|
2496 |
widget_kwargs['display_value'] = value |
|
2497 |
value = kwargs.get('value_id') |
|
2498 |
widget = self.widget_class('x%s' % random.random(), value, readonly=True, **widget_kwargs) |
|
2470 | 2499 |
return widget.render_widget_content() |
2471 | 2500 | |
2501 |
def get_view_short_value(self, value, max_len=30, raw_value=None, **kwargs): |
|
2502 |
if self.data_source: |
|
2503 |
# value will be _display value |
|
2504 |
return value or '' |
|
2505 |
return self.get_view_value(value, value_id=raw_value) |
|
2506 | ||
2472 | 2507 |
def get_rst_view_value(self, value, indent=''): |
2473 | 2508 |
if isinstance(value, str): # compatiblity with old pickled data |
2474 | 2509 |
return indent + value |
... | ... | |
2490 | 2525 |
return self.convert_value_from_str(value) |
2491 | 2526 |
return value |
2492 | 2527 | |
2528 |
def get_display_value(self, value): |
|
2529 |
if not self.data_source: |
|
2530 |
if value: |
|
2531 |
# backward compatibility |
|
2532 |
return '%(lat)s;%(lon)s' % value |
|
2533 |
return '' |
|
2534 |
data_source = data_sources.get_object(self.data_source) |
|
2535 |
if data_source is None or value is None: |
|
2536 |
return '' |
|
2537 |
return data_source.get_display_value(value.get('marker_id')) |
|
2538 | ||
2539 |
def store_display_value(self, data, field_id): |
|
2540 |
value = data.get(field_id) |
|
2541 |
return self.get_display_value(value) |
|
2542 | ||
2493 | 2543 |
def get_structured_value(self, data): |
2494 | 2544 |
return self.get_json_value(data.get(self.id)) |
2495 | 2545 |
wcs/formdata.py | ||
---|---|---|
676 | 676 |
if max_length is not None: |
677 | 677 |
# if max_length is set the target is a backoffice listing/table, |
678 | 678 |
# return an html value, appropriately shortened. |
679 |
field_value = self.data.get('%s_display' % field.id, field_value)
|
|
680 |
return field.get_view_short_value(field_value, max_length)
|
|
679 |
display_value = self.data.get('%s_display' % field.id, field_value)
|
|
680 |
return field.get_view_short_value(display_value, max_length, raw_value=field_value)
|
|
681 | 681 |
else: |
682 | 682 |
# otherwise return the actual "raw" field value |
683 | 683 |
return field_value |
... | ... | |
1172 | 1172 |
continue |
1173 | 1173 | |
1174 | 1174 |
value, value_details = f.get_value_info(self.data) |
1175 |
if value is None and not (f.required and include_unset_required_fields):
|
|
1175 |
if (value is None and not value_details) and not (f.required and include_unset_required_fields):
|
|
1176 | 1176 |
continue |
1177 | 1177 | |
1178 | 1178 |
current_page_fields.append({'field': f, 'value': value, 'value_details': value_details}) |
wcs/formdef.py | ||
---|---|---|
665 | 665 |
field.add_to_view_form(form, value) |
666 | 666 |
form.widgets.append(HtmlWidget(htmltext('</div>'))) |
667 | 667 |
else: |
668 |
field.add_to_view_form(form, value) |
|
668 |
kwargs = {} |
|
669 |
if field.store_display_value: |
|
670 |
kwargs['display_value'] = dict.get(field.id + '_display') |
|
671 |
field.add_to_view_form(form, value, **kwargs) |
|
669 | 672 | |
670 | 673 |
if visible_contents: |
671 | 674 |
form.widgets.append(HtmlWidget(htmltext('</div></div>'))) |
wcs/forms/common.py | ||
---|---|---|
491 | 491 |
r += htmltext('<div class="%s">' % ' '.join(css_classes)) |
492 | 492 |
r += htmltext('<span class="label">%s</span> ') % f.label |
493 | 493 |
value, value_details = field_value_info['value'], field_value_info['value_details'] |
494 |
if value is None: |
|
494 |
if value is None and not value_details:
|
|
495 | 495 |
r += htmltext('<div class="value"><i>%s</i></div>') % _('Not set') |
496 | 496 |
else: |
497 | 497 |
r += htmltext('<div class="value">') |
wcs/qommon/form.py | ||
---|---|---|
2361 | 2361 | |
2362 | 2362 |
class MapWidget(CompositeWidget): |
2363 | 2363 |
template_name = 'qommon/forms/widgets/map.html' |
2364 |
geojson_markers_url = None |
|
2364 | 2365 | |
2365 | 2366 |
def __init__(self, name, value=None, **kwargs): |
2367 |
self.display_value = kwargs.pop('display_value', None) |
|
2366 | 2368 |
CompositeWidget.__init__(self, name, value, **kwargs) |
2367 | 2369 |
latlng_value = None |
2368 | 2370 |
if isinstance(value, str): # legacy data type |
... | ... | |
2390 | 2392 |
def transfer_form_value(self, request): |
2391 | 2393 |
request.form[self.get_widget('latlng').name] = self.point2str(self.value) |
2392 | 2394 | |
2395 |
def add_geojson_markers(self, url): |
|
2396 |
self.geojson_markers_url = url |
|
2397 |
self.add(HiddenWidget, 'marker_id') |
|
2398 | ||
2393 | 2399 |
def initial_position(self): |
2394 | 2400 |
if isinstance(self.value, str): |
2395 | 2401 |
return {'lat': self.value.split(';')[0], |
... | ... | |
2408 | 2414 |
if self.value: |
2409 | 2415 |
lat, lon = self.value.split(';') |
2410 | 2416 |
self.value = misc.normalize_geolocation({'lat': lat, 'lon': lon}) |
2417 |
if self.geojson_markers_url: |
|
2418 |
self.value['marker_id'] = self.get('marker_id') |
|
2411 | 2419 | |
2412 | 2420 | |
2413 | 2421 |
class HiddenErrorWidget(HiddenWidget): |
wcs/qommon/static/js/qommon.map.js | ||
---|---|---|
46 | 46 |
var hidden = $(this).prev(); |
47 | 47 |
map.marker = null; |
48 | 48 |
var latlng; |
49 |
var initial_marker_id = $map_widget.data('markers-initial-id'); |
|
49 | 50 |
if ($map_widget.data('init-lat')) { |
50 | 51 |
latlng = [$map_widget.data('init-lat'), $map_widget.data('init-lng')] |
51 |
map.marker = L.marker(latlng); |
|
52 |
map.marker.addTo(map); |
|
52 |
if (typeof initial_marker_id == 'undefined') { |
|
53 |
// if markers are used they will appear via their input widget |
|
54 |
map.marker = L.marker(latlng); |
|
55 |
map.marker.addTo(map); |
|
56 |
} |
|
53 | 57 |
} else if ($map_widget.data('def-lat')) { |
54 | 58 |
latlng = [$map_widget.data('def-lat'), $map_widget.data('def-lng')] |
55 | 59 |
} else { |
... | ... | |
67 | 71 |
} else { |
68 | 72 |
map.addControl(gps_control); |
69 | 73 |
} |
70 |
if (! $map_widget.data('readonly')) { |
|
74 |
if ($map_widget.data('markers-url')) { |
|
75 |
var radio_name = $map_widget.data('markers-radio-name'); |
|
76 |
$map_widget.on('change', 'input[name="' + radio_name + '"]', function() { |
|
77 |
var $radio = $(this); |
|
78 |
if ($radio.is(':checked')) { |
|
79 |
hidden.val($radio.data('lat') + ';' + $radio.data('lng')); |
|
80 |
hidden.trigger('change'); |
|
81 |
} |
|
82 |
}); |
|
83 |
$.getJSON($map_widget.data('markers-url')).done( |
|
84 |
function(data) { |
|
85 |
var geo_json = L.geoJson(data, { |
|
86 |
pointToLayer: function (feature, latlng) { |
|
87 |
var $label = $('<label>', { |
|
88 |
title: feature.properties._text |
|
89 |
}); |
|
90 |
var $radio = $('<input>', { |
|
91 |
value: feature.properties._id, |
|
92 |
name: radio_name, |
|
93 |
type: 'radio', |
|
94 |
'data-lat': latlng.lat, |
|
95 |
'data-lng': latlng.lng |
|
96 |
}); |
|
97 |
if (typeof initial_marker_id !== 'undefined' && feature.properties._id == initial_marker_id) { |
|
98 |
$radio.attr('checked', 'checked'); |
|
99 |
} |
|
100 |
$label.append($radio); |
|
101 |
$label.append($('<span></span>')); |
|
102 |
var div_marker = L.divIcon({ |
|
103 |
className: 'item-marker', |
|
104 |
html: '<div>' + $label.prop('outerHTML') + '</div>' |
|
105 |
}); |
|
106 |
return L.marker(latlng, {icon: div_marker}); |
|
107 |
} |
|
108 |
}); |
|
109 |
geo_json.addTo(map); |
|
110 |
} |
|
111 |
); |
|
112 |
} else if ($map_widget.data('readonly') && initial_marker_id) { |
|
113 |
// readonly and marker |
|
114 |
console.log('plop'); |
|
115 |
map.marker = L.marker(latlng); |
|
116 |
map.marker.addTo(map); |
|
117 |
} |
|
118 | ||
119 |
if (! $map_widget.data('readonly') && ! $map_widget.data('markers-url')) { |
|
71 | 120 |
map.on('click', function(e) { |
72 | 121 |
$map_widget.trigger('set-geolocation', e.latlng); |
73 | 122 |
}); |
wcs/qommon/templates/qommon/forms/widgets/map.html | ||
---|---|---|
2 | 2 | |
3 | 3 |
{% block widget-control %} |
4 | 4 |
{% localize off %} |
5 |
{% if widget.display_value and widget.readonly %} |
|
6 |
<div class="display-value"><input type="text" |
|
7 |
value="{{widget.display_value}}" readonly size="{{widget.display_value|length}}"></div> |
|
8 |
{% endif %} |
|
5 | 9 |
<input type="hidden" name="{{widget.name}}$latlng" {% if widget.value %}value="{{widget.value.lat}};{{widget.value.lon}}"{% endif %}> |
6 | 10 |
<div id="map-{{widget.name}}" class="qommon-map" |
7 | 11 |
{% if widget.readonly %}data-readonly="true"{% endif %} |
... | ... | |
11 | 15 |
data-init-lat="{{ widget.initial_position.lat }}" |
12 | 16 |
data-init-lng="{{ widget.initial_position.lng }}" |
13 | 17 |
{% endif %} |
18 |
{% if widget.geojson_markers_url %} |
|
19 |
{% if not widget.readonly %} |
|
20 |
data-markers-url="{{ widget.geojson_markers_url }}" |
|
21 |
data-markers-radio-name="{{widget.name}}$marker_id" |
|
22 |
{% endif %} |
|
23 |
{% if widget.value.marker_id %} |
|
24 |
data-markers-initial-id="{{ widget.value.marker_id }}" |
|
25 |
{% endif %} |
|
26 |
{% endif %} |
|
14 | 27 |
></div> |
28 |
{% if widget.readonly and widget.geojson_markers_url and widget.value.marker_id %} |
|
29 |
<input type="hidden" name="{{widget.name}}$marker_id" value="{{ widget.value.marker_id }}"> |
|
30 |
{% endif %} |
|
15 | 31 |
{% endlocalize %} |
32 | ||
33 |
{% if widget.geojson_markers_url %} |
|
34 |
<style> |
|
35 |
.item-marker input + span { |
|
36 |
position: relative; |
|
37 |
z-index: 1000; |
|
38 |
margin-top: -55px; |
|
39 |
margin-left: -5px; |
|
40 |
display: block; |
|
41 |
width: 25px; |
|
42 |
height: 41px; |
|
43 |
background: url(/static/images/blank-marker-icon.png); |
|
44 |
} |
|
45 |
.item-marker input:checked + span { |
|
46 |
background: url(/static/xstatic/images/marker-icon.png); |
|
47 |
} |
|
48 |
.MapWidget div.display-value { |
|
49 |
max-width: 100%; |
|
50 |
} |
|
51 |
</style> |
|
52 |
{% endif %} |
|
16 | 53 |
{% endblock %} |
wcs/variables.py | ||
---|---|---|
840 | 840 |
return self._data.get(self._field.id).split(*args, **kwargs) |
841 | 841 | |
842 | 842 |
def inspect_keys(self): |
843 |
if self._field.data_source: |
|
844 |
return ['lat', 'lon', 'marker_id'] |
|
843 | 845 |
return ['lat', 'lon'] |
844 | 846 | |
847 |
@property |
|
848 |
def marker_id(self): |
|
849 |
value = self._data.get(self._field.id) |
|
850 |
if value: |
|
851 |
return value.get('marker_id') |
|
852 | ||
845 | 853 |
def __str__(self): |
846 |
# backward compatibility |
|
854 |
if self._field.data_source: |
|
855 |
value = self._data.get(self._field.id + '_display') |
|
856 |
if value: |
|
857 |
return value |
|
858 |
# "lat;lon" for backward compatibility |
|
847 | 859 |
value = self._data.get(self._field.id) |
848 | 860 |
if not value: |
849 | 861 |
return '' |
850 |
- |