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

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 

6 

7from django.core import validators 

8from django.db import models 

9from django.utils.text import capfirst 

10 

11from rest_framework.compat import postgres_fields 

12from rest_framework.validators import UniqueValidator 

13 

14NUMERIC_FIELD_TYPES = ( 

15 models.IntegerField, models.FloatField, models.DecimalField, models.DurationField, 

16) 

17 

18 

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 

28 

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__ 

36 

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__) 

41 

42 def __setitem__(self, key, value): 

43 self.mapping[key] = value 

44 

45 

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 

53 

54 

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 } 

63 

64 

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) 

71 

72 # The following will only be used by ModelField classes. 

73 # Gets removed for everything else. 

74 kwargs['model_field'] = model_field 

75 

76 if model_field.verbose_name and needs_label(model_field, field_name): 

77 kwargs['label'] = capfirst(model_field.verbose_name) 

78 

79 if model_field.help_text: 

80 kwargs['help_text'] = model_field.help_text 

81 

82 max_digits = getattr(model_field, 'max_digits', None) 

83 if max_digits is not None: 

84 kwargs['max_digits'] = max_digits 

85 

86 decimal_places = getattr(model_field, 'decimal_places', None) 

87 if decimal_places is not None: 

88 kwargs['decimal_places'] = decimal_places 

89 

90 if isinstance(model_field, models.SlugField): 

91 kwargs['allow_unicode'] = model_field.allow_unicode 

92 

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'} 

97 

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 

103 

104 if model_field.has_default() or model_field.blank or model_field.null: 

105 kwargs['required'] = False 

106 

107 if model_field.null: 

108 kwargs['allow_null'] = True 

109 

110 if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))): 

111 kwargs['allow_blank'] = True 

112 

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 

115 

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 

118 

119 if model_field.match is not None: 

120 kwargs['match'] = model_field.match 

121 

122 if model_field.recursive is not False: 

123 kwargs['recursive'] = model_field.recursive 

124 

125 if model_field.allow_files is not True: 

126 kwargs['allow_files'] = model_field.allow_files 

127 

128 if model_field.allow_folders is not False: 

129 kwargs['allow_folders'] = model_field.allow_folders 

130 

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 ] 

146 

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 ] 

159 

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 ] 

167 

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 ] 

175 

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 ] 

182 

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 ] 

195 

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 ] 

205 

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 ] 

218 

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) 

230 

231 if validator_kwarg: 

232 kwargs['validators'] = validator_kwarg 

233 

234 return kwargs 

235 

236 

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 } 

246 

247 if to_many: 

248 kwargs['many'] = True 

249 

250 if to_field: 

251 kwargs['to_field'] = to_field 

252 

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) 

258 

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) 

262 

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 

276 

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 

288 

289 return kwargs 

290 

291 

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 

297 

298 

299def get_url_kwargs(model_field): 

300 return { 

301 'view_name': get_detail_view_name(model_field) 

302 }