Coverage for apis_core/generic/helpers.py: 37%

57 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-16 07:42 +0000

1import functools 

2import logging 

3 

4from django.contrib.auth import get_permission_codename 

5from django.db.models import CharField, Model, Q, TextField 

6from django.utils import module_loading 

7 

8logger = logging.getLogger(__name__) 

9 

10 

11def generate_search_filter(model, query, fields_to_search=None, prefix=""): 

12 """ 

13 Generate a default search filter that searches for the `query` 

14 in all the CharFields and TextFields of a model (case-insensitive) 

15 or in the fields listed in the models `default_search_fields` attribute 

16 or in the fields listed in the `fields_to_search` argument 

17 This helper can be used by autocomplete querysets if nothing 

18 fancier is needed. 

19 If the `prefix` is set, the field names will be prefixed with that string - 

20 this can be useful if you want to use the `generate_search_filter` in a 

21 `Q` combined query while searching over multiple models. 

22 """ 

23 query = query.split() 

24 

25 modelfields = model._meta.fields 

26 # search all char and text fields by default 

27 _fields_to_search = [ 

28 field.name for field in modelfields if isinstance(field, (CharField, TextField)) 

29 ] 

30 

31 # check if the model has a `_default_search_fields` 

32 # list and use that as searchfields 

33 if isinstance(getattr(model, "_default_search_fields", None), list): 

34 _fields_to_search = model._default_search_fields 

35 

36 # if the method was passed a `fields_to_search` list, use that 

37 if isinstance(fields_to_search, list): 

38 _fields_to_search = fields_to_search 

39 

40 q = Q() 

41 

42 for token in query: 

43 q &= functools.reduce( 

44 lambda acc, field_name: acc 

45 | Q(**{f"{prefix}{field_name}__icontains": token}), 

46 _fields_to_search, 

47 Q(), 

48 ) 

49 return q 

50 

51 

52@functools.lru_cache 

53def mro_paths(model): 

54 """ 

55 Create a list of MRO classes for a Django model 

56 """ 

57 paths = [] 

58 for cls in filter(lambda x: x not in Model.mro(), model.mro()): 

59 paths.append(cls.__module__.split(".")[:-1] + [cls.__name__]) 

60 return paths 

61 

62 

63@functools.lru_cache 

64def template_names_via_mro(model, suffix=""): 

65 """ 

66 Use the MRO to generate a list of template names for a model 

67 """ 

68 mro_prefix_list = ["/".join(prefix) for prefix in mro_paths(model)] 

69 return [f"{prefix.lower()}{suffix}" for prefix in mro_prefix_list] 

70 

71 

72@functools.lru_cache 

73def permission_fullname(action: str, model: object) -> str: 

74 permission_codename = get_permission_codename(action, model._meta) 

75 return f"{model._meta.app_label}.{permission_codename}" 

76 

77 

78@functools.lru_cache 

79def module_paths(model, path: str = "", suffix: str = "") -> list: 

80 paths = list(map(lambda x: x[:-1] + [path] + x[-1:], mro_paths(model))) 

81 classes = tuple(".".join(prefix) + suffix for prefix in paths) 

82 return classes 

83 

84 

85@functools.lru_cache 

86def makeclassprefix(string: str) -> str: 

87 string = "".join([c if c.isidentifier() else " " for c in string]) 

88 string = "".join([word.strip().capitalize() for word in string.split(" ")]) 

89 return string 

90 

91 

92@functools.lru_cache 

93def import_string(dotted_path): 

94 try: 

95 return module_loading.import_string(dotted_path) 

96 except (ModuleNotFoundError, ImportError): 

97 return False 

98 

99 

100@functools.lru_cache 

101def first_member_match(dotted_path_list: tuple[str], fallback=None) -> object: 

102 logger.debug("Looking for matching class in %s", dotted_path_list) 

103 pathgen = map(import_string, dotted_path_list) 

104 result = next(filter(bool, pathgen), None) 

105 if result: 

106 logger.debug("Found matching attribute/class in %s", result) 

107 else: 

108 logger.debug("Found nothing, returning fallback: %s", fallback) 

109 return result or fallback