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