Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django_filters/utils.py: 56%

143 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import warnings 

2from collections import OrderedDict 

3 

4from django.conf import settings 

5from django.core.exceptions import FieldDoesNotExist, FieldError 

6from django.db import models 

7from django.db.models.constants import LOOKUP_SEP 

8from django.db.models.expressions import Expression 

9from django.db.models.fields.related import ForeignObjectRel, RelatedField 

10from django.utils import timezone 

11from django.utils.encoding import force_str 

12from django.utils.text import capfirst 

13from django.utils.translation import gettext as _ 

14 

15from .exceptions import FieldLookupError 

16 

17 

18def deprecate(msg, level_modifier=0): 

19 warnings.warn(msg, MigrationNotice, stacklevel=3 + level_modifier) 

20 

21 

22class MigrationNotice(DeprecationWarning): 

23 url = "https://django-filter.readthedocs.io/en/main/guide/migration.html" 

24 

25 def __init__(self, message): 

26 super().__init__("%s See: %s" % (message, self.url)) 

27 

28 

29class RenameAttributesBase(type): 

30 """ 

31 Handles the deprecation paths when renaming an attribute. 

32 

33 It does the following: 

34 - Defines accessors that redirect to the renamed attributes. 

35 - Complain whenever an old attribute is accessed. 

36 

37 This is conceptually based on `django.utils.deprecation.RenameMethodsBase`. 

38 """ 

39 

40 renamed_attributes = () 

41 

42 def __new__(metacls, name, bases, attrs): 

43 # remove old attributes before creating class 

44 old_names = [r[0] for r in metacls.renamed_attributes] 

45 old_names = [name for name in old_names if name in attrs] 

46 old_attrs = {name: attrs.pop(name) for name in old_names} 

47 

48 # get a handle to any accessors defined on the class 

49 cls_getattr = attrs.pop("__getattr__", None) 

50 cls_setattr = attrs.pop("__setattr__", None) 

51 

52 new_class = super().__new__(metacls, name, bases, attrs) 

53 

54 def __getattr__(self, name): 

55 name = type(self).get_name(name) 

56 if cls_getattr is not None: 

57 return cls_getattr(self, name) 

58 elif hasattr(super(new_class, self), "__getattr__"): 

59 return super(new_class, self).__getattr__(name) 

60 return self.__getattribute__(name) 

61 

62 def __setattr__(self, name, value): 

63 name = type(self).get_name(name) 

64 if cls_setattr is not None: 

65 return cls_setattr(self, name, value) 

66 return super(new_class, self).__setattr__(name, value) 

67 

68 new_class.__getattr__ = __getattr__ 

69 new_class.__setattr__ = __setattr__ 

70 

71 # set renamed attributes 

72 for name, value in old_attrs.items(): 

73 setattr(new_class, name, value) 

74 

75 return new_class 

76 

77 def get_name(metacls, name): 

78 """ 

79 Get the real attribute name. If the attribute has been renamed, 

80 the new name will be returned and a deprecation warning issued. 

81 """ 

82 for renamed_attribute in metacls.renamed_attributes: 

83 old_name, new_name, deprecation_warning = renamed_attribute 

84 

85 if old_name == name: 

86 warnings.warn( 

87 "`%s.%s` attribute should be renamed `%s`." 

88 % (metacls.__name__, old_name, new_name), 

89 deprecation_warning, 

90 3, 

91 ) 

92 return new_name 

93 

94 return name 

95 

96 def __getattr__(metacls, name): 

97 return super().__getattribute__(metacls.get_name(name)) 

98 

99 def __setattr__(metacls, name, value): 

100 return super().__setattr__(metacls.get_name(name), value) 

101 

102 

103def try_dbfield(fn, field_class): 

104 """ 

105 Try ``fn`` with the DB ``field_class`` by walking its 

106 MRO until a result is found. 

107 

108 ex:: 

109 _try_dbfield(field_dict.get, models.CharField) 

110 

111 """ 

112 # walk the mro, as field_class could be a derived model field. 

113 for cls in field_class.mro(): 113 ↛ exitline 113 didn't return from function 'try_dbfield', because the loop on line 113 didn't complete

114 # skip if cls is models.Field 

115 if cls is models.Field: 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true

116 continue 

117 

118 data = fn(cls) 

119 if data: 

120 return data 

121 

122 

123def get_all_model_fields(model): 

124 opts = model._meta 

125 

126 return [ 

127 f.name 

128 for f in sorted(opts.fields + opts.many_to_many) 

129 if not isinstance(f, models.AutoField) 

130 and not (getattr(f.remote_field, "parent_link", False)) 

131 ] 

132 

133 

134def get_model_field(model, field_name): 

135 """ 

136 Get a ``model`` field, traversing relationships 

137 in the ``field_name``. 

138 

139 ex:: 

140 

141 f = get_model_field(Book, 'author__first_name') 

142 

143 """ 

144 fields = get_field_parts(model, field_name) 

145 return fields[-1] if fields else None 

146 

147 

148def get_field_parts(model, field_name): 

149 """ 

150 Get the field parts that represent the traversable relationships from the 

151 base ``model`` to the final field, described by ``field_name``. 

152 

153 ex:: 

154 

155 >>> parts = get_field_parts(Book, 'author__first_name') 

156 >>> [p.verbose_name for p in parts] 

157 ['author', 'first name'] 

158 

159 """ 

160 parts = field_name.split(LOOKUP_SEP) 

161 opts = model._meta 

162 fields = [] 

163 

164 # walk relationships 

165 for name in parts: 

166 try: 

167 field = opts.get_field(name) 

168 except FieldDoesNotExist: 

169 return None 

170 

171 fields.append(field) 

172 try: 

173 if isinstance(field, RelatedField): 

174 opts = field.remote_field.model._meta 

175 elif isinstance(field, ForeignObjectRel): 

176 opts = field.related_model._meta 

177 except AttributeError: 

178 # Lazy relationships are not resolved until registry is populated. 

179 raise RuntimeError( 

180 "Unable to resolve relationship `%s` for `%s`. Django is most " 

181 "likely not initialized, and its apps registry not populated. " 

182 "Ensure Django has finished setup before loading `FilterSet`s." 

183 % (field_name, model._meta.label) 

184 ) 

185 

186 return fields 

187 

188 

189def resolve_field(model_field, lookup_expr): 

190 """ 

191 Resolves a ``lookup_expr`` into its final output field, given 

192 the initial ``model_field``. The lookup expression should only contain 

193 transforms and lookups, not intermediary model field parts. 

194 

195 Note: 

196 This method is based on django.db.models.sql.query.Query.build_lookup 

197 

198 For more info on the lookup API: 

199 https://docs.djangoproject.com/en/stable/ref/models/lookups/ 

200 

201 """ 

202 query = model_field.model._default_manager.all().query 

203 lhs = Expression(model_field) 

204 lookups = lookup_expr.split(LOOKUP_SEP) 

205 

206 assert len(lookups) > 0 

207 

208 try: 

209 while lookups: 209 ↛ exitline 209 didn't return from function 'resolve_field', because the condition on line 209 was never false

210 name = lookups[0] 

211 args = (lhs, name) 

212 # If there is just one part left, try first get_lookup() so 

213 # that if the lhs supports both transform and lookup for the 

214 # name, then lookup will be picked. 

215 if len(lookups) == 1: 215 ↛ 224line 215 didn't jump to line 224, because the condition on line 215 was never false

216 final_lookup = lhs.get_lookup(name) 

217 if not final_lookup: 217 ↛ 221line 217 didn't jump to line 221, because the condition on line 217 was never true

218 # We didn't find a lookup. We are going to interpret 

219 # the name as transform, and do an Exact lookup against 

220 # it. 

221 lhs = query.try_transform(*args) 

222 final_lookup = lhs.get_lookup("exact") 

223 return lhs.output_field, final_lookup.lookup_name 

224 lhs = query.try_transform(*args) 

225 lookups = lookups[1:] 

226 except FieldError as e: 

227 raise FieldLookupError(model_field, lookup_expr) from e 

228 

229 

230def handle_timezone(value, is_dst=None): 

231 if settings.USE_TZ and timezone.is_naive(value): 

232 return timezone.make_aware(value, timezone.get_current_timezone(), is_dst) 

233 elif not settings.USE_TZ and timezone.is_aware(value): 

234 return timezone.make_naive(value, timezone.utc) 

235 return value 

236 

237 

238def verbose_field_name(model, field_name): 

239 """ 

240 Get the verbose name for a given ``field_name``. The ``field_name`` 

241 will be traversed across relationships. Returns '[invalid name]' for 

242 any field name that cannot be traversed. 

243 

244 ex:: 

245 

246 >>> verbose_field_name(Article, 'author__name') 

247 'author name' 

248 

249 """ 

250 if field_name is None: 250 ↛ 251line 250 didn't jump to line 251, because the condition on line 250 was never true

251 return "[invalid name]" 

252 

253 parts = get_field_parts(model, field_name) 

254 if not parts: 

255 return "[invalid name]" 

256 

257 names = [] 

258 for part in parts: 

259 if isinstance(part, ForeignObjectRel): 259 ↛ 260line 259 didn't jump to line 260, because the condition on line 259 was never true

260 if part.related_name: 

261 names.append(part.related_name.replace("_", " ")) 

262 else: 

263 return "[invalid name]" 

264 else: 

265 names.append(force_str(part.verbose_name)) 

266 

267 return " ".join(names) 

268 

269 

270def verbose_lookup_expr(lookup_expr): 

271 """ 

272 Get a verbose, more humanized expression for a given ``lookup_expr``. 

273 Each part in the expression is looked up in the ``FILTERS_VERBOSE_LOOKUPS`` 

274 dictionary. Missing keys will simply default to itself. 

275 

276 ex:: 

277 

278 >>> verbose_lookup_expr('year__lt') 

279 'year is less than' 

280 

281 # with `FILTERS_VERBOSE_LOOKUPS = {}` 

282 >>> verbose_lookup_expr('year__lt') 

283 'year lt' 

284 

285 """ 

286 from .conf import settings as app_settings 

287 

288 VERBOSE_LOOKUPS = app_settings.VERBOSE_LOOKUPS or {} 

289 lookups = [ 

290 force_str(VERBOSE_LOOKUPS.get(lookup, _(lookup))) 

291 for lookup in lookup_expr.split(LOOKUP_SEP) 

292 ] 

293 

294 return " ".join(lookups) 

295 

296 

297def label_for_filter(model, field_name, lookup_expr, exclude=False): 

298 """ 

299 Create a generic label suitable for a filter. 

300 

301 ex:: 

302 

303 >>> label_for_filter(Article, 'author__name', 'in') 

304 'auther name is in' 

305 

306 """ 

307 name = verbose_field_name(model, field_name) 

308 verbose_expression = [_("exclude"), name] if exclude else [name] 

309 

310 # iterable lookups indicate a LookupTypeField, which should not be verbose 

311 if isinstance(lookup_expr, str): 311 ↛ 314line 311 didn't jump to line 314, because the condition on line 311 was never false

312 verbose_expression += [verbose_lookup_expr(lookup_expr)] 

313 

314 verbose_expression = [force_str(part) for part in verbose_expression if part] 

315 verbose_expression = capfirst(" ".join(verbose_expression)) 

316 

317 return verbose_expression 

318 

319 

320def translate_validation(error_dict): 

321 """ 

322 Translate a Django ErrorDict into its DRF ValidationError. 

323 """ 

324 # it's necessary to lazily import the exception, as it can otherwise create 

325 # an import loop when importing django_filters inside the project settings. 

326 from rest_framework.exceptions import ErrorDetail, ValidationError 

327 

328 exc = OrderedDict( 

329 ( 

330 key, 

331 [ 

332 ErrorDetail(e.message % (e.params or ()), code=e.code) 

333 for e in error_list 

334 ], 

335 ) 

336 for key, error_list in error_dict.as_data().items() 

337 ) 

338 

339 return ValidationError(exc)