• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rdmorganiser / rdmo / 14404330126

11 Apr 2025 01:31PM UTC coverage: 90.789% (+0.3%) from 90.478%
14404330126

push

github

web-flow
Merge pull request #1195 from rdmorganiser/2.3.0

RDMO 2.3.0 ⭐

989 of 1076 branches covered (91.91%)

9176 of 10107 relevant lines covered (90.79%)

3.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.22
rdmo/projects/validators.py
1
from datetime import datetime, timedelta
4✔
2

3
from django.conf import settings
4✔
4
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist, ValidationError
4✔
5
from django.core.validators import EmailValidator, RegexValidator, URLValidator
4✔
6
from django.utils.dateparse import parse_datetime
4✔
7
from django.utils.translation import gettext_lazy as _
4✔
8

9
from rest_framework import serializers
4✔
10

11
from rdmo.core.constants import (
4✔
12
    VALUE_TYPE_BOOLEAN,
13
    VALUE_TYPE_DATE,
14
    VALUE_TYPE_DATETIME,
15
    VALUE_TYPE_EMAIL,
16
    VALUE_TYPE_FILE,
17
    VALUE_TYPE_FLOAT,
18
    VALUE_TYPE_INTEGER,
19
    VALUE_TYPE_PHONE,
20
    VALUE_TYPE_URL,
21
)
22
from rdmo.core.utils import human2bytes
4✔
23
from rdmo.core.validators import InstanceValidator
4✔
24

25

26
class ProjectParentValidator(InstanceValidator):
4✔
27

28
    def __call__(self, data, serializer=None):
4✔
29
        super().__call__(data, serializer)
4✔
30

31
        if self.instance and self.instance.id \
4✔
32
                and data.get('parent') in self.instance.get_descendants(include_self=True):
33
            raise self.raise_validation_error({
4✔
34
                'parent': [_('A project may not be moved to be a child of itself or one of its descendants.')]
35
            })
36

37

38
class ValueConflictValidator:
4✔
39

40
    requires_context = True
4✔
41

42
    def __call__(self, data, serializer):
4✔
43
        if serializer.instance:
4✔
44
            # for an update, check if the value was updated in the meantime
45
            updated = serializer.context['view'].request.data.get('updated')
4✔
46

47
            if updated is not None:
4✔
48
                delta = abs(parse_datetime(updated) - serializer.instance.updated)
4✔
49
                if delta > timedelta(seconds=settings.PROJECT_VALUES_CONFLICT_THRESHOLD):
4✔
50
                    raise serializers.ValidationError({
4✔
51
                        'conflict': [_('A newer version of this value was found.')]
52
                    })
53
        else:
54
            # for a new value, check if there is already a value with the same attribute and indexes
55
            get_kwargs = {
4✔
56
                'attribute': data.get('attribute'),
57
                'set_prefix': data.get('set_prefix'),
58
                'set_index': data.get('set_index')
59
            }
60

61
            # check the widget type, which is provided with the post request
62
            widget_type = serializer.context['view'].request.data.get('widget_type')
4✔
63
            if widget_type == 'checkbox':
4✔
64
                # for checkboxes, fail if a value with the same option exist
65
                get_kwargs['option'] = data.get('option')
4✔
66
            else:
67
                # for all other widget_types, fail if a value with the same collection_index exist
68
                get_kwargs['collection_index'] = data.get('collection_index')
4✔
69

70
            try:
4✔
71
                serializer.context['view'].project.values.filter(snapshot=None).get(**get_kwargs)
4✔
72
            except ObjectDoesNotExist:
4✔
73
                return
4✔
74
            except MultipleObjectsReturned:
×
75
                pass
×
76

77
            raise serializers.ValidationError({
4✔
78
                'conflict': [_('An existing value for this attribute/set_prefix/set_index/collection_index'
79
                              ' was found.')]
80
            })
81

82

83
class ValueQuotaValidator:
4✔
84

85
    requires_context = True
4✔
86

87
    def __call__(self, data, serializer):
4✔
88
        if serializer.context['view'].action == 'create' and data.get('value_type') == VALUE_TYPE_FILE:
4✔
89
            project = serializer.context['view'].project
4✔
90
            if project.file_size > human2bytes(settings.PROJECT_FILE_QUOTA):
4✔
91
                raise serializers.ValidationError({
4✔
92
                    'quota': [_('The file quota for this project has been reached.')]
93
                })
94

95

96
class ValueTypeValidator:
4✔
97

98
    def __call__(self, data):
4✔
99
        text = data.get('text')
4✔
100
        value_type = data.get('value_type')
4✔
101

102
        try:
4✔
103
            self.validate(text, value_type)
4✔
104
        except ValidationError as e:
4✔
105
            raise serializers.ValidationError({
4✔
106
                'text': [e.message]
107
            }) from e
108

109
    def validate(self, text, value_type):
4✔
110
        if text and settings.PROJECT_VALUES_VALIDATION:
4✔
111
            if value_type == VALUE_TYPE_URL and settings.PROJECT_VALUES_VALIDATION_URL:
4✔
112
                URLValidator()(text)
4✔
113

114
            elif value_type == VALUE_TYPE_INTEGER and settings.PROJECT_VALUES_VALIDATION_INTEGER:
4✔
115
                RegexValidator(
4✔
116
                    settings.PROJECT_VALUES_VALIDATION_INTEGER_REGEX,
117
                    settings.PROJECT_VALUES_VALIDATION_INTEGER_MESSAGE
118
                )(text)
119

120
            elif value_type == VALUE_TYPE_FLOAT and settings.PROJECT_VALUES_VALIDATION_FLOAT:
4✔
121
                RegexValidator(
4✔
122
                    settings.PROJECT_VALUES_VALIDATION_FLOAT_REGEX,
123
                    settings.PROJECT_VALUES_VALIDATION_FLOAT_MESSAGE
124
                )(text)
125

126
            elif value_type == VALUE_TYPE_BOOLEAN and settings.PROJECT_VALUES_VALIDATION_BOOLEAN:
4✔
127
                RegexValidator(
4✔
128
                    settings.PROJECT_VALUES_VALIDATION_BOOLEAN_REGEX,
129
                    settings.PROJECT_VALUES_VALIDATION_BOOLEAN_MESSAGE
130
                )(text)
131

132
            elif value_type == VALUE_TYPE_DATE and settings.PROJECT_VALUES_VALIDATION_DATE:
4✔
133
                RegexValidator(
4✔
134
                    settings.PROJECT_VALUES_VALIDATION_DATE_REGEX,
135
                    settings.PROJECT_VALUES_VALIDATION_DATE_MESSAGE
136
                )(text)
137

138
            elif value_type == VALUE_TYPE_DATETIME and settings.PROJECT_VALUES_VALIDATION_DATETIME:
4✔
139
                try:
4✔
140
                    datetime.fromisoformat(text)
4✔
141
                except ValueError as e:
4✔
142
                    raise ValidationError(_('Enter a valid datetime.')) from e
4✔
143

144
            elif value_type == VALUE_TYPE_EMAIL and settings.PROJECT_VALUES_VALIDATION_EMAIL:
4✔
145
                EmailValidator()(text)
4✔
146

147
            elif value_type == VALUE_TYPE_PHONE and settings.PROJECT_VALUES_VALIDATION_PHONE:
4✔
148
                RegexValidator(
4✔
149
                    settings.PROJECT_VALUES_VALIDATION_PHONE_REGEX,
150
                    settings.PROJECT_VALUES_VALIDATION_PHONE_MESSAGE
151
                )(text)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc