Skip to content

Commit cc1625c

Browse files
committedJan 31, 2018
[bugfix][attrtable] Convert comma to dot for floating point input
This fixes an unreported bug that without detecting an invalid input when using a comma as a decimal separator silently converts the entered value to NULL. Since locale support in QGIS is in its early stages we convert commas to dots within the validator, this is common practice in almost all web applications where you can enter a comma instead of a dot and the conversion appears while you digit. This comes with brand new tests for QgsFieldValidator. Bonus: small fix in sipify.
1 parent cbd0ece commit cc1625c

File tree

6 files changed

+114
-3
lines changed

6 files changed

+114
-3
lines changed
 

‎python/gui/qgsfieldvalidator.sip.in‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class QgsFieldValidator : QValidator
1919
QgsFieldValidator( QObject *parent, const QgsField &field, const QString &defaultValue, const QString &dateFormat = "yyyy-MM-dd" );
2020
~QgsFieldValidator();
2121

22-
virtual State validate( QString &s /Constrained/, int &i /In,Out/ ) const;
22+
virtual State validate( QString &s /Constrained,In,Out/, int &i /In,Out/ ) const;
2323

2424
virtual void fixup( QString &s /Constrained/ ) const;
2525

‎src/gui/qgsfieldvalidator.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ QgsFieldValidator::QgsFieldValidator( QObject *parent, const QgsField &field, co
8484
mValidator = nullptr;
8585
}
8686

87-
QgsSettings settings;
8887
mNullValue = QgsApplication::nullRepresentation();
8988
}
9089

@@ -113,6 +112,12 @@ QValidator::State QgsFieldValidator::validate( QString &s, int &i ) const
113112
// delegate to the child validator if any
114113
if ( mValidator )
115114
{
115+
// force to use the '.' as a decimal point or in case we are using QDoubleValidator
116+
// we can get a valid number with a comma depending on current locale
117+
// ... but this will fail subsequently when converted from string to double and
118+
// becomes a NULL!
119+
if ( mField.type() == QVariant::Double )
120+
s = s.replace( ',', '.' );
116121
QValidator::State result = mValidator->validate( s, i );
117122
return result;
118123
}

‎src/gui/qgsfieldvalidator.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class GUI_EXPORT QgsFieldValidator : public QValidator
3838
QgsFieldValidator( QObject *parent, const QgsField &field, const QString &defaultValue, const QString &dateFormat = "yyyy-MM-dd" );
3939
~QgsFieldValidator() override;
4040

41-
State validate( QString &s SIP_CONSTRAINED, int &i SIP_INOUT ) const override;
41+
State validate( QString &s SIP_CONSTRAINED SIP_INOUT, int &i SIP_INOUT ) const override;
4242
void fixup( QString &s SIP_CONSTRAINED ) const override;
4343

4444
QString dateFormat() const { return mDateFormat; }

‎tests/src/python/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ ADD_PYTHON_TEST(PyQgsAuthManagerProxy test_authmanager_proxy.py)
196196
ADD_PYTHON_TEST(PyQgsAuthSettingsWidget test_authsettingswidget.py)
197197
ADD_PYTHON_TEST(PyQgsAuxiliaryStorage test_qgsauxiliarystorage.py)
198198
ADD_PYTHON_TEST(PyQgsAuthManagerOgr test_authmanager_ogr.py)
199+
ADD_PYTHON_TEST(PyQgsFieldValidator test_qgsfieldvalidator.py)
199200

200201
IF (NOT WIN32)
201202
ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsFieldValidator.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Alessandro Pasotti'
10+
__date__ = '31/01/2018'
11+
__copyright__ = 'Copyright 2018, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis # NOQA
16+
17+
from qgis.PyQt.QtCore import QVariant
18+
from qgis.PyQt.QtGui import QValidator
19+
from qgis.core import QgsVectorLayer
20+
from qgis.gui import QgsFieldValidator
21+
from qgis.testing import start_app, unittest
22+
from utilities import unitTestDataPath
23+
24+
TEST_DATA_DIR = unitTestDataPath()
25+
26+
27+
start_app()
28+
29+
30+
class TestQgsFieldValidator(unittest.TestCase):
31+
32+
def setUp(self):
33+
"""Run before each test."""
34+
testPath = TEST_DATA_DIR + '/' + 'bug_17878.gpkg|layername=bug_17878'
35+
self.vl = QgsVectorLayer(testPath, "test_data", "ogr")
36+
assert self.vl.isValid()
37+
38+
def tearDown(self):
39+
"""Run after each test."""
40+
pass
41+
42+
def test_validator(self):
43+
# Test the double
44+
"""
45+
Expected results from validate
46+
QValidator::Invalid 0 The string is clearly invalid.
47+
QValidator::Intermediate 1 The string is a plausible intermediate value.
48+
QValidator::Acceptable 2 The string is acceptable as a final result; i.e. it is valid.
49+
"""
50+
51+
double_field = self.vl.fields()[self.vl.fields().indexFromName('double_field')]
52+
self.assertEqual(double_field.precision(), 0) # this is what the provider reports :(
53+
self.assertEqual(double_field.length(), 0) # not set
54+
self.assertEqual(double_field.type(), QVariant.Double)
55+
56+
validator = QgsFieldValidator(None, double_field, '0.0', '')
57+
58+
def _test(value, expected):
59+
ret = validator.validate(value, 0)
60+
self.assertEqual(ret[0], expected, value)
61+
if value:
62+
self.assertEqual(validator.validate('-' + value, 0)[0], expected, '-' + value)
63+
# Check the decimal comma separator has been properly transformed
64+
if expected != QValidator.Invalid:
65+
self.assertEqual(ret[1], value.replace(',', '.'))
66+
67+
# Valid
68+
_test('0.1234', QValidator.Acceptable)
69+
_test('0,1234', QValidator.Acceptable)
70+
_test('12345.1234e+123', QValidator.Acceptable)
71+
_test('12345.1234e-123', QValidator.Acceptable)
72+
_test('12345,1234e+123', QValidator.Acceptable)
73+
_test('12345,1234e-123', QValidator.Acceptable)
74+
_test('', QValidator.Acceptable)
75+
76+
# Out of range
77+
_test('12345.1234e+823', QValidator.Intermediate)
78+
_test('12345.1234e-823', QValidator.Intermediate)
79+
_test('12345,1234e+823', QValidator.Intermediate)
80+
_test('12345,1234e-823', QValidator.Intermediate)
81+
82+
# Invalid
83+
_test('12345-1234', QValidator.Invalid)
84+
_test('onetwothree', QValidator.Invalid)
85+
86+
int_field = self.vl.fields()[self.vl.fields().indexFromName('int_field')]
87+
self.assertEqual(int_field.precision(), 0) # this is what the provider reports :(
88+
self.assertEqual(int_field.length(), 0) # not set
89+
self.assertEqual(int_field.type(), QVariant.Int)
90+
91+
validator = QgsFieldValidator(None, int_field, '0', '')
92+
93+
# Valid
94+
_test('0', QValidator.Acceptable)
95+
_test('1234', QValidator.Acceptable)
96+
_test('', QValidator.Acceptable)
97+
98+
# Invalid
99+
_test('12345-1234', QValidator.Invalid)
100+
_test('12345.1234', QValidator.Invalid)
101+
_test('onetwothree', QValidator.Invalid)
102+
103+
104+
if __name__ == '__main__':
105+
unittest.main()

‎tests/testdata/bug_17878.gpkg‎

116 KB
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.