Source code for apis_core.utils.helpers

import difflib

from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
from django.db import DEFAULT_DB_ALIAS, router

from apis_core.apis_metainfo.models import Uri
from apis_core.generic.helpers import first_member_match, module_paths


[docs] def datadump_get_objects(models: list = [], *args, **kwargs): for model in models: if not model._meta.proxy and router.allow_migrate_model( DEFAULT_DB_ALIAS, model ): objects = model._default_manager queryset = objects.using(DEFAULT_DB_ALIAS).order_by(model._meta.pk.name) yield from queryset.iterator()
[docs] def datadump_get_queryset(additional_app_labels: list = []): """ This method is loosely based on the `dumpdata` admin command. It iterates throug the relevant app models and exports them using a serializer and natural foreign keys. Data exported this way can be reimported into a newly created Django APIS app """ # get all APIS apps and all APIS models apis_app_labels = ["apis_relations", "apis_metainfo"] apis_app_models = [ model for model in apps.get_models() if model._meta.app_label in apis_app_labels ] # create a list of app labels we want to iterate # this allows to extend the apps via the ?app_labels= parameter app_labels = set(apis_app_labels) app_labels |= set(additional_app_labels) # look for models that inherit from APIS models and add their # app label to app_labels for model in apps.get_models(): if any(map(lambda x: issubclass(model, x), apis_app_models)): app_labels.add(model._meta.app_label) # now go through all app labels app_list = {} for app_label in app_labels: app_config = apps.get_app_config(app_label) app_list[app_config] = None models = serializers.sort_dependencies(app_list.items(), allow_cycles=True) yield from datadump_get_objects(models)
[docs] def datadump_serializer(additional_app_labels: list = [], serialier_format="json"): return serializers.serialize( serialier_format, datadump_get_queryset(additional_app_labels), use_natural_foreign_keys=True, )
[docs] def get_importer_for_model(model: object): importer_paths = module_paths(model, path="importers", suffix="Importer") if importer := first_member_match(importer_paths): return importer raise ImproperlyConfigured(f"No suitable importer found for {model}")
[docs] def create_object_from_uri(uri: str, model: object, raise_on_fail=False) -> object: if uri.startswith("http"): try: uri = Uri.objects.get(uri=uri) return uri.root_object except Uri.DoesNotExist: Importer = get_importer_for_model(model) importer = Importer(uri, model) instance = importer.create_instance() uri = Uri.objects.create(uri=importer.get_uri, root_object=instance) return instance if raise_on_fail: content_type = ContentType.objects.get_for_model(model) raise ImproperlyConfigured( f'Could not create {content_type.name} from string "{uri}"' ) return False
[docs] def get_html_diff(a, b, show_a=True, show_b=True, shorten=0): """ Create an colorized html represenation of the difference of two values a and b If `show_a` is True, colorize deletions in `a` If `show_b` is True, colorize insertions in `b` The value of `shorten` defines if long parts of strings that contains no change should be shortened """ def style_remove(text): return f"<span class='diff-remove'>{text}</span>" def style_insert(text): return f"<span class='diff-insert'>{text}</span>" nones = ["", None] if a in nones and b in nones: result = "" elif a in nones: result = style_insert(b) if show_b else "" elif b in nones: result = style_remove(a) if show_a else "" else: result = "" a = str(a) b = str(b) codes = difflib.SequenceMatcher(None, a, b).get_opcodes() for opcode, a_start, a_end, b_start, b_end in codes: match opcode: case "equal": equal = a[a_start:a_end] if shorten and len(equal) > shorten: equal = equal[:5] + " ... " + equal[-10:] result += equal case "delete": if show_a: result += style_remove(a[a_start:a_end]) case "insert": if show_b: result += style_insert(b[b_start:b_end]) case "replace": if show_b: result += style_insert(b[b_start:b_end]) if show_a: result += style_remove(a[a_start:a_end]) return result
[docs] def construct_lookup(value: str) -> tuple[str, str]: """ Helper method to parse input values and construct field lookups (https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups) Parses user input for wildcards and returns a tuple containing the interpreted django lookup string and the trimmed value E.g. - ``example`` -> ``('__icontains', 'example')`` - ``*example*`` -> ``('__icontains', 'example')`` - ``*example`` -> ``('__iendswith', 'example')`` - ``example*``-> ``('__istartswith', 'example')`` - ``"example"`` -> ``('__iexact', 'example')`` :param str value: text to be parsed for ``*`` :return: a tuple containing the lookup type and the value without modifiers """ if value.startswith("*") and not value.endswith("*"): value = value[1:] return "__iendswith", value elif not value.startswith("*") and value.endswith("*"): value = value[:-1] return "__istartswith", value elif value.startswith('"') and value.endswith('"'): value = value[1:-1] return "__iexact", value else: if value.startswith("*") and value.endswith("*"): value = value[1:-1] return "__icontains", value