Passed
Push — master ( d8e2ec...90ae0b )
by Jordi
10:07 queued 04:19
created

bika.lims.validators   F

Complexity

Total Complexity 190

Size/Duplication

Total Lines 1384
Duplicated Lines 10.91 %

Importance

Changes 0
Metric Value
wmc 190
eloc 869
dl 151
loc 1384
rs 1.731
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
F build.bika.lims.validators.AnalysisSpecificationsValidator.validate_service() 0 35 14
F build.bika.lims.validators.InterimFieldsValidator.__call__() 24 119 25
B build.bika.lims.validators.ReflexRuleValidator.__call__() 0 33 5
B build.bika.lims.validators.UniqueFieldValidator.__call__() 0 38 7
A build.bika.lims.validators.ReferenceValuesValidator.__call__() 0 17 3
A build.bika.lims.validators.get_record_value() 0 8 3
A build.bika.lims.validators.ImportValidator.__call__() 0 15 2
C build.bika.lims.validators.ServiceKeywordValidator.__call__() 0 49 9
C build.bika.lims.validators.ReferenceValuesValidator.validate_service() 0 26 9
A build.bika.lims.validators.UniqueFieldValidator.query_parent_objects() 0 27 3
A build.bika.lims.validators.StandardIDValidator.__call__() 0 18 2
B build.bika.lims.validators.RestrictedCategoriesValidator.__call__() 0 36 8
D build.bika.lims.validators.UncertaintiesValidator.__call__() 0 73 12
B build.bika.lims.validators.IBANvalidator.__call__() 0 30 5
A build.bika.lims.validators.DurationValidator.__call__() 0 15 3
A build.bika.lims.validators.AnalysisSpecificationsValidator.__call__() 0 27 4
F build.bika.lims.validators.CoordinateValidator.__call__() 36 80 19
A build.bika.lims.validators.InvoiceBatch_EndDate_Validator.__call__() 0 16 4
A build.bika.lims.validators.NoWhiteSpaceValidator.__call__() 0 9 3
A build.bika.lims.validators.ResultOptionsValidator.__call__() 0 25 4
A build.bika.lims.validators.IdentifierTypeAttributesValidator.__call__() 14 14 3
A build.bika.lims.validators.NIBvalidator.__call__() 0 20 2
B build.bika.lims.validators.FormulaValidator.__call__() 0 53 7
A build.bika.lims.validators.UniqueFieldValidator.get_parent_objects() 0 6 1
A build.bika.lims.validators.IdentifierValidator.__call__() 14 14 3
A build.bika.lims.validators.PercentValidator.__call__() 20 20 4
A build.bika.lims.validators.PrePreservationValidator.__call__() 0 24 5
A build.bika.lims.validators.SortKeyValidator.__call__() 14 14 4
A build.bika.lims.validators.UniqueFieldValidator.make_catalog_query() 0 36 4
A build.bika.lims.validators._sumLists() 0 9 3
B build.bika.lims.validators._toIntList() 0 15 6
A build.bika.lims.validators.InlineFieldValidator.__call__() 0 22 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like bika.lims.validators often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
import re
9
import string
10
import types
11
from time import strptime as _strptime
12
13
from Products.CMFCore.utils import getToolByName
14
from Products.CMFPlone.utils import safe_unicode
15
from Products.ZCTextIndex.ParseTree import ParseError
16
from Products.validation import validation
17
from Products.validation.interfaces.IValidator import IValidator
18
from zope.interface import implements
19
20
from bika.lims import bikaMessageFactory as _
21
from bika.lims.utils import to_utf8
22
from bika.lims import api
23
from bika.lims import logger
24
25
26 View Code Duplication
class IdentifierTypeAttributesValidator:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
27
    """Validate IdentifierTypeAttributes to ensure that attributes are
28
    not duplicated.
29
    """
30
31
    implements(IValidator)
32
    name = "identifiertypeattributesvalidator"
33
34
    def __call__(self, value, *args, **kwargs):
35
        instance = kwargs['instance']
36
        request = instance.REQUEST
37
        form = request.get('form', {})
38
        fieldname = kwargs['field'].getName()
39
        form_value = form.get(fieldname, False)
40
        if form_value is False:
41
            # not required...
42
            return True
43
        if value == instance.get(fieldname):
44
            # no change.
45
            return True
46
47
        return True
48
49
50
validation.register(IdentifierTypeAttributesValidator())
51
52
53 View Code Duplication
class IdentifierValidator:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
54
    """some actual validation should go here.
55
    I'm leaving this stub registered, but adding no extra validation.
56
    """
57
58
    implements(IValidator)
59
    name = "identifiervalidator"
60
61
    def __call__(self, value, *args, **kwargs):
62
        instance = kwargs['instance']
63
        request = instance.REQUEST
64
        form = request.get('form', {})
65
        fieldname = kwargs['field'].getName()
66
        form_value = form.get(fieldname, False)
67
        if form_value is False:
68
            # not required...
69
            return True
70
        if value == instance.get(fieldname):
71
            # no change.
72
            return True
73
74
        return True
75
76
77
validation.register(IdentifierValidator())
78
79
80
class UniqueFieldValidator:
81
    """Verifies if a field value is unique within the same container
82
    """
83
    implements(IValidator)
84
    name = "uniquefieldvalidator"
85
86
    def get_parent_objects(self, context):
87
        """Return all objects of the same type from the parent object
88
        """
89
        parent_object = api.get_parent(context)
90
        portal_type = api.get_portal_type(context)
91
        return parent_object.objectValues(portal_type)
92
93
    def query_parent_objects(self, context, query=None):
94
        """Return the objects of the same type from the parent object
95
96
        :param query: Catalog query to narrow down the objects
97
        :type query: dict
98
        :returns: Content objects of the same portal type in the parent
99
        """
100
101
        # return the object values if we have no catalog query
102
        if query is None:
103
            return self.get_parent_objects(context)
104
105
        # avoid undefined reference of catalog in except...
106
        catalog = None
107
108
        # try to fetch the results via the catalog
109
        try:
110
            catalogs = api.get_catalogs_for(context)
111
            catalog = catalogs[0]
112
            return map(api.get_object, catalog(query))
113
        except (IndexError, UnicodeDecodeError, ParseError, api.BikaLIMSError) as e:
114
            # fall back to the object values of the parent
115
            logger.warn("UniqueFieldValidator: Catalog query {} failed "
116
                        "for catalog {} ({}) -> returning object values of {}"
117
                        .format(query, repr(catalog), str(e),
118
                                repr(api.get_parent(context))))
119
            return self.get_parent_objects(context)
120
121
    def make_catalog_query(self, context, field, value):
122
        """Create a catalog query for the field
123
        """
124
125
        # get the catalogs for the context
126
        catalogs = api.get_catalogs_for(context)
127
        # context not in any catalog?
128
        if not catalogs:
129
            logger.warn("UniqueFieldValidator: Context '{}' is not assigned"
130
                        "to any catalog!".format(repr(context)))
131
            return None
132
133
        # take the first catalog
134
        catalog = catalogs[0]
135
136
        # Check if the field accessor is indexed
137
        field_index = field.getName()
138
        accessor = field.getAccessor(context)
139
        if accessor:
140
            field_index = accessor.__name__
141
142
        # return if the field is not indexed
143
        if field_index not in catalog.indexes():
144
            return None
145
146
        # build a catalog query
147
        query = {
148
            "portal_type": api.get_portal_type(context),
149
            "path": {
150
                "query": api.get_parent_path(context),
151
                "depth": 1,
152
            }
153
        }
154
        query[field_index] = value
155
        logger.info("UniqueFieldValidator:Query={}".format(query))
156
        return query
157
158
    def __call__(self, value, *args, **kwargs):
159
        context = kwargs['instance']
160
        uid = api.get_uid(context)
161
        field = kwargs['field']
162
        fieldname = field.getName()
163
        translate = getToolByName(context, 'translation_service').translate
164
165
        # return directly if nothing changed
166
        if value == field.get(context):
167
            return True
168
169
        # Fetch the parent object candidates by catalog or by objectValues
170
        #
171
        # N.B. We want to use the catalog to speed things up, because using
172
        # `parent.objectValues` is very expensive if the parent object contains
173
        # many items and causes the UI to block too long
174
        catalog_query = self.make_catalog_query(context, field, value)
175
        parent_objects = self.query_parent_objects(
176
            context, query=catalog_query)
177
178
        for item in parent_objects:
179
            if hasattr(item, 'UID') and item.UID() != uid and \
180
               fieldname in item.Schema() and \
181
               str(item.Schema()[fieldname].get(item)) == str(value).strip():
182
                # We have to compare them as strings because
183
                # even if a number (as an  id) is saved inside
184
                # a string widget and string field, it will be
185
                # returned as an int. I don't know if it is
186
                # caused because is called with
187
                # <item.Schema()[fieldname].get(item)>,
188
                # but it happens...
189
                msg = _(
190
                    "Validation failed: '${value}' is not unique",
191
                    mapping={
192
                        'value': safe_unicode(value)
193
                    })
194
                return to_utf8(translate(msg))
195
        return True
196
197
198
validation.register(UniqueFieldValidator())
199
200
201
class InvoiceBatch_EndDate_Validator:
202
    """ Verifies that the End Date is after the Start Date """
203
204
    implements(IValidator)
205
    name = "invoicebatch_EndDate_validator"
206
207
    def __call__(self, value, *args, **kwargs):
208
        instance = kwargs.get('instance')
209
        request = kwargs.get('REQUEST')
210
211
        if request and request.form.get('BatchStartDate'):
212
            startdate = _strptime(request.form.get('BatchStartDate'), '%Y-%m-%d %H:%M')
213
        else:
214
            startdate = _strptime(instance.getBatchStartDate(), '%Y-%m-%d %H:%M')
215
216
        enddate = _strptime(value, '%Y-%m-%d %H:%M')
217
218
        translate = api.get_tool('translation_service', instance).translate
219
        if not enddate >= startdate:
220
            msg = _("Start date must be before End Date")
221
            return to_utf8(translate(msg))
222
        return True
223
224
225
validation.register(InvoiceBatch_EndDate_Validator())
226
227
228
class ServiceKeywordValidator:
229
    """Validate AnalysisService Keywords
230
    must match isUnixLikeName
231
    may not be the same as another service keyword
232
    may not be the same as any InterimField id.
233
    """
234
235
    implements(IValidator)
236
    name = "servicekeywordvalidator"
237
238
    def __call__(self, value, *args, **kwargs):
239
        instance = kwargs['instance']
240
        # fieldname = kwargs['field'].getName()
241
        # request = kwargs.get('REQUEST', {})
242
        # form = request.get('form', {})
243
244
        translate = getToolByName(instance, 'translation_service').translate
245
246
        if re.findall(r"[^A-Za-z\w\d\-\_]", value):
247
            return _("Validation failed: keyword contains invalid characters")
248
249
        # check the value against all AnalysisService keywords
250
        # this has to be done from catalog so we don't
251
        # clash with ourself
252
        bsc = getToolByName(instance, 'bika_setup_catalog')
253
        services = bsc(portal_type='AnalysisService', getKeyword=value)
254
        for service in services:
255
            if service.UID != instance.UID():
256
                msg = _(
257
                    "Validation failed: '${title}': This keyword "
258
                    "is already in use by service '${used_by}'",
259
                    mapping={
260
                        'title': safe_unicode(value),
261
                        'used_by': safe_unicode(service.Title)
262
                    })
263
                return to_utf8(translate(msg))
264
265
        calc = hasattr(instance, 'getCalculation') and \
266
            instance.getCalculation() or None
267
        our_calc_uid = calc and calc.UID() or ''
268
269
        # check the value against all Calculation Interim Field ids
270
        calcs = [c for c in bsc(portal_type='Calculation')]
271
        for calc in calcs:
272
            calc = calc.getObject()
273
            interim_fields = calc.getInterimFields()
274
            if not interim_fields:
275
                continue
276
            for field in interim_fields:
277
                if field['keyword'] == value and our_calc_uid != calc.UID():
278
                    msg = _(
279
                        "Validation failed: '${title}': This keyword "
280
                        "is already in use by calculation '${used_by}'",
281
                        mapping={
282
                            'title': safe_unicode(value),
283
                            'used_by': safe_unicode(calc.Title())
284
                        })
285
                    return to_utf8(translate(msg))
286
        return True
287
288
289
validation.register(ServiceKeywordValidator())
290
291
292
class InterimFieldsValidator:
293
    """Validating InterimField keywords.
294
        XXX Applied as a subfield validator but validates entire field.
295
        keyword must match isUnixLikeName
296
        keyword may not be the same as any service keyword.
297
        keyword must be unique in this InterimFields field
298
        keyword must be unique for interimfields which share the same title.
299
        title must be unique for interimfields which share the same keyword.
300
    """
301
302
    implements(IValidator)
303
    name = "interimfieldsvalidator"
304
305
    def __call__(self, value, *args, **kwargs):
306
        instance = kwargs['instance']
307
        fieldname = kwargs['field'].getName()
308
        request = kwargs.get('REQUEST', {})
309
        form = request.form
310
        interim_fields = form.get(fieldname, [])
311
312
        translate = getToolByName(instance, 'translation_service').translate
313
        bsc = getToolByName(instance, 'bika_setup_catalog')
314
315
        # We run through the validator once per form submit, and check all
316
        # values
317
        # this value in request prevents running once per subfield value.
318
        key = instance.id + fieldname
319
        if instance.REQUEST.get(key, False):
320
            return True
321
322
        for x in range(len(interim_fields)):
323
            row = interim_fields[x]
324
            keys = row.keys()
325
            if 'title' not in keys:
326
                instance.REQUEST[key] = to_utf8(
327
                    translate(_("Validation failed: title is required")))
328
                return instance.REQUEST[key]
329
            if 'keyword' not in keys:
330
                instance.REQUEST[key] = to_utf8(
331
                    translate(_("Validation failed: keyword is required")))
332
                return instance.REQUEST[key]
333
            if not re.match(r"^[A-Za-z\w\d\-\_]+$", row['keyword']):
334
                instance.REQUEST[key] = _(
335
                    "Validation failed: keyword contains invalid characters")
336
                return instance.REQUEST[key]
337
338
        # keywords and titles used once only in the submitted form
339
        keywords = {}
340
        titles = {}
341
        for field in interim_fields:
342
            if 'keyword' in field:
343
                if field['keyword'] in keywords:
344
                    keywords[field['keyword']] += 1
345
                else:
346
                    keywords[field['keyword']] = 1
347
            if 'title' in field:
348
                if field['title'] in titles:
349
                    titles[field['title']] += 1
350
                else:
351
                    titles[field['title']] = 1
352
        for k in [k for k in keywords.keys() if keywords[k] > 1]:
353
            msg = _(
354
                "Validation failed: '${keyword}': duplicate keyword",
355
                mapping={
356
                    'keyword': safe_unicode(k)
357
                })
358
            instance.REQUEST[key] = to_utf8(translate(msg))
359
            return instance.REQUEST[key]
360
        for t in [t for t in titles.keys() if titles[t] > 1]:
361
            msg = _(
362
                "Validation failed: '${title}': duplicate title",
363
                mapping={
364
                    'title': safe_unicode(t)
365
                })
366
            instance.REQUEST[key] = to_utf8(translate(msg))
367
            return instance.REQUEST[key]
368
369
        # check all keywords against all AnalysisService keywords for dups
370
        services = bsc(portal_type='AnalysisService', getKeyword=value)
371
        if services:
372
            msg = _(
373
                "Validation failed: '${title}': "
374
                "This keyword is already in use by service '${used_by}'",
375
                mapping={
376
                    'title': safe_unicode(value),
377
                    'used_by': safe_unicode(services[0].Title)
378
                })
379
            instance.REQUEST[key] = to_utf8(translate(msg))
380
            return instance.REQUEST[key]
381
382
        # any duplicated interimfield titles must share the same keyword
383
        # any duplicated interimfield keywords must share the same title
384
        calcs = bsc(portal_type='Calculation')
385
        keyword_titles = {}
386
        title_keywords = {}
387
        for calc in calcs:
388
            if calc.UID == instance.UID():
389
                continue
390
            calc = calc.getObject()
391
            for field in calc.getInterimFields():
392
                keyword_titles[field['keyword']] = field['title']
393
                title_keywords[field['title']] = field['keyword']
394
        for field in interim_fields:
395
            if field['keyword'] != value:
396
                continue
397 View Code Duplication
            if 'title' in field and \
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
398
               field['title'] in title_keywords.keys() and \
399
               title_keywords[field['title']] != field['keyword']:
400
                msg = _(
401
                    "Validation failed: column title '${title}' "
402
                    "must have keyword '${keyword}'",
403
                    mapping={
404
                        'title': safe_unicode(field['title']),
405
                        'keyword': safe_unicode(title_keywords[field['title']])
406
                    })
407
                instance.REQUEST[key] = to_utf8(translate(msg))
408
                return instance.REQUEST[key]
409 View Code Duplication
            if 'keyword' in field and \
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
410
               field['keyword'] in keyword_titles.keys() and \
411
               keyword_titles[field['keyword']] != field['title']:
412
                msg = _(
413
                    "Validation failed: keyword '${keyword}' "
414
                    "must have column title '${title}'",
415
                    mapping={
416
                        'keyword': safe_unicode(field['keyword']),
417
                        'title': safe_unicode(keyword_titles[field['keyword']])
418
                    })
419
                instance.REQUEST[key] = to_utf8(translate(msg))
420
                return instance.REQUEST[key]
421
422
        instance.REQUEST[key] = True
423
        return True
424
425
426
validation.register(InterimFieldsValidator())
427
428
429
class FormulaValidator:
430
    """ Validate keywords in calculation formula entry
431
    """
432
    implements(IValidator)
433
    name = "formulavalidator"
434
435
    def __call__(self, value, *args, **kwargs):
436
        if not value:
437
            return True
438
        instance = kwargs['instance']
439
        # fieldname = kwargs['field'].getName()
440
        request = kwargs.get('REQUEST', {})
441
        form = request.form
442
        interim_fields = form.get('InterimFields')
443
444
        translate = getToolByName(instance, 'translation_service').translate
445
        bsc = getToolByName(instance, 'bika_setup_catalog')
446
        interim_keywords = interim_fields and \
447
            [f['keyword'] for f in interim_fields] or []
448
        keywords = re.compile(r"\[([^\.^\]]+)\]").findall(value)
449
450
        for keyword in keywords:
451
            # Check if the service keyword exists and is active.
452
            dep_service = bsc(getKeyword=keyword, is_active=True)
453
            if not dep_service and keyword not in interim_keywords:
454
                msg = _(
455
                    "Validation failed: Keyword '${keyword}' is invalid",
456
                    mapping={
457
                        'keyword': safe_unicode(keyword)
458
                    })
459
                return to_utf8(translate(msg))
460
461
        # Wildcards
462
        # LIMS-1769 Allow to use LDL and UDL in calculations
463
        # https://jira.bikalabs.com/browse/LIMS-1769
464
        allowedwds = ['LDL', 'UDL', 'BELOWLDL', 'ABOVEUDL']
465
        keysandwildcards = re.compile(r"\[([^\]]+)\]").findall(value)
466
        keysandwildcards = [k for k in keysandwildcards if '.' in k]
467
        keysandwildcards = [k.split('.', 1) for k in keysandwildcards]
468
        errwilds = [k[1] for k in keysandwildcards if k[0] not in keywords]
469
        if len(errwilds) > 0:
470
            msg = _(
471
                "Wildcards for interims are not allowed: ${wildcards}",
472
                mapping={
473
                    'wildcards': safe_unicode(', '.join(errwilds))
474
                })
475
            return to_utf8(translate(msg))
476
477
        wildcards = [k[1] for k in keysandwildcards if k[0] in keywords]
478
        wildcards = [wd for wd in wildcards if wd not in allowedwds]
479
        if len(wildcards) > 0:
480
            msg = _(
481
                "Invalid wildcards found: ${wildcards}",
482
                mapping={
483
                    'wildcards': safe_unicode(', '.join(wildcards))
484
                })
485
            return to_utf8(translate(msg))
486
487
        return True
488
489
490
validation.register(FormulaValidator())
491
492
493
class CoordinateValidator:
494
    """ Validate latitude or longitude field values
495
    """
496
    implements(IValidator)
497
    name = "coordinatevalidator"
498
499
    def __call__(self, value, **kwargs):
500
        if not value:
501
            return True
502
503
        instance = kwargs['instance']
504
        fieldname = kwargs['field'].getName()
505
        request = instance.REQUEST
506
507
        form = request.form
508
        form_value = form.get(fieldname)
509
510
        translate = getToolByName(instance, 'translation_service').translate
511
512
        try:
513
            degrees = int(form_value['degrees'])
514
        except ValueError:
515
            return to_utf8(
516
                translate(_("Validation failed: degrees must be numeric")))
517
518
        try:
519
            minutes = int(form_value['minutes'])
520
        except ValueError:
521
            return to_utf8(
522
                translate(_("Validation failed: minutes must be numeric")))
523
524
        try:
525
            seconds = int(form_value['seconds'])
526
        except ValueError:
527
            return to_utf8(
528
                translate(_("Validation failed: seconds must be numeric")))
529
530
        if not 0 <= minutes <= 59:
531
            return to_utf8(
532
                translate(_("Validation failed: minutes must be 0 - 59")))
533
534
        if not 0 <= seconds <= 59:
535
            return to_utf8(
536
                translate(_("Validation failed: seconds must be 0 - 59")))
537
538
        bearing = form_value['bearing']
539
540 View Code Duplication
        if fieldname == 'Latitude':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
541
            if not 0 <= degrees <= 90:
542
                return to_utf8(
543
                    translate(_("Validation failed: degrees must be 0 - 90")))
544
            if degrees == 90:
545
                if minutes != 0:
546
                    return to_utf8(
547
                        translate(
548
                            _("Validation failed: degrees is 90; "
549
                              "minutes must be zero")))
550
                if seconds != 0:
551
                    return to_utf8(
552
                        translate(
553
                            _("Validation failed: degrees is 90; "
554
                              "seconds must be zero")))
555
            if bearing.lower() not in 'sn':
556
                return to_utf8(
557
                    translate(_("Validation failed: Bearing must be N/S")))
558
559 View Code Duplication
        if fieldname == 'Longitude':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
560
            if not 0 <= degrees <= 180:
561
                return to_utf8(
562
                    translate(_("Validation failed: degrees must be 0 - 180")))
563
            if degrees == 180:
564
                if minutes != 0:
565
                    return to_utf8(
566
                        translate(
567
                            _("Validation failed: degrees is 180; "
568
                              "minutes must be zero")))
569
                if seconds != 0:
570
                    return to_utf8(
571
                        translate(
572
                            _("Validation failed: degrees is 180; "
573
                              "seconds must be zero")))
574
            if bearing.lower() not in 'ew':
575
                return to_utf8(
576
                    translate(_("Validation failed: Bearing must be E/W")))
577
578
        return True
579
580
581
validation.register(CoordinateValidator())
582
583
584
class ResultOptionsValidator:
585
    """Validating AnalysisService ResultOptions field.
586
        XXX Applied as a subfield validator but validates
587
        for x in range(len(interim_fields)):
588
            row = interim_fields[x]
589
            keys = row.keys()
590
            if 'title' not in keys:entire field.
591
    """
592
593
    implements(IValidator)
594
    name = "resultoptionsvalidator"
595
596
    def __call__(self, value, *args, **kwargs):
597
        instance = kwargs['instance']
598
        fieldname = kwargs['field'].getName()
599
        request = kwargs.get('REQUEST', {})
600
        form = request.form
601
        form_value = form.get(fieldname)
602
603
        translate = getToolByName(instance, 'translation_service').translate
604
        # bsc = getToolByName(instance, 'bika_setup_catalog')
605
606
        # ResultValue must always be a number
607
        for field in form_value:
608
            try:
609
                float(field['ResultValue'])
610
            except:
611
                return to_utf8(
612
                    translate(
613
                        _("Validation failed: "
614
                          "Result Values must be numbers")))
615
            if 'ResultText' not in field:
616
                return to_utf8(
617
                    translate(
618
                        _("Validation failed: Result Text cannot be blank")))
619
620
        return True
621
622
623
validation.register(ResultOptionsValidator())
624
625
626
class RestrictedCategoriesValidator:
627
    """ Verifies that client Restricted categories include all categories
628
    required by service dependencies. """
629
630
    implements(IValidator)
631
    name = "restrictedcategoriesvalidator"
632
633
    def __call__(self, value, *args, **kwargs):
634
        instance = kwargs['instance']
635
        # fieldname = kwargs['field'].getName()
636
        # request = kwargs.get('REQUEST', {})
637
        # form = request.get('form', {})
638
639
        translate = getToolByName(instance, 'translation_service').translate
640
        bsc = getToolByName(instance, 'bika_setup_catalog')
641
        # uc = getToolByName(instance, 'uid_catalog')
642
643
        failures = []
644
645
        for category in value:
646
            if not category:
647
                continue
648
            services = bsc(
649
                portal_type="AnalysisService", getCategoryUID=category)
650
            for service in services:
651
                service = service.getObject()
652
                calc = service.getCalculation()
653
                deps = calc and calc.getDependentServices() or []
654
                for dep in deps:
655
                    if dep.getCategoryUID() not in value:
656
                        title = dep.getCategoryTitle()
657
                        if title not in failures:
658
                            failures.append(title)
659
        if failures:
660
            msg = _(
661
                "Validation failed: The selection requires the following "
662
                "categories to be selected: ${categories}",
663
                mapping={
664
                    'categories': safe_unicode(','.join(failures))
665
                })
666
            return to_utf8(translate(msg))
667
668
        return True
669
670
671
validation.register(RestrictedCategoriesValidator())
672
673
674
class PrePreservationValidator:
675
    """ Validate PrePreserved Containers.
676
        User must select a Preservation.
677
    """
678
    implements(IValidator)
679
    name = "container_prepreservation_validator"
680
681
    def __call__(self, value, *args, **kwargs):
682
        # If not prepreserved, no validation required.
683
        if not value:
684
            return True
685
686
        instance = kwargs['instance']
687
        # fieldname = kwargs['field'].getName()
688
        request = kwargs.get('REQUEST', {})
689
        form = request.form
690
        preservation = form.get('Preservation')
691
692
        if type(preservation) in (list, tuple):
693
            preservation = preservation[0]
694
695
        if preservation:
696
            return True
697
698
        translate = getToolByName(instance, 'translation_service').translate
699
        # bsc = getToolByName(instance, 'bika_setup_catalog')
700
701
        if not preservation:
702
            msg = _("Validation failed: PrePreserved containers "
703
                    "must have a preservation selected.")
704
            return to_utf8(translate(msg))
705
706
707
validation.register(PrePreservationValidator())
708
709
710
class StandardIDValidator:
711
    """Matches against regular expression:
712
       [^A-Za-z\w\d\-\_]
713
    """
714
715
    implements(IValidator)
716
    name = "standard_id_validator"
717
718
    def __call__(self, value, *args, **kwargs):
719
720
        regex = r"[^A-Za-z\w\d\-\_]"
721
722
        instance = kwargs['instance']
723
        # fieldname = kwargs['field'].getName()
724
        # request = kwargs.get('REQUEST', {})
725
        # form = request.get('form', {})
726
727
        translate = getToolByName(instance, 'translation_service').translate
728
729
        # check the value against all AnalysisService keywords
730
        if re.findall(regex, value):
731
            msg = _("Validation failed: keyword contains invalid "
732
                    "characters")
733
            return to_utf8(translate(msg))
734
735
        return True
736
737
738
validation.register(StandardIDValidator())
739
740
741
def get_record_value(request, uid, keyword, default=None):
742
    """Returns the value for the keyword and uid from the request"""
743
    value = request.get(keyword)
744
    if not value:
745
        return default
746
    if not isinstance(value, list):
747
        return default
748
    return value[0].get(uid, default) or default
749
750
751
class AnalysisSpecificationsValidator:
752
    """Min value must be below max value
753
       Warn min value must be below min value or empty
754
       Warn max value must above max value or empty
755
       Percentage value must be between 0 and 100
756
       Values must be numbers
757
    """
758
759
    implements(IValidator)
760
    name = "analysisspecs_validator"
761
762
    def __call__(self, value, *args, **kwargs):
763
        instance = kwargs['instance']
764
        request = kwargs.get('REQUEST', {})
765
        fieldname = kwargs['field'].getName()
766
767
        # This value in request prevents running once per subfield value.
768
        key = '{}{}'.format(instance.getId(), fieldname)
769
        if instance.REQUEST.get(key, False):
770
            return True
771
772
        # Walk through all AS UIDs and validate each parameter for that AS
773
        services = request.get('service', [{}])[0]
774
        for uid, service_name in services.items():
775
            err_msg = self.validate_service(request, uid)
776
            if not err_msg:
777
                continue
778
779
            # Validation failed
780
            err_msg = "{}: {}".format(_("Validation for '{}' failed"),
781
                                      _(err_msg))
782
            err_msg = err_msg.format(service_name)
783
            translate = api.get_tool('translation_service').translate
784
            instance.REQUEST[key] = to_utf8(translate(safe_unicode(err_msg)))
785
            return instance.REQUEST[key]
786
787
        instance.REQUEST[key] = True
788
        return True
789
790
    def validate_service(self, request, uid):
791
        """Validates the specs values from request for the service uid. Returns
792
        a non-translated message if the validation failed.
793
        """
794
        spec_min = get_record_value(request, uid, "min")
795
        spec_max = get_record_value(request, uid, "max")
796
        error = get_record_value(request, uid, "error", "0")
797
        warn_min = get_record_value(request, uid, "warn_min")
798
        warn_max = get_record_value(request, uid, "warn_max")
799
800
        if not spec_min and not spec_max:
801
            # Neither min nor max values have been set, dismiss
802
            return None
803
804
        if not api.is_floatable(spec_min):
805
            return "'Min' value must be numeric"
806
        if not api.is_floatable(spec_max):
807
            return "'Max' value must be numeric"
808
        if api.to_float(spec_min) > api.to_float(spec_max):
809
            return "'Max' value must be above 'Min' value"
810
        if not api.is_floatable(error) or 0.0 < api.to_float(error) > 100:
811
            return "% Error must be between 0 and 100"
812
813
        if warn_min:
814
            if not api.is_floatable(warn_min):
815
                return "'Warn Min' value must be numeric or empty"
816
            if api.to_float(warn_min) > api.to_float(spec_min):
817
                return "'Warn Min' value must be below 'Min' value"
818
819
        if warn_max:
820
            if not api.is_floatable(warn_max):
821
                return "'Warn Max' value must be numeric or empty"
822
            if api.to_float(warn_max) < api.to_float(spec_max):
823
                return "'Warn Max' value must be above 'Max' value"
824
        return None
825
826
827
validation.register(AnalysisSpecificationsValidator())
828
829
830
class UncertaintiesValidator:
831
    """Uncertainties may be specified as numeric values or percentages.
832
    Min value must be below max value.
833
    Uncertainty must not be < 0.
834
    """
835
836
    implements(IValidator)
837
    name = "uncertainties_validator"
838
839
    def __call__(self, subf_value, *args, **kwargs):
840
841
        instance = kwargs['instance']
842
        request = kwargs.get('REQUEST', {})
843
        fieldname = kwargs['field'].getName()
844
        translate = getToolByName(instance, 'translation_service').translate
845
846
        # We run through the validator once per form submit, and check all
847
        # values
848
        # this value in request prevents running once per subfield value.
849
        key = instance.id + fieldname
850
        if instance.REQUEST.get(key, False):
851
            return True
852
853
        for i, value in enumerate(request[fieldname]):
854
855
            # Values must be numbers
856
            try:
857
                minv = float(value['intercept_min'])
858
            except ValueError:
859
                instance.REQUEST[key] = to_utf8(
860
                    translate(
861
                        _("Validation failed: Min values must be numeric")))
862
                return instance.REQUEST[key]
863
            try:
864
                maxv = float(value['intercept_max'])
865
            except ValueError:
866
                instance.REQUEST[key] = to_utf8(
867
                    translate(
868
                        _("Validation failed: Max values must be numeric")))
869
                return instance.REQUEST[key]
870
871
            # values may be percentages; the rest of the numeric validation must
872
            # still pass once the '%' is stripped off.
873
            err = value['errorvalue']
874
            perc = False
875
            if err.endswith('%'):
876
                perc = True
877
                err = err[:-1]
878
            try:
879
                err = float(err)
880
            except ValueError:
881
                instance.REQUEST[key] = to_utf8(
882
                    translate(
883
                        _("Validation failed: Error values must be numeric")))
884
                return instance.REQUEST[key]
885
886
            if perc and (err < 0 or err > 100):
887
                # Error percentage must be between 0 and 100
888
                instance.REQUEST[key] = to_utf8(
889
                    translate(
890
                        _("Validation failed: Error percentage must be between 0 "
891
                          "and 100")))
892
                return instance.REQUEST[key]
893
894
            # Min value must be < max
895
            if minv > maxv:
896
                instance.REQUEST[key] = to_utf8(
897
                    translate(
898
                        _("Validation failed: Max values must be greater than Min "
899
                          "values")))
900
                return instance.REQUEST[key]
901
902
            # Error values must be >-1
903
            if err < 0:
904
                instance.REQUEST[key] = to_utf8(
905
                    translate(
906
                        _("Validation failed: Error value must be 0 or greater"
907
                          )))
908
                return instance.REQUEST[key]
909
910
        instance.REQUEST[key] = True
911
        return True
912
913
914
validation.register(UncertaintiesValidator())
915
916
917
class DurationValidator:
918
    """Simple stuff - just checking for integer values.
919
    """
920
921
    implements(IValidator)
922
    name = "duration_validator"
923
924
    def __call__(self, value, *args, **kwargs):
925
926
        instance = kwargs['instance']
927
        request = kwargs.get('REQUEST', {})
928
        fieldname = kwargs['field'].getName()
929
        translate = getToolByName(instance, 'translation_service').translate
930
931
        value = request[fieldname]
932
        for v in value.values():
933
            try:
934
                int(v)
935
            except:
936
                return to_utf8(
937
                    translate(_("Validation failed: Values must be numbers")))
938
        return True
939
940
941
validation.register(DurationValidator())
942
943
944
class ReferenceValuesValidator:
945
    """Min value must be below max value
946
       Percentage value must be between 0 and 100
947
       Values must be numbers
948
       Expected values must be between min and max values
949
    """
950
951
    implements(IValidator)
952
    name = "referencevalues_validator"
953
954
    def __call__(self, value, *args, **kwargs):
955
        request = kwargs.get('REQUEST', {})
956
        # Retrieve all AS uids
957
        services = request.get('service', [{}])[0]
958
        for uid, service_name in services.items():
959
            err_msg = self.validate_service(request, uid)
960
            if not err_msg:
961
                continue
962
963
            # Validation failed
964
            err_msg = "{}: {}".format(_("Validation for '{}' failed"),
965
                                      _(err_msg))
966
            err_msg = err_msg.format(service_name)
967
            translate = api.get_tool('translation_service').translate
968
            return to_utf8(translate(safe_unicode(err_msg)))
969
970
        return True
971
972
    def validate_service(self, request, uid):
973
        """Validates the specs values from request for the service uid. Returns
974
        a non-translated message if the validation failed."""
975
976
        result = get_record_value(request, uid, 'result')
977
        if not result:
978
            # No result set for this service, dismiss
979
            return None
980
981
        if not api.is_floatable(result):
982
            return "Expected result value must be numeric"
983
984
        spec_min = get_record_value(request, uid, "min", result)
985
        spec_max = get_record_value(request, uid, "max", result)
986
        error = get_record_value(request, uid, "error", "0")
987
        if not api.is_floatable(spec_min):
988
            return "'Min' value must be numeric"
989
        if not api.is_floatable(spec_max):
990
            return "'Max' value must be numeric"
991
        if api.to_float(spec_min) > api.to_float(result):
992
            return "'Min' value must be below the expected result"
993
        if api.to_float(spec_max) < api.to_float(result):
994
            return "'Max' value must be above the expected result"
995
        if not api.is_floatable(error) or 0.0 < api.to_float(error) > 100:
996
            return "% Error must be between 0 and 100"
997
        return None
998
999
validation.register(ReferenceValuesValidator())
1000
1001
1002 View Code Duplication
class PercentValidator:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1003
    """ Floatable, >=0, <=100. """
1004
1005
    implements(IValidator)
1006
    name = "percentvalidator"
1007
1008
    def __call__(self, value, *args, **kwargs):
1009
        instance = kwargs['instance']
1010
        # fieldname = kwargs['field'].getName()
1011
        # request = kwargs.get('REQUEST', {})
1012
        # form = request.get('form', {})
1013
1014
        translate = getToolByName(instance, 'translation_service').translate
1015
1016
        try:
1017
            value = float(value)
1018
        except:
1019
            msg = _("Validation failed: percent values must be numbers")
1020
            return to_utf8(translate(msg))
1021
1022
        if value < 0 or value > 100:
1023
            msg = _(
1024
                "Validation failed: percent values must be between 0 and 100")
1025
            return to_utf8(translate(msg))
1026
1027
        return True
1028
1029
1030
validation.register(PercentValidator())
1031
1032
1033
def _toIntList(numstr, acceptX=0):
1034
    """
1035
    Convert ans string to a list removing all invalid characters.
1036
    Receive: a string as a number
1037
    """
1038
    res = []
1039
    # Converting and removing invalid characters
1040
    for i in numstr:
1041
        if i in string.digits and i not in string.letters:
1042
            res.append(int(i))
1043
1044
    # Converting control number into ISBN
1045
    if acceptX and (numstr[-1] in 'Xx'):
1046
        res.append(10)
1047
    return res
1048
1049
1050
def _sumLists(a, b):
1051
    """
1052
    Algorithm to check validity of NBI and NIF.
1053
    Receives string with a umber to validate.
1054
    """
1055
    val = 0
1056
    for i in map(lambda a, b: a * b, a, b):
1057
        val += i
1058
    return val
1059
1060
1061
class NIBvalidator:
1062
    """
1063
    Validates if the introduced NIB is correct.
1064
    """
1065
1066
    implements(IValidator)
1067
    name = "NIBvalidator"
1068
1069
    def __call__(self, value, *args, **kwargs):
1070
        """
1071
        Check the NIB number
1072
        value:: string with NIB.
1073
        """
1074
        instance = kwargs['instance']
1075
        translate = getToolByName(instance, 'translation_service').translate
1076
        LEN_NIB = 21
1077
        table = (73, 17, 89, 38, 62, 45, 53, 15, 50, 5, 49, 34, 81, 76, 27, 90,
1078
                 9, 30, 3)
1079
1080
        # convert to entire numbers list
1081
        nib = _toIntList(value)
1082
1083
        # checking the length of the number
1084
        if len(nib) != LEN_NIB:
1085
            msg = _('Incorrect NIB number: %s' % value)
1086
            return to_utf8(translate(msg))
1087
        # last numbers algorithm validator
1088
        return nib[-2] * 10 + nib[-1] == 98 - _sumLists(table, nib[:-2]) % 97
1089
1090
1091
validation.register(NIBvalidator())
1092
1093
1094
class IBANvalidator:
1095
    """
1096
    Validates if the introduced NIB is correct.
1097
    """
1098
1099
    implements(IValidator)
1100
    name = "IBANvalidator"
1101
1102
    def __call__(self, value, *args, **kwargs):
1103
        instance = kwargs['instance']
1104
        translate = getToolByName(instance, 'translation_service').translate
1105
1106
        # remove spaces from formatted
1107
        IBAN = ''.join(c for c in value if c.isalnum())
1108
1109
        IBAN = IBAN[4:] + IBAN[:4]
1110
        country = IBAN[-4:-2]
1111
1112
        if country not in country_dic:
1113
            msg = _('Unknown IBAN country %s' % country)
1114
            return to_utf8(translate(msg))
1115
1116
        length_c, name_c = country_dic[country]
1117
1118
        if len(IBAN) != length_c:
1119
            diff = len(IBAN) - length_c
1120
            msg = _('Wrong IBAN length by %s: %s' %
1121
                    (('short by %i' % -diff)
1122
                     if diff < 0 else ('too long by %i' % diff), value))
1123
            return to_utf8(translate(msg))
1124
        # Validating procedure
1125
        elif int("".join(str(letter_dic[x]) for x in IBAN)) % 97 != 1:
1126
            msg = _('Incorrect IBAN number: %s' % value)
1127
            return to_utf8(translate(msg))
1128
1129
        else:
1130
            # Accepted:
1131
            return True
1132
1133
1134
validation.register(IBANvalidator())
1135
1136
# Utility to check the integrity of an IBAN bank account No.
1137
# based on https://www.daniweb.com/software-development/python/code/382069
1138
# /iban-number-check-refreshed
1139
# Dictionaries - Refer to ISO 7064 mod 97-10
1140
letter_dic = {
1141
    "A": 10,
1142
    "B": 11,
1143
    "C": 12,
1144
    "D": 13,
1145
    "E": 14,
1146
    "F": 15,
1147
    "G": 16,
1148
    "H": 17,
1149
    "I": 18,
1150
    "J": 19,
1151
    "K": 20,
1152
    "L": 21,
1153
    "M": 22,
1154
    "N": 23,
1155
    "O": 24,
1156
    "P": 25,
1157
    "Q": 26,
1158
    "R": 27,
1159
    "S": 28,
1160
    "T": 29,
1161
    "U": 30,
1162
    "V": 31,
1163
    "W": 32,
1164
    "X": 33,
1165
    "Y": 34,
1166
    "Z": 35,
1167
    "0": 0,
1168
    "1": 1,
1169
    "2": 2,
1170
    "3": 3,
1171
    "4": 4,
1172
    "5": 5,
1173
    "6": 6,
1174
    "7": 7,
1175
    "8": 8,
1176
    "9": 9
1177
}
1178
1179
# ISO 3166-1 alpha-2 country code
1180
country_dic = {
1181
    "AL": [28, "Albania"],
1182
    "AD": [24, "Andorra"],
1183
    "AT": [20, "Austria"],
1184
    "BE": [16, "Belgium"],
1185
    "BA": [20, "Bosnia"],
1186
    "BG": [22, "Bulgaria"],
1187
    "HR": [21, "Croatia"],
1188
    "CY": [28, "Cyprus"],
1189
    "CZ": [24, "Czech Republic"],
1190
    "DK": [18, "Denmark"],
1191
    "EE": [20, "Estonia"],
1192
    "FO": [18, "Faroe Islands"],
1193
    "FI": [18, "Finland"],
1194
    "FR": [27, "France"],
1195
    "DE": [22, "Germany"],
1196
    "GI": [23, "Gibraltar"],
1197
    "GR": [27, "Greece"],
1198
    "GL": [18, "Greenland"],
1199
    "HU": [28, "Hungary"],
1200
    "IS": [26, "Iceland"],
1201
    "IE": [22, "Ireland"],
1202
    "IL": [23, "Israel"],
1203
    "IT": [27, "Italy"],
1204
    "LV": [21, "Latvia"],
1205
    "LI": [21, "Liechtenstein"],
1206
    "LT": [20, "Lithuania"],
1207
    "LU": [20, "Luxembourg"],
1208
    "MK": [19, "Macedonia"],
1209
    "MT": [31, "Malta"],
1210
    "MU": [30, "Mauritius"],
1211
    "MC": [27, "Monaco"],
1212
    "ME": [22, "Montenegro"],
1213
    "NL": [18, "Netherlands"],
1214
    "NO": [15, "Northern Ireland"],
1215
    "PO": [28, "Poland"],
1216
    "PT": [25, "Portugal"],
1217
    "RO": [24, "Romania"],
1218
    "SM": [27, "San Marino"],
1219
    "SA": [24, "Saudi Arabia"],
1220
    "RS": [22, "Serbia"],
1221
    "SK": [24, "Slovakia"],
1222
    "SI": [19, "Slovenia"],
1223
    "ES": [24, "Spain"],
1224
    "SE": [24, "Sweden"],
1225
    "CH": [21, "Switzerland"],
1226
    "TR": [26, "Turkey"],
1227
    "TN": [24, "Tunisia"],
1228
    "GB": [22, "United Kingdom"]
1229
}
1230
1231
1232 View Code Duplication
class SortKeyValidator:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1233
    """ Check for out of range values.
1234
    """
1235
1236
    implements(IValidator)
1237
    name = "SortKeyValidator"
1238
1239
    def __call__(self, value, *args, **kwargs):
1240
        instance = kwargs['instance']
1241
        translate = getToolByName(instance, 'translation_service').translate
1242
        try:
1243
            value = float(value)
1244
        except:
1245
            msg = _("Validation failed: value must be float")
1246
            return to_utf8(translate(msg))
1247
1248
        if value < 0 or value > 1000:
1249
            msg = _("Validation failed: value must be between 0 and 1000")
1250
            return to_utf8(translate(msg))
1251
1252
        return True
1253
1254
1255
validation.register(SortKeyValidator())
1256
1257
1258
class InlineFieldValidator:
1259
    """ Inline Field Validator
1260
1261
    calls a field function for validation
1262
    """
1263
1264
    implements(IValidator)
1265
    name = "inline_field_validator"
1266
1267
    def __call__(self, value, *args, **kwargs):
1268
        field = kwargs['field']
1269
        request = kwargs['REQUEST']
1270
        instance = kwargs['instance']
1271
1272
        # extract the request values
1273
        data = request.get(field.getName())
1274
1275
        # check if the field contains a callable
1276
        validator = getattr(field, self.name, None)
1277
1278
        # validator is a callable
1279
        if callable(validator):
1280
            return validator(instance, request, field, data)
1281
1282
        # validator is a string, check if the instance has a method with this name
1283
        if type(validator) in types.StringTypes:
1284
            instance_validator = getattr(instance, validator, None)
1285
            if callable(instance_validator):
1286
                return instance_validator(request, field, data)
1287
1288
        return True
1289
1290
1291
validation.register(InlineFieldValidator())
1292
1293
1294
class ReflexRuleValidator:
1295
    """
1296
    - The analysis service have to be related to the method
1297
    """
1298
1299
    implements(IValidator)
1300
    name = "reflexrulevalidator"
1301
1302
    def __call__(self, value, *args, **kwargs):
1303
1304
        instance = kwargs['instance']
1305
        # fieldname = kwargs['field'].getName()
1306
        # request = kwargs.get('REQUEST', {})
1307
        # form = request.get('form', {})
1308
        method = instance.getMethod()
1309
        bsc = getToolByName(instance, 'bika_setup_catalog')
1310
        query = {
1311
            'portal_type': 'AnalysisService',
1312
            'getAvailableMethodUIDs': method.UID()
1313
        }
1314
        method_ans_uids = [b.UID for b in bsc(query)]
1315
        rules = instance.getReflexRules()
1316
        error = ''
1317
        pc = getToolByName(instance, 'portal_catalog')
1318
        for rule in rules:
1319
            as_uid = rule.get('analysisservice', '')
1320
            as_brain = pc(
1321
                UID=as_uid,
1322
                portal_type='AnalysisService',
1323
                is_active=True)
1324
            if as_brain[0] and as_brain[0].UID in method_ans_uids:
1325
                pass
1326
            else:
1327
                error += as_brain['title'] + ' '
1328
        if error:
1329
            translate = getToolByName(instance,
1330
                                      'translation_service').translate
1331
            msg = _("The following analysis services don't belong to the"
1332
                    "current method: " + error)
1333
            return to_utf8(translate(msg))
1334
        return True
1335
1336
1337
validation.register(ReflexRuleValidator())
1338
1339
1340
class NoWhiteSpaceValidator:
1341
    """ String, not containing space(s). """
1342
1343
    implements(IValidator)
1344
    name = "no_white_space_validator"
1345
1346
    def __call__(self, value, *args, **kwargs):
1347
        instance = kwargs['instance']
1348
        translate = getToolByName(instance, 'translation_service').translate
1349
1350
        if value and " " in value:
1351
            msg = _("Invalid value: Please enter a value without spaces.")
1352
            return to_utf8(translate(msg))
1353
1354
        return True
1355
1356
1357
validation.register(NoWhiteSpaceValidator())
1358
1359
1360
class ImportValidator(object):
1361
    """Checks if a dotted name can be imported or not
1362
    """
1363
    implements(IValidator)
1364
    name = "importvalidator"
1365
1366
    def __call__(self, mod, **kwargs):
1367
1368
        # some needed tools
1369
        instance = kwargs['instance']
1370
        translate = getToolByName(instance, 'translation_service').translate
1371
1372
        try:
1373
            # noinspection PyUnresolvedReferences
1374
            import importlib
1375
            importlib.import_module(mod)
1376
        except ImportError:
1377
            msg = _("Validation failed: Could not import module '%s'" % mod)
1378
            return to_utf8(translate(msg))
1379
1380
        return True
1381
1382
1383
validation.register(ImportValidator())
1384