Coverage for apis_core/generic/helpers.py: 37%
60 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-20 09:24 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-20 09:24 +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 default_search_fields(model, field_names=None):
12 """
13 Retrieve the default model fields to use for a search operation
14 By default those are all the CharFields and TextFields of a model.
15 It is also possible to define those fields on the model using the
16 `_default_search_fields` attribute.
17 The method also takes a `field_names` argument to override the list
18 of fields.
19 """
20 default_types = (CharField, TextField)
21 fields = [
22 field for field in model._meta.get_fields() if isinstance(field, default_types)
23 ]
24 # check if the model has a `_default_search_fields`
25 # list and use that as searchfields
26 if isinstance(getattr(model, "_default_search_fields", None), list):
27 fields = [
28 model._meta.get_field(field) for field in model._default_search_fields
29 ]
30 # if `fields_to_search` is a list, use that
31 if isinstance(field_names, list):
32 fields = [model._meta.get_field(field) for field in field_names]
33 return fields
36def generate_search_filter(model, query, fields_to_search=None, prefix=""):
37 """
38 Generate a default search filter that searches for the `query`
39 This helper can be used by autocomplete querysets if nothing
40 fancier is needed.
41 If the `prefix` is set, the field names will be prefixed with that string -
42 this can be useful if you want to use the `generate_search_filter` in a
43 `Q` combined query while searching over multiple models.
44 """
45 query = query.split()
47 _fields_to_search = [
48 field.name for field in default_search_fields(model, fields_to_search)
49 ]
51 q = Q()
53 for token in query:
54 q &= functools.reduce(
55 lambda acc, field_name: acc
56 | Q(**{f"{prefix}{field_name}__icontains": token}),
57 _fields_to_search,
58 Q(),
59 )
60 return q
63@functools.lru_cache
64def mro_paths(model):
65 """
66 Create a list of MRO classes for a Django model
67 """
68 paths = []
69 for cls in filter(lambda x: x not in Model.mro(), model.mro()):
70 paths.append(cls.__module__.split(".")[:-1] + [cls.__name__])
71 return paths
74@functools.lru_cache
75def template_names_via_mro(model, suffix=""):
76 """
77 Use the MRO to generate a list of template names for a model
78 """
79 mro_prefix_list = ["/".join(prefix) for prefix in mro_paths(model)]
80 return [f"{prefix.lower()}{suffix}" for prefix in mro_prefix_list]
83@functools.lru_cache
84def permission_fullname(action: str, model: object) -> str:
85 permission_codename = get_permission_codename(action, model._meta)
86 return f"{model._meta.app_label}.{permission_codename}"
89@functools.lru_cache
90def module_paths(model, path: str = "", suffix: str = "") -> list:
91 paths = list(map(lambda x: x[:-1] + [path] + x[-1:], mro_paths(model)))
92 classes = tuple(".".join(prefix) + suffix for prefix in paths)
93 return classes
96@functools.lru_cache
97def makeclassprefix(string: str) -> str:
98 string = "".join([c if c.isidentifier() else " " for c in string])
99 string = "".join([word.strip().capitalize() for word in string.split(" ")])
100 return string
103@functools.lru_cache
104def import_string(dotted_path):
105 try:
106 return module_loading.import_string(dotted_path)
107 except (ModuleNotFoundError, ImportError):
108 return False
111@functools.lru_cache
112def first_member_match(dotted_path_list: tuple[str], fallback=None) -> object:
113 logger.debug("Looking for matching class in %s", dotted_path_list)
114 pathgen = map(import_string, dotted_path_list)
115 result = next(filter(bool, pathgen), None)
116 if result:
117 logger.debug("Found matching attribute/class in %s", result)
118 else:
119 logger.debug("Found nothing, returning fallback: %s", fallback)
120 return result or fallback