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
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-16 07:42 +0000
1import functools
2import logging
4from django.contrib.auth import get_permission_codename
5from django.db.models import CharField, Model, Q, TextField
6from django.utils import module_loading
8logger = logging.getLogger(__name__)
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()
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 ]
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
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
40 q = Q()
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
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
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]
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}"
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
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
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
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