Coverage for apis_core/utils/helpers.py: 29%
85 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-06-25 10:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-06-25 10:00 +0000
1import difflib
3from django.apps import apps
4from django.core import serializers
5from django.core.exceptions import ImproperlyConfigured
6from django.db import DEFAULT_DB_ALIAS, router
8from apis_core.generic.helpers import first_member_match, module_paths
11def datadump_get_objects(models: list = [], *args, **kwargs):
12 for model in models:
13 if not model._meta.proxy and router.allow_migrate_model(
14 DEFAULT_DB_ALIAS, model
15 ):
16 objects = model._default_manager
17 queryset = objects.using(DEFAULT_DB_ALIAS).order_by(model._meta.pk.name)
18 yield from queryset.iterator()
21def datadump_get_queryset(additional_app_labels: list = []):
22 """
23 This method is loosely based on the `dumpdata` admin command.
24 It iterates throug the relevant app models and exports them using
25 a serializer and natural foreign keys.
26 Data exported this way can be reimported into a newly created Django APIS app
27 """
29 # get all APIS apps and all APIS models
30 apis_app_labels = ["apis_relations", "apis_metainfo"]
31 apis_app_models = [
32 model for model in apps.get_models() if model._meta.app_label in apis_app_labels
33 ]
35 # create a list of app labels we want to iterate
36 # this allows to extend the apps via the ?app_labels= parameter
37 app_labels = set(apis_app_labels)
38 app_labels |= set(additional_app_labels)
40 # look for models that inherit from APIS models and add their
41 # app label to app_labels
42 for model in apps.get_models():
43 if any(map(lambda x: issubclass(model, x), apis_app_models)):
44 app_labels.add(model._meta.app_label)
46 # now go through all app labels
47 app_list = {}
48 for app_label in app_labels:
49 app_config = apps.get_app_config(app_label)
50 app_list[app_config] = None
52 models = serializers.sort_dependencies(app_list.items(), allow_cycles=True)
54 yield from datadump_get_objects(models)
57def datadump_serializer(additional_app_labels: list = [], serialier_format="json"):
58 return serializers.serialize(
59 serialier_format,
60 datadump_get_queryset(additional_app_labels),
61 use_natural_foreign_keys=True,
62 )
65def get_importer_for_model(model: object):
66 importer_paths = module_paths(model, path="importers", suffix="Importer")
67 if importer := first_member_match(importer_paths):
68 return importer
69 raise ImproperlyConfigured(f"No suitable importer found for {model}")
72def get_html_diff(a, b, show_a=True, show_b=True, shorten=0):
73 """
74 Create an colorized html represenation of the difference of two values a and b
75 If `show_a` is True, colorize deletions in `a`
76 If `show_b` is True, colorize insertions in `b`
77 The value of `shorten` defines if long parts of strings that contains no change should be shortened
78 """
80 def style_remove(text):
81 return f"<span class='diff-remove'>{text}</span>"
83 def style_insert(text):
84 return f"<span class='diff-insert'>{text}</span>"
86 nones = ["", None]
87 if a in nones and b in nones:
88 result = ""
89 elif a in nones:
90 result = style_insert(b) if show_b else ""
91 elif b in nones:
92 result = style_remove(a) if show_a else ""
93 else:
94 result = ""
95 a = str(a)
96 b = str(b)
97 codes = difflib.SequenceMatcher(None, a, b).get_opcodes()
98 for opcode, a_start, a_end, b_start, b_end in codes:
99 match opcode:
100 case "equal":
101 equal = a[a_start:a_end]
102 if shorten and len(equal) > shorten:
103 equal = equal[:5] + " ... " + equal[-10:]
104 result += equal
105 case "delete":
106 if show_a:
107 result += style_remove(a[a_start:a_end])
108 case "insert":
109 if show_b:
110 result += style_insert(b[b_start:b_end])
111 case "replace":
112 if show_b:
113 result += style_insert(b[b_start:b_end])
114 if show_a:
115 result += style_remove(a[a_start:a_end])
116 return result
119def construct_lookup(value: str) -> tuple[str, str]:
120 """
121 Helper method to parse input values and construct field lookups
122 (https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups)
123 Parses user input for wildcards and returns a tuple containing the
124 interpreted django lookup string and the trimmed value
125 E.g.
127 - ``example`` -> ``('__icontains', 'example')``
128 - ``*example*`` -> ``('__icontains', 'example')``
129 - ``*example`` -> ``('__iendswith', 'example')``
130 - ``example*``-> ``('__istartswith', 'example')``
131 - ``"example"`` -> ``('__iexact', 'example')``
133 :param str value: text to be parsed for ``*``
134 :return: a tuple containing the lookup type and the value without modifiers
135 """
137 if value.startswith("*") and not value.endswith("*"):
138 value = value[1:]
139 return "__iendswith", value
141 elif not value.startswith("*") and value.endswith("*"):
142 value = value[:-1]
143 return "__istartswith", value
145 elif value.startswith('"') and value.endswith('"'):
146 value = value[1:-1]
147 return "__iexact", value
149 else:
150 if value.startswith("*") and value.endswith("*"):
151 value = value[1:-1]
152 return "__icontains", value
155def flatten_if_single(value: list):
156 if len(value) == 1:
157 return value[0]
158 return value