backoffice: use actual data for item(s) field filters (#35703)
This commit is contained in:
parent
ee4d46947a
commit
7955c2980a
|
@ -694,6 +694,144 @@ def test_backoffice_bool_filter(pub):
|
|||
assert resp.text.count('<td>Yes</td>') == 0
|
||||
assert resp.text.count('<td>No</td>') > 0
|
||||
|
||||
def test_backoffice_item_filter(pub):
|
||||
create_superuser(pub)
|
||||
create_environment(pub)
|
||||
formdef = FormDef.get_by_urlname('form-title')
|
||||
formdef.fields.append(fields.ItemField(id='4', label='4th field', type='item',
|
||||
items=['a', 'b', 'c', 'd'],
|
||||
display_locations=['validation', 'summary', 'listings']))
|
||||
formdef.store()
|
||||
|
||||
for i, formdata in enumerate(formdef.data_class().select()):
|
||||
if i%4 == 0:
|
||||
formdata.data['4'] = 'a'
|
||||
formdata.data['4_display'] = 'a'
|
||||
elif i%4 == 1:
|
||||
formdata.data['4'] = 'b'
|
||||
formdata.data['4_display'] = 'b'
|
||||
elif i%4 == 2:
|
||||
formdata.data['4'] = 'd'
|
||||
formdata.data['4_display'] = 'd'
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp.form['filter-4'].checked = True
|
||||
resp = resp.form.submit()
|
||||
|
||||
assert resp.form['filter-4-value'].value == ''
|
||||
|
||||
resp.form['filter-4-value'].value = 'a'
|
||||
resp = resp.form.submit()
|
||||
assert resp.text.count('<td>a</td>') > 0
|
||||
assert resp.text.count('<td>b</td>') == 0
|
||||
assert resp.text.count('<td>d</td>') == 0
|
||||
|
||||
resp.form['filter-4-value'].value = 'b'
|
||||
resp = resp.form.submit()
|
||||
assert resp.text.count('<td>a</td>') == 0
|
||||
assert resp.text.count('<td>b</td>') > 0
|
||||
assert resp.text.count('<td>d</td>') == 0
|
||||
|
||||
if not pub.is_using_postgresql():
|
||||
# in pickle all options are always displayed
|
||||
resp.form['filter-4-value'].value = 'c'
|
||||
resp = resp.form.submit()
|
||||
assert resp.text.count('<td>a</td>') == 0
|
||||
assert resp.text.count('<td>b</td>') == 0
|
||||
assert resp.text.count('<td>c</td>') == 0
|
||||
|
||||
else:
|
||||
# in postgresql, option 'c' is never used so not even listed
|
||||
with pytest.raises(ValueError):
|
||||
resp.form['filter-4-value'].value = 'c'
|
||||
|
||||
# check json view used to fill select filters from javascript
|
||||
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&' + resp.request.query_string)
|
||||
assert [x['id'] for x in resp2.json['data']] == ['a', 'b', 'd']
|
||||
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&_search=d&' + resp.request.query_string)
|
||||
assert [x['id'] for x in resp2.json['data']] == ['d']
|
||||
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=7&' + resp.request.query_string, status=404)
|
||||
|
||||
for status in ('all', 'waiting', 'pending', 'done', 'accepted'):
|
||||
resp.form['filter'] = status
|
||||
resp = resp.form.submit()
|
||||
resp2 = app.get(resp.request.path + 'filter-options?filter_field_id=4&' + resp.request.query_string)
|
||||
if status == 'accepted':
|
||||
assert [x['id'] for x in resp2.json['data']] == []
|
||||
else:
|
||||
assert [x['id'] for x in resp2.json['data']] == ['a', 'b', 'd']
|
||||
|
||||
def test_backoffice_item_double_filter(pub):
|
||||
if not pub.is_using_postgresql():
|
||||
pytest.skip('this requires SQL')
|
||||
return
|
||||
create_superuser(pub)
|
||||
create_environment(pub)
|
||||
formdef = FormDef.get_by_urlname('form-title')
|
||||
formdef.fields.append(fields.ItemField(id='4', label='4th field', type='item',
|
||||
items=['a', 'b', 'c', 'd'],
|
||||
display_locations=['validation', 'summary', 'listings']))
|
||||
formdef.fields.append(fields.ItemField(id='5', label='5th field', type='item',
|
||||
items=['A', 'B', 'C', 'D'],
|
||||
display_locations=['validation', 'summary', 'listings']))
|
||||
formdef.store()
|
||||
|
||||
for i, formdata in enumerate(formdef.data_class().select()):
|
||||
if i%4 == 0:
|
||||
formdata.data['4'] = 'a'
|
||||
formdata.data['4_display'] = 'a'
|
||||
formdata.data['5'] = 'A'
|
||||
formdata.data['5_display'] = 'A'
|
||||
elif i%4 == 1:
|
||||
formdata.data['4'] = 'a'
|
||||
formdata.data['4_display'] = 'a'
|
||||
formdata.data['5'] = 'B'
|
||||
formdata.data['5_display'] = 'B'
|
||||
elif i%4 == 2:
|
||||
formdata.data['4'] = 'a'
|
||||
formdata.data['4_display'] = 'a'
|
||||
formdata.data['5'] = 'C'
|
||||
formdata.data['5_display'] = 'C'
|
||||
elif i%4 == 3:
|
||||
formdata.data['4'] = 'b'
|
||||
formdata.data['4_display'] = 'b'
|
||||
formdata.data['5'] = 'B'
|
||||
formdata.data['5_display'] = 'B'
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp.form['filter-4'].checked = True
|
||||
resp.form['filter-5'].checked = True
|
||||
resp = resp.form.submit()
|
||||
|
||||
assert resp.form['filter-4-value'].value == ''
|
||||
assert resp.form['filter-5-value'].value == ''
|
||||
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b']
|
||||
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'A', 'B', 'C']
|
||||
|
||||
resp.form['filter-4-value'].value = 'a'
|
||||
resp = resp.form.submit()
|
||||
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b']
|
||||
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'A', 'B', 'C']
|
||||
|
||||
resp.form['filter-4-value'].value = 'b'
|
||||
resp = resp.form.submit()
|
||||
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b']
|
||||
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'B']
|
||||
|
||||
resp.form['filter-5-value'].value = 'B'
|
||||
resp = resp.form.submit()
|
||||
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b']
|
||||
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'B']
|
||||
|
||||
resp.form['filter-4-value'].value = ''
|
||||
resp = resp.form.submit()
|
||||
assert [x[0] for x in resp.form['filter-4-value'].options] == ['', 'a', 'b']
|
||||
assert [x[0] for x in resp.form['filter-5-value'].options] == ['', 'A', 'B', 'C']
|
||||
|
||||
def test_backoffice_items_filter(pub):
|
||||
create_superuser(pub)
|
||||
create_environment(pub)
|
||||
|
@ -734,12 +872,17 @@ def test_backoffice_items_filter(pub):
|
|||
assert resp.text.count('<td>a</td>') == 0
|
||||
assert resp.text.count('<td>b, d</td>') > 0
|
||||
|
||||
resp.form['filter-4-value'].value = 'c'
|
||||
resp = resp.form.submit()
|
||||
assert resp.text.count('<td>a, b</td>') == 0
|
||||
assert resp.text.count('<td>a</td>') == 0
|
||||
assert resp.text.count('<td>b, d</td>') == 0
|
||||
assert resp.text.count('data-link') == 0 # no rows
|
||||
if pub.is_using_postgresql():
|
||||
# option 'c' is never used so not even listed
|
||||
with pytest.raises(ValueError):
|
||||
resp.form['filter-4-value'].value = 'c'
|
||||
else:
|
||||
resp.form['filter-4-value'].value = 'c'
|
||||
resp = resp.form.submit()
|
||||
assert resp.text.count('<td>a, b</td>') == 0
|
||||
assert resp.text.count('<td>a</td>') == 0
|
||||
assert resp.text.count('<td>b, d</td>') == 0
|
||||
assert resp.text.count('data-link') == 0 # no rows
|
||||
|
||||
def test_backoffice_csv(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -789,6 +932,8 @@ def test_backoffice_csv(pub):
|
|||
|
||||
resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
|
||||
resp = resp.forms[0].submit()
|
||||
resp.forms[0]['filter-2-value'] = 'baz'
|
||||
resp = resp.forms[0].submit()
|
||||
resp_csv = resp.click('Export as CSV File')
|
||||
assert len(resp_csv.text.splitlines()) == 9
|
||||
assert 'Created' in resp_csv.text.splitlines()[0]
|
||||
|
|
|
@ -50,7 +50,7 @@ from ..qommon import errors
|
|||
from ..qommon import ods
|
||||
from ..qommon.form import *
|
||||
from ..qommon.storage import (Equal, NotEqual, LessOrEqual, GreaterOrEqual, Or,
|
||||
Intersects, ILike, FtsMatch, Contains, Null)
|
||||
Intersects, ILike, FtsMatch, Contains, Null, NotNull)
|
||||
|
||||
from wcs.api_utils import get_user_from_api_query_string
|
||||
from wcs.conditions import Condition
|
||||
|
@ -998,7 +998,7 @@ class ManagementDirectory(Directory):
|
|||
|
||||
class FormPage(Directory):
|
||||
_q_exports = ['', 'csv', 'stats', 'xls', 'ods', 'json', 'export', 'map',
|
||||
'geojson']
|
||||
'geojson', ('filter-options', 'filter_options')]
|
||||
|
||||
def __init__(self, component):
|
||||
try:
|
||||
|
@ -1049,7 +1049,63 @@ class FormPage(Directory):
|
|||
return ('start', 'end')
|
||||
return ()
|
||||
|
||||
def get_filter_sidebar(self, selected_filter=None, mode='listing'):
|
||||
def get_item_filter_options(self, filter_field, selected_filter, criterias):
|
||||
criterias = (criterias or [])[:]
|
||||
# remove potential filter on self (Equal for item, Intersects for items)
|
||||
criterias = [x for x in criterias if not (isinstance(x, (Equal, Intersects)) and
|
||||
x.attribute == 'f%s' % filter_field.id)]
|
||||
# apply other filters
|
||||
criterias.append(Null('anonymised'))
|
||||
if selected_filter == 'all':
|
||||
criterias.append(NotEqual('status', 'draft'))
|
||||
elif selected_filter in ('waiting', 'pending'):
|
||||
statuses = ['wf-%s' % x.id for x in self.formdef.workflow.get_not_endpoint_status()]
|
||||
criterias.append(Contains('status', statuses))
|
||||
if selected_filter == 'waiting':
|
||||
user = get_request().user
|
||||
user_roles = [logged_users_role().id] + user.get_roles()
|
||||
criterias.append(Intersects('actions_roles_array', user_roles))
|
||||
elif selected_filter == 'done':
|
||||
statuses = ['wf-%s' % x.id for x in self.formdef.workflow.get_endpoint_status()]
|
||||
criterias.append(Contains('status', statuses))
|
||||
else:
|
||||
criterias.append(Equal('status', 'wf-%s' % selected_filter))
|
||||
criterias.append(NotNull('f%s' % filter_field.id))
|
||||
options = self.formdef.data_class().select_distinct(
|
||||
['f%s' % filter_field.id, 'f%s_display' % filter_field.id],
|
||||
clause=criterias)
|
||||
|
||||
if filter_field.type == 'items':
|
||||
# unnest key/values
|
||||
exploded_options = {}
|
||||
for option_keys, option_label in options:
|
||||
for option_key, option_label in zip(option_keys, option_label.split(', ')):
|
||||
exploded_options[option_key] = option_label
|
||||
options = list(sorted(exploded_options.items(), key=lambda x: x[1]))
|
||||
|
||||
return options
|
||||
|
||||
def filter_options(self):
|
||||
get_request().is_json_marker = True
|
||||
field_id = get_request().form.get('filter_field_id')
|
||||
for filter_field in self.get_formdef_fields():
|
||||
if filter_field.id == field_id:
|
||||
break
|
||||
else:
|
||||
raise errors.TraversalError()
|
||||
|
||||
selected_filter = self.get_filter_from_query()
|
||||
criterias = self.get_criterias_from_query()
|
||||
options = self.get_item_filter_options(filter_field, selected_filter, criterias)
|
||||
if get_request().form.get('_search'): # select2
|
||||
term = get_request().form.get('_search')
|
||||
if term:
|
||||
options = [x for x in options if term.lower() in x[1].lower()]
|
||||
options = options[:15]
|
||||
get_response().set_content_type('application/json')
|
||||
return json.dumps({'err': 0, 'data': [{'id': x[0], 'text': x[1]} for x in options]})
|
||||
|
||||
def get_filter_sidebar(self, selected_filter=None, mode='listing', query=None, criterias=None):
|
||||
r = TemplateIO(html=True)
|
||||
|
||||
waitpoint_status = self.formdef.workflow.get_waitpoint_status()
|
||||
|
@ -1119,22 +1175,48 @@ class FormPage(Directory):
|
|||
|
||||
elif filter_field.type in ('item', 'items'):
|
||||
filter_field.required = False
|
||||
options = filter_field.get_options()
|
||||
if options:
|
||||
if len(options[0]) == 2:
|
||||
|
||||
if get_publisher().is_using_postgresql():
|
||||
# Get options from existing formdatas.
|
||||
# This allows for options that don't appear anymore in the
|
||||
# data source to be listed (for example because the field
|
||||
# is using a parametrized URL depending on unavailable
|
||||
# variables, or simply returning different results now).
|
||||
display_mode = 'select'
|
||||
if filter_field.type == 'item' and filter_field.get_display_mode() == 'autocomplete':
|
||||
display_mode = 'select2'
|
||||
|
||||
if display_mode == 'select':
|
||||
options = self.get_item_filter_options(
|
||||
filter_field, selected_filter, criterias)
|
||||
options = [(x[0], x[1], x[0]) for x in options]
|
||||
options.insert(0, (None, '', ''))
|
||||
options.insert(0, (None, '', ''))
|
||||
attrs = {'data-refresh-options': str(filter_field.id)}
|
||||
else:
|
||||
current_filter = get_request().form.get('filter-%s-value' % filter_field.id)
|
||||
options = [(current_filter, '', current_filter or '')]
|
||||
attrs = {'data-remote-options': str(filter_field.id)}
|
||||
get_response().add_javascript(['jquery.js', '../../i18n.js', 'qommon.forms.js', 'select2.js'])
|
||||
get_response().add_css_include('../js/select2/select2.css')
|
||||
|
||||
r += SingleSelectWidget(filter_field_key, title=filter_field.label,
|
||||
options=options, value=filter_field_value,
|
||||
render_br=False).render()
|
||||
render_br=False, attrs=attrs).render()
|
||||
|
||||
else:
|
||||
# There may be no options because the field is using
|
||||
# a jsonp data source, or a json source using a
|
||||
# parametrized URL depending on unavailable variables.
|
||||
#
|
||||
# In that case fall back on a string widget.
|
||||
r += StringWidget(filter_field_key, title=filter_field.label,
|
||||
value=filter_field_value, render_br=False).render()
|
||||
# In pickle environments, get options from data source
|
||||
options = filter_field.get_options()
|
||||
if options:
|
||||
if len(options[0]) == 2:
|
||||
options = [(x[0], x[1], x[0]) for x in options]
|
||||
options.insert(0, (None, '', ''))
|
||||
r += SingleSelectWidget(filter_field_key, title=filter_field.label,
|
||||
options=options, value=filter_field_value,
|
||||
render_br=False).render()
|
||||
else:
|
||||
# and fall back on a string widget if there are none.
|
||||
r += StringWidget(filter_field_key, title=filter_field.label,
|
||||
value=filter_field_value, render_br=False).render()
|
||||
|
||||
elif filter_field.type == 'bool':
|
||||
options = [(None, '', ''), (True, _('Yes'), 'true'), (False, _('No'), 'false')]
|
||||
|
@ -1160,7 +1242,8 @@ class FormPage(Directory):
|
|||
return r.getvalue()
|
||||
|
||||
def get_fields_sidebar(self, selected_filter, fields, offset=None,
|
||||
limit=None, order_by=None, columns_settings_label=None):
|
||||
limit=None, order_by=None, columns_settings_label=None,
|
||||
query=None, criterias=None):
|
||||
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'wcs.listing.js'])
|
||||
|
||||
r = TemplateIO(html=True)
|
||||
|
@ -1186,7 +1269,7 @@ class FormPage(Directory):
|
|||
r += htmltext('<input class="inline-input" name="q">')
|
||||
r += htmltext('<input type="submit" class="side-button" value="%s"/>') % _('Search')
|
||||
|
||||
r += self.get_filter_sidebar(selected_filter=selected_filter)
|
||||
r += self.get_filter_sidebar(selected_filter=selected_filter, query=query, criterias=criterias)
|
||||
|
||||
r += htmltext('<button class="refresh">%s</button>') % _('Refresh')
|
||||
|
||||
|
@ -1421,6 +1504,7 @@ class FormPage(Directory):
|
|||
|
||||
get_response().filter['sidebar'] = self.get_formdata_sidebar(qs) + \
|
||||
self.get_fields_sidebar(selected_filter, fields, limit=limit,
|
||||
query=query, criterias=criterias,
|
||||
offset=offset, order_by=order_by,
|
||||
columns_settings_label=_('Columns Settings'))
|
||||
|
||||
|
|
|
@ -1307,14 +1307,21 @@ class ItemField(WidgetField):
|
|||
return [(x, x) for x in self.items]
|
||||
return []
|
||||
|
||||
def perform_more_widget_changes(self, form, kwargs, edit=True):
|
||||
data_source = data_sources.get_object(self.data_source)
|
||||
def get_display_mode(self, data_source=None):
|
||||
if not data_source:
|
||||
data_source = data_sources.get_object(self.data_source)
|
||||
|
||||
if data_source and data_source.type == 'jsonp':
|
||||
# a source defined as JSONP can only be used in autocomplete mode
|
||||
self.display_mode = 'autocomplete'
|
||||
return 'autocomplete'
|
||||
|
||||
if self.display_mode == 'autocomplete' and data_source and data_source.can_jsonp():
|
||||
return self.display_mode
|
||||
|
||||
def perform_more_widget_changes(self, form, kwargs, edit=True):
|
||||
data_source = data_sources.get_object(self.data_source)
|
||||
display_mode = self.get_display_mode(data_source)
|
||||
|
||||
if display_mode == 'autocomplete' and data_source and data_source.can_jsonp():
|
||||
self.url = kwargs['url'] = data_source.get_jsonp_url()
|
||||
self.widget_class = JsonpSingleSelectWidget
|
||||
return
|
||||
|
@ -1330,7 +1337,7 @@ class ItemField(WidgetField):
|
|||
kwargs['options'] = self.get_options()
|
||||
if not kwargs.get('options'):
|
||||
kwargs['options'] = [(None, '---')]
|
||||
if self.display_mode == 'radio':
|
||||
if display_mode == 'radio':
|
||||
self.widget_class = RadiobuttonsWidget
|
||||
if type(kwargs['options'][0]) is str:
|
||||
first_items = [x for x in kwargs['options'][:3]]
|
||||
|
@ -1340,7 +1347,7 @@ class ItemField(WidgetField):
|
|||
if len(kwargs['options']) > 3 or length_first_items > 40:
|
||||
# TODO: absence/presence of delimitor should be an option
|
||||
kwargs['delim'] = htmltext('<br />')
|
||||
elif self.display_mode == 'autocomplete':
|
||||
elif display_mode == 'autocomplete':
|
||||
kwargs['select2'] = True
|
||||
|
||||
def get_display_value(self, value):
|
||||
|
|
|
@ -1101,7 +1101,15 @@ class FormData(StorableObject):
|
|||
except KeyError:
|
||||
# give direct access to values from the data dictionary
|
||||
if attr[0] == 'f':
|
||||
return self.__dict__['data'][attr[1:]]
|
||||
field_id = attr[1:]
|
||||
if field_id in self.__dict__['data']:
|
||||
return self.__dict__['data'][field_id]
|
||||
# if field id is not in data dictionary it may still be a valid
|
||||
# field, never initialized, check requested field id against
|
||||
# existing fields ids.
|
||||
formdef_fields = self.formdef.get_all_fields()
|
||||
if field_id in [x.id for x in formdef_fields]:
|
||||
return None
|
||||
raise AttributeError(attr)
|
||||
|
||||
# don't pickle _formdef cache
|
||||
|
|
|
@ -38,7 +38,6 @@ class FormDefUI(object):
|
|||
partial_display = False
|
||||
using_postgresql = get_publisher().is_using_postgresql()
|
||||
|
||||
|
||||
if not items:
|
||||
if offset and not limit:
|
||||
limit = int(get_publisher().get_site_option('default-page-size') or 20)
|
||||
|
|
|
@ -166,6 +166,28 @@ $(document).on('backoffice-filter-change', function(event, listing_settings) {
|
|||
if (window.history) {
|
||||
window.history.replaceState(null, null, pathname + '?' + listing_settings.qs);
|
||||
}
|
||||
/* refresh dynamic filters */
|
||||
$('[data-refresh-options]').each(function(idx, elem) {
|
||||
var $select = $(elem);
|
||||
var current_value = $select.val();
|
||||
var filter_path = pathname + 'filter-options?filter_field_id=' + $(elem).data('refresh-options') + '&' + listing_settings.qs;
|
||||
$.ajax({
|
||||
url: filter_path,
|
||||
success: function(data) {
|
||||
$select.empty();
|
||||
var $option = $('<option></option>', {value: ''});
|
||||
$option.appendTo($select);
|
||||
for (var i=0; i<data.data.length; i++) {
|
||||
var $option = $('<option></option>', {value: data.data[i].id, text: data.data[i].text});
|
||||
if (data.data[i].id == current_value) {
|
||||
$option.attr('selected', 'selected');
|
||||
}
|
||||
$option.appendTo($select);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* makes sure activity and disabled-during-submit are removed */
|
||||
$('#more-user-links, #listing, #statistics').removeClass('activity');
|
||||
$('form').removeClass('disabled-during-submit');
|
||||
|
@ -236,6 +258,41 @@ $(function() {
|
|||
return false;
|
||||
});
|
||||
|
||||
/* set filter options from server (select2) */
|
||||
$('[data-remote-options]').each(function(idx, elem) {
|
||||
var filter_field_id = $(elem).data('remote-options');
|
||||
var options = {
|
||||
language: {
|
||||
errorLoading: function() { return WCS_I18N.s2_errorloading; },
|
||||
noResults: function () { return WCS_I18N.s2_nomatches; },
|
||||
inputTooShort: function (input, min) { return WCS_I18N.s2_tooshort; },
|
||||
loadingMore: function () { return WCS_I18N.s2_loadmore; },
|
||||
searching: function () { return WCS_I18N.s2_searching; },
|
||||
},
|
||||
placeholder: '',
|
||||
allowClear: true,
|
||||
minimumInputLength: 1,
|
||||
ajax: {
|
||||
url: function() {
|
||||
var pathname = window.location.pathname.replace(/^\/+/, '/');
|
||||
var filter_settings = $('form#listing-settings').serialize();
|
||||
return pathname + 'filter-options?filter_field_id=' + filter_field_id + '&' + filter_settings;
|
||||
},
|
||||
dataType: 'json',
|
||||
data: function(params) {
|
||||
var query = {
|
||||
_search: params.term,
|
||||
}
|
||||
return query;
|
||||
},
|
||||
processResults: function (data, params) {
|
||||
return {results: data.data};
|
||||
},
|
||||
},
|
||||
};
|
||||
$(elem).select2(options);
|
||||
});
|
||||
|
||||
$('button.pdf').click(function() {
|
||||
if (window.location.href.indexOf('?') == -1) {
|
||||
window.location = window.location + '?pdf=on';
|
||||
|
|
16
wcs/sql.py
16
wcs/sql.py
|
@ -1142,7 +1142,21 @@ class SqlMixin(object):
|
|||
return objects
|
||||
return list(objects)
|
||||
|
||||
|
||||
@classmethod
|
||||
@guard_postgres
|
||||
def select_distinct(cls, columns, clause=None):
|
||||
conn, cur = get_connection_and_cursor()
|
||||
sql_statement = 'SELECT DISTINCT ON (%s) %s FROM %s' % (columns[0], ', '.join(columns), cls._table_name)
|
||||
where_clauses, parameters, func_clause = parse_clause(clause)
|
||||
assert not func_clause
|
||||
if where_clauses:
|
||||
sql_statement += ' WHERE ' + ' AND '.join(where_clauses)
|
||||
sql_statement += ' ORDER BY %s' % columns[0]
|
||||
cur.execute(sql_statement, parameters)
|
||||
values = [x for x in cur.fetchall()]
|
||||
conn.commit()
|
||||
cur.close()
|
||||
return values
|
||||
|
||||
def get_sql_dict_from_data(self, data, formdef):
|
||||
sql_dict = {}
|
||||
|
|
Loading…
Reference in New Issue