Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/utils/field_mapping.py: 75%
146 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2Helper functions for mapping model fields to a dictionary of default
3keyword arguments that should be used for their equivalent serializer fields.
4"""
5import inspect
7from django.core import validators
8from django.db import models
9from django.utils.text import capfirst
11from rest_framework.compat import postgres_fields
12from rest_framework.validators import UniqueValidator
14NUMERIC_FIELD_TYPES = (
15 models.IntegerField, models.FloatField, models.DecimalField, models.DurationField,
16)
19class ClassLookupDict:
20 """
21 Takes a dictionary with classes as keys.
22 Lookups against this object will traverses the object's inheritance
23 hierarchy in method resolution order, and returns the first matching value
24 from the dictionary or raises a KeyError if nothing matches.
25 """
26 def __init__(self, mapping):
27 self.mapping = mapping
29 def __getitem__(self, key):
30 if hasattr(key, '_proxy_class'): 30 ↛ 33line 30 didn't jump to line 33, because the condition on line 30 was never true
31 # Deal with proxy classes. Ie. BoundField behaves as if it
32 # is a Field instance when using ClassLookupDict.
33 base_class = key._proxy_class
34 else:
35 base_class = key.__class__
37 for cls in inspect.getmro(base_class): 37 ↛ 40line 37 didn't jump to line 40, because the loop on line 37 didn't complete
38 if cls in self.mapping:
39 return self.mapping[cls]
40 raise KeyError('Class %s not found in lookup.' % base_class.__name__)
42 def __setitem__(self, key, value):
43 self.mapping[key] = value
46def needs_label(model_field, field_name):
47 """
48 Returns `True` if the label based on the model's verbose name
49 is not equal to the default label it would have based on it's field name.
50 """
51 default_label = field_name.replace('_', ' ').capitalize()
52 return capfirst(model_field.verbose_name) != default_label
55def get_detail_view_name(model):
56 """
57 Given a model class, return the view name to use for URL relationships
58 that refer to instances of the model.
59 """
60 return '%(model_name)s-detail' % {
61 'model_name': model._meta.object_name.lower()
62 }
65def get_field_kwargs(field_name, model_field):
66 """
67 Creates a default instance of a basic non-relational field.
68 """
69 kwargs = {}
70 validator_kwarg = list(model_field.validators)
72 # The following will only be used by ModelField classes.
73 # Gets removed for everything else.
74 kwargs['model_field'] = model_field
76 if model_field.verbose_name and needs_label(model_field, field_name):
77 kwargs['label'] = capfirst(model_field.verbose_name)
79 if model_field.help_text:
80 kwargs['help_text'] = model_field.help_text
82 max_digits = getattr(model_field, 'max_digits', None)
83 if max_digits is not None:
84 kwargs['max_digits'] = max_digits
86 decimal_places = getattr(model_field, 'decimal_places', None)
87 if decimal_places is not None:
88 kwargs['decimal_places'] = decimal_places
90 if isinstance(model_field, models.SlugField):
91 kwargs['allow_unicode'] = model_field.allow_unicode
93 if isinstance(model_field, models.TextField) and not model_field.choices or \
94 (postgres_fields and isinstance(model_field, postgres_fields.JSONField)) or \
95 (hasattr(models, 'JSONField') and isinstance(model_field, models.JSONField)):
96 kwargs['style'] = {'base_template': 'textarea.html'}
98 if isinstance(model_field, models.AutoField) or not model_field.editable:
99 # If this field is read-only, then return early.
100 # Further keyword arguments are not valid.
101 kwargs['read_only'] = True
102 return kwargs
104 if model_field.has_default() or model_field.blank or model_field.null:
105 kwargs['required'] = False
107 if model_field.null:
108 kwargs['allow_null'] = True
110 if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))):
111 kwargs['allow_blank'] = True
113 if not model_field.blank and (postgres_fields and isinstance(model_field, postgres_fields.ArrayField)): 113 ↛ 114line 113 didn't jump to line 114, because the condition on line 113 was never true
114 kwargs['allow_empty'] = False
116 if isinstance(model_field, models.FilePathField): 116 ↛ 117line 116 didn't jump to line 117, because the condition on line 116 was never true
117 kwargs['path'] = model_field.path
119 if model_field.match is not None:
120 kwargs['match'] = model_field.match
122 if model_field.recursive is not False:
123 kwargs['recursive'] = model_field.recursive
125 if model_field.allow_files is not True:
126 kwargs['allow_files'] = model_field.allow_files
128 if model_field.allow_folders is not False:
129 kwargs['allow_folders'] = model_field.allow_folders
131 if model_field.choices:
132 kwargs['choices'] = model_field.choices
133 else:
134 # Ensure that max_value is passed explicitly as a keyword arg,
135 # rather than as a validator.
136 max_value = next((
137 validator.limit_value for validator in validator_kwarg
138 if isinstance(validator, validators.MaxValueValidator)
139 ), None)
140 if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
141 kwargs['max_value'] = max_value
142 validator_kwarg = [
143 validator for validator in validator_kwarg
144 if not isinstance(validator, validators.MaxValueValidator)
145 ]
147 # Ensure that min_value is passed explicitly as a keyword arg,
148 # rather than as a validator.
149 min_value = next((
150 validator.limit_value for validator in validator_kwarg
151 if isinstance(validator, validators.MinValueValidator)
152 ), None)
153 if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
154 kwargs['min_value'] = min_value
155 validator_kwarg = [
156 validator for validator in validator_kwarg
157 if not isinstance(validator, validators.MinValueValidator)
158 ]
160 # URLField does not need to include the URLValidator argument,
161 # as it is explicitly added in.
162 if isinstance(model_field, models.URLField): 162 ↛ 163line 162 didn't jump to line 163, because the condition on line 162 was never true
163 validator_kwarg = [
164 validator for validator in validator_kwarg
165 if not isinstance(validator, validators.URLValidator)
166 ]
168 # EmailField does not need to include the validate_email argument,
169 # as it is explicitly added in.
170 if isinstance(model_field, models.EmailField):
171 validator_kwarg = [
172 validator for validator in validator_kwarg
173 if validator is not validators.validate_email
174 ]
176 # SlugField do not need to include the 'validate_slug' argument,
177 if isinstance(model_field, models.SlugField): 177 ↛ 178line 177 didn't jump to line 178, because the condition on line 177 was never true
178 validator_kwarg = [
179 validator for validator in validator_kwarg
180 if validator is not validators.validate_slug
181 ]
183 # IPAddressField do not need to include the 'validate_ipv46_address' argument,
184 if isinstance(model_field, models.GenericIPAddressField): 184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true
185 validator_kwarg = [
186 validator for validator in validator_kwarg
187 if validator is not validators.validate_ipv46_address
188 ]
189 # Our decimal validation is handled in the field code, not validator code.
190 if isinstance(model_field, models.DecimalField):
191 validator_kwarg = [
192 validator for validator in validator_kwarg
193 if not isinstance(validator, validators.DecimalValidator)
194 ]
196 # Ensure that max_length is passed explicitly as a keyword arg,
197 # rather than as a validator.
198 max_length = getattr(model_field, 'max_length', None)
199 if max_length is not None and (isinstance(model_field, (models.CharField, models.TextField, models.FileField))):
200 kwargs['max_length'] = max_length
201 validator_kwarg = [
202 validator for validator in validator_kwarg
203 if not isinstance(validator, validators.MaxLengthValidator)
204 ]
206 # Ensure that min_length is passed explicitly as a keyword arg,
207 # rather than as a validator.
208 min_length = next((
209 validator.limit_value for validator in validator_kwarg
210 if isinstance(validator, validators.MinLengthValidator)
211 ), None)
212 if min_length is not None and isinstance(model_field, models.CharField): 212 ↛ 213line 212 didn't jump to line 213, because the condition on line 212 was never true
213 kwargs['min_length'] = min_length
214 validator_kwarg = [
215 validator for validator in validator_kwarg
216 if not isinstance(validator, validators.MinLengthValidator)
217 ]
219 if getattr(model_field, 'unique', False):
220 unique_error_message = model_field.error_messages.get('unique', None)
221 if unique_error_message: 221 ↛ 226line 221 didn't jump to line 226, because the condition on line 221 was never false
222 unique_error_message = unique_error_message % {
223 'model_name': model_field.model._meta.verbose_name,
224 'field_label': model_field.verbose_name
225 }
226 validator = UniqueValidator(
227 queryset=model_field.model._default_manager,
228 message=unique_error_message)
229 validator_kwarg.append(validator)
231 if validator_kwarg:
232 kwargs['validators'] = validator_kwarg
234 return kwargs
237def get_relation_kwargs(field_name, relation_info):
238 """
239 Creates a default instance of a flat relational field.
240 """
241 model_field, related_model, to_many, to_field, has_through_model, reverse = relation_info
242 kwargs = {
243 'queryset': related_model._default_manager,
244 'view_name': get_detail_view_name(related_model)
245 }
247 if to_many:
248 kwargs['many'] = True
250 if to_field:
251 kwargs['to_field'] = to_field
253 limit_choices_to = model_field and model_field.get_limit_choices_to()
254 if limit_choices_to: 254 ↛ 255line 254 didn't jump to line 255, because the condition on line 254 was never true
255 if not isinstance(limit_choices_to, models.Q):
256 limit_choices_to = models.Q(**limit_choices_to)
257 kwargs['queryset'] = kwargs['queryset'].filter(limit_choices_to)
259 if has_through_model: 259 ↛ 260line 259 didn't jump to line 260, because the condition on line 259 was never true
260 kwargs['read_only'] = True
261 kwargs.pop('queryset', None)
263 if model_field:
264 if model_field.verbose_name and needs_label(model_field, field_name): 264 ↛ 265line 264 didn't jump to line 265, because the condition on line 264 was never true
265 kwargs['label'] = capfirst(model_field.verbose_name)
266 help_text = model_field.help_text
267 if help_text:
268 kwargs['help_text'] = help_text
269 if not model_field.editable: 269 ↛ 270line 269 didn't jump to line 270, because the condition on line 269 was never true
270 kwargs['read_only'] = True
271 kwargs.pop('queryset', None)
272 if kwargs.get('read_only', False): 272 ↛ 275line 272 didn't jump to line 275, because the condition on line 272 was never true
273 # If this field is read-only, then return early.
274 # No further keyword arguments are valid.
275 return kwargs
277 if model_field.has_default() or model_field.blank or model_field.null:
278 kwargs['required'] = False
279 if model_field.null:
280 kwargs['allow_null'] = True
281 if model_field.validators: 281 ↛ 282line 281 didn't jump to line 282, because the condition on line 281 was never true
282 kwargs['validators'] = model_field.validators
283 if getattr(model_field, 'unique', False):
284 validator = UniqueValidator(queryset=model_field.model._default_manager)
285 kwargs['validators'] = kwargs.get('validators', []) + [validator]
286 if to_many and not model_field.blank:
287 kwargs['allow_empty'] = False
289 return kwargs
292def get_nested_relation_kwargs(relation_info):
293 kwargs = {'read_only': True}
294 if relation_info.to_many:
295 kwargs['many'] = True
296 return kwargs
299def get_url_kwargs(model_field):
300 return {
301 'view_name': get_detail_view_name(model_field)
302 }