-
Notifications
You must be signed in to change notification settings - Fork 85
Expand file tree
/
Copy pathfieldvalidator.cpp
More file actions
282 lines (233 loc) 路 9.77 KB
/
Copy pathfieldvalidator.cpp
File metadata and controls
282 lines (233 loc) 路 9.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "fieldvalidator.h"
#include "attributedata.h"
#include "featurelayerpair.h"
#include "mixedattributevalue.h"
#include <qgsfield.h>
#include <qgsvectorlayerutils.h>
#include <QRegularExpression>
#include <QLocale>
QString FieldValidator::numberInvalid() { return tr( "Value must be a number" ); };
QString FieldValidator::numberUpperBoundReached() { return tr( "Value must be less than or equal to %1" ); };
QString FieldValidator::numberLowerBoundReached() { return tr( "Value must be greater than or equal to %1" ); };
QString FieldValidator::numberExceedingVariableLimits() { return tr( "Value is too large" );};
QString FieldValidator::numberMustBeInt() { return tr( "Field can not contain decimal places" );};
QString FieldValidator::textTooLong() { return tr( "Can not be longer than %1 characters" );};
QString FieldValidator::softNotNullFailed() { return tr( "Field should not be empty" );};
QString FieldValidator::hardNotNullFailed() { return tr( "Field must not be empty" );};
QString FieldValidator::softUniqueFailed() { return tr( "Value should be unique" );};
QString FieldValidator::hardUniqueFailed() { return tr( "Value must be unique" );};
QString FieldValidator::softExpressionFailed() { return tr( "Unmet QGIS expression constraint" );};
QString FieldValidator::hardExpressionFailed() { return tr( "Unmet QGIS expression constraint" );};
QString FieldValidator::genericValidationFailed() { return tr( "Not a valid value" );};
FieldValidator::FieldValidator( QObject *parent ) :
QObject( parent )
{
}
FieldValidator::ValidationStatus FieldValidator::validate( const FeatureLayerPair &pair, const FormItem &item, QString &validationMessage )
{
validationMessage = QString();
// Ignore fid field and relations
if ( item.type() != FormItem::Field || item.name() == QStringLiteral( "fid" ) )
{
return Valid;
}
ValidationStatus state = Valid;
const QgsField field = item.field();
QVariant value = item.rawValue();
// We also ignore Mixed values when multi-editing, as those fields' values will not be saved to the edited features
if ( value.userType() == qMetaTypeId<MixedAttributeValue>() )
return Valid;
const bool isNumeric = item.editorWidgetType() == QStringLiteral( "Range" ) || field.isNumeric();
if ( isNumeric )
{
state = validateNumericField( item, value, validationMessage );
}
else if ( item.editorWidgetType() == QStringLiteral( "TextEdit" ) )
{
state = validateTextField( item, value, validationMessage );
}
else
{
state = validateGenericField( item, value, validationMessage );
}
if ( state != Valid )
return state;
// Continue to check hard and soft QGIS constraints
QStringList errors;
const bool hardConstraintSatisfied = QgsVectorLayerUtils::validateAttribute( pair.layer(), pair.feature(), item.fieldIndex(), errors, QgsFieldConstraints::ConstraintStrengthHard );
if ( !hardConstraintSatisfied )
{
validationMessage = constructConstraintValidationMessage( item, errors );
return Error;
}
errors.clear();
const bool softConstraintSatisfied = QgsVectorLayerUtils::validateAttribute( pair.layer(), pair.feature(), item.fieldIndex(), errors, QgsFieldConstraints::ConstraintStrengthSoft );
if ( !softConstraintSatisfied )
{
validationMessage = constructConstraintValidationMessage( item, errors );
return Warning;
}
return Valid;
}
FieldValidator::ValidationStatus FieldValidator::validateTextField( const FormItem &item, QVariant &value, QString &validationMessage )
{
const QgsField field = item.field();
// Check if the text is not too long for the field
if ( field.length() > 0 )
{
const int vLength = static_cast<int>( value.toString().length() );
if ( vLength > field.length() )
{
validationMessage = textTooLong().arg( field.length() );
return Error;
}
}
if ( !field.convertCompatible( value ) )
{
validationMessage = genericValidationFailed();
return Error;
}
return Valid;
}
FieldValidator::ValidationStatus FieldValidator::validateNumericField( const FormItem &item, QVariant &value, QString &validationMessage )
{
const QgsField field = item.field();
if ( value.isNull() )
{
return Valid;
}
// in Qt 6 isNull() does not return true for true if the variant contained an object
// of a builtin type with an isNull() method that returned true for that object.
// So isNull() for QVariant( QString() ) will return false, and we need to handle this
// separately.
if ( value.userType() == QVariant::String && value.toString().isEmpty() )
{
return Valid;
}
QString errorMessage;
const bool containsDecimals = value.toString().contains( QLocale().decimalPoint() ) || value.toString().contains( "." );
if ( !field.convertCompatible( value, &errorMessage ) )
{
if ( errorMessage.contains( QStringLiteral( "too large" ) ) )
{
validationMessage = numberExceedingVariableLimits();
}
else
{
validationMessage = numberInvalid();
}
return Error;
}
else if ( containsDecimals && field.type() != QMetaType::Type::Double )
{
/* ConverCompatible check passes for doubles written into int fields,
* however, the value would not be saved and would get replaced by zero,
* so we need to handle it here and set invalid state for such input.
*/
validationMessage = numberMustBeInt();
return Error;
}
const bool isRangeEditable = item.editorWidgetType() == QStringLiteral( "Range" ) &&
item.editorWidgetConfig().value( QStringLiteral( "Style" ) ) == QStringLiteral( "SpinBox" );
// Check min/max range
if ( isRangeEditable )
{
const double min = item.editorWidgetConfig().value( "Min" ).toDouble();
const double max = item.editorWidgetConfig().value( "Max" ).toDouble();
const double val = value.toDouble();
if ( val < min )
{
validationMessage = numberLowerBoundReached().arg( min );
return Error;
}
else if ( val > max )
{
validationMessage = numberUpperBoundReached().arg( max );
return Error;
}
}
return Valid;
}
FieldValidator::ValidationStatus FieldValidator::validateGenericField( const FormItem &item, QVariant &value, QString &validationMessage )
{
const QgsField field = item.field();
if ( !field.convertCompatible( value ) )
{
validationMessage = genericValidationFailed();
return Error;
}
return Valid;
}
QString FieldValidator::constructConstraintValidationMessage( const FormItem &item, const QStringList &unmetConstraints )
{
/* BEWARE: this method uses QStringList of errors coming from QGIS validation function
* and does string comparison on them. These error strings are, however, set for translation
* in QGIS, so comparisons would fail if we would want to translate QGIS strings too.
*/
const QgsField field = item.field();
const QgsFieldConstraints &fldCons = field.constraints();
QStringList validationMessages;
const bool hasNotNullConstraint = fldCons.constraints() & QgsFieldConstraints::ConstraintNotNull;
const bool notNullViolated = unmetConstraints.contains( QStringLiteral( "value is NULL" ) );
if ( hasNotNullConstraint && notNullViolated )
{
const QgsFieldConstraints::ConstraintStrength strength = fldCons.constraintStrength( QgsFieldConstraints::ConstraintNotNull );
if ( strength == QgsFieldConstraints::ConstraintStrengthHard )
{
validationMessages << hardNotNullFailed();
}
else if ( strength == QgsFieldConstraints::ConstraintStrengthSoft )
{
validationMessages << softNotNullFailed();
}
}
const bool hasUniqueConstraint = fldCons.constraints() & QgsFieldConstraints::ConstraintUnique;
const bool uniqueViolated = unmetConstraints.contains( QStringLiteral( "value is not unique" ) );
if ( hasUniqueConstraint && uniqueViolated )
{
const QgsFieldConstraints::ConstraintStrength strength = fldCons.constraintStrength( QgsFieldConstraints::ConstraintUnique );
if ( strength == QgsFieldConstraints::ConstraintStrengthHard )
{
validationMessages << hardUniqueFailed();
}
else if ( strength == QgsFieldConstraints::ConstraintStrengthSoft )
{
validationMessages << softUniqueFailed();
}
}
const bool hasExpressionConstrain = fldCons.constraints() & QgsFieldConstraints::ConstraintExpression;
const bool expressionViolated = !unmetConstraints.filter( QRegularExpression( "(parser error|evaluation error|check failed)" ) ).empty();
if ( hasExpressionConstrain && expressionViolated )
{
const QgsFieldConstraints::ConstraintStrength strength = fldCons.constraintStrength( QgsFieldConstraints::ConstraintExpression );
const bool containsDescription = !fldCons.constraintDescription().isEmpty();
if ( containsDescription )
{
validationMessages << fldCons.constraintDescription();
}
else
{
if ( strength == QgsFieldConstraints::ConstraintStrengthHard )
{
validationMessages << hardExpressionFailed();
}
else if ( strength == QgsFieldConstraints::ConstraintStrengthSoft )
{
validationMessages << softExpressionFailed();
}
}
}
if ( !validationMessages.empty() )
{
return validationMessages.join( QStringLiteral( "\n" ) ); // each message on new line
}
return {};
}