diff --git a/apis_core/generic/abc.py b/apis_core/generic/abc.py index 72d500aa2..c6c7f5444 100644 --- a/apis_core/generic/abc.py +++ b/apis_core/generic/abc.py @@ -25,6 +25,10 @@ def get_edit_url(self): ct = ContentType.objects.get_for_model(self) return reverse("apis_core:generic:update", args=[ct, self.id]) + def get_enrich_url(self): + ct = ContentType.objects.get_for_model(self) + return reverse("apis_core:generic:enrich", args=[ct, self.id]) + def get_absolute_url(self): ct = ContentType.objects.get_for_model(self) return reverse("apis_core:generic:detail", args=[ct, self.id]) diff --git a/apis_core/generic/forms/__init__.py b/apis_core/generic/forms/__init__.py index 7b788a9e0..0ff76306b 100644 --- a/apis_core/generic/forms/__init__.py +++ b/apis_core/generic/forms/__init__.py @@ -93,3 +93,21 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.add_input(Submit("submit", "Merge")) + + +class GenericEnrichForm(forms.Form): + def __init__(self, *args, **kwargs): + data = kwargs.pop("data", {}) + instance = kwargs.pop("instance", None) + super().__init__(*args, **kwargs) + for key, value in data.items(): + update_key = f"update_{key}" + self.fields[update_key] = forms.BooleanField( + required=False, + label=f"Update {key} from {getattr(instance, key)} to {value}", + ) + + self.fields[key] = forms.CharField(initial=value, required=False) + self.fields[key].widget = self.fields[key].hidden_widget() + self.helper = FormHelper() + self.helper.add_input(Submit("submit", "Submit")) diff --git a/apis_core/generic/templates/generic/generic_enrich.html b/apis_core/generic/templates/generic/generic_enrich.html new file mode 100644 index 000000000..286e527a7 --- /dev/null +++ b/apis_core/generic/templates/generic/generic_enrich.html @@ -0,0 +1,14 @@ +{% extends basetemplate|default:"base.html" %} +{% load generic %} +{% load crispy_forms_tags %} + +{% block content %} +
+ {% if form.fields %} +

+ Updating {{ object }} using the data from {{ uri }} +

+ {% crispy form form.helper %} + {% endif %} +
+{% endblock %} diff --git a/apis_core/generic/urls.py b/apis_core/generic/urls.py index c73a27a8f..62cc4f542 100644 --- a/apis_core/generic/urls.py +++ b/apis_core/generic/urls.py @@ -54,6 +54,7 @@ def to_url(self, value): views.MergeWith.as_view(), name="merge", ), + path("enrich/", views.Enrich.as_view(), name="enrich"), path("autocomplete", views.Autocomplete.as_view(), name="autocomplete"), path("import", views.Import.as_view(), name="import"), path( diff --git a/apis_core/generic/views.py b/apis_core/generic/views.py index 7faecc6c1..a27f2c961 100644 --- a/apis_core/generic/views.py +++ b/apis_core/generic/views.py @@ -6,8 +6,9 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin +from django.core.exceptions import ImproperlyConfigured from django.forms import modelform_factory -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, redirect from django.template.exceptions import TemplateDoesNotExist from django.template.loader import select_template from django.urls import reverse, reverse_lazy @@ -20,11 +21,17 @@ from django_tables2.columns import library from django_tables2.tables import table_factory +from apis_core.apis_metainfo.models import Uri from apis_core.core.mixins import ListViewObjectFilterMixin -from apis_core.utils.helpers import create_object_from_uri +from apis_core.utils.helpers import create_object_from_uri, get_importer_for_model from .filtersets import GenericFilterSet -from .forms import GenericImportForm, GenericMergeForm, GenericModelForm +from .forms import ( + GenericEnrichForm, + GenericImportForm, + GenericMergeForm, + GenericModelForm, +) from .helpers import ( first_member_match, generate_search_filter, @@ -382,5 +389,78 @@ def form_valid(self, form): messages.info(self.request, f"Merged values of {self.other} into {self.object}") return super().form_valid(form) + +class Enrich(GenericModelMixin, PermissionRequiredMixin, FormView): + """ + Enrich an entity with data from an external source + If so, it uses the proper Importer to get the data from the Uri and + provides the user with a form to select the fields that should be updated. + """ + + permission_action_required = "change" + template_name = "generic/generic_enrich.html" + form_class = GenericEnrichForm + importer_class = None + + def setup(self, *args, **kwargs): + super().setup(*args, **kwargs) + self.object = get_object_or_404(self.model, pk=self.kwargs["pk"]) + self.uri = self.request.GET.get("uri") + if not self.uri: + messages.error(self.request, "No uri parameter specified.") + self.importer_class = get_importer_for_model(self.model) + + def get(self, *args, **kwargs): + if self.uri.isdigit(): + return redirect(self.object.get_merge_url(self.uri)) + try: + uriobj = Uri.objects.get(uri=self.uri) + if uriobj.root_object.id != self.object.id: + messages.info( + self.request, + f"Object with URI {self.uri} already exists, you were redirected to the merge form.", + ) + return redirect(self.object.get_merge_url(uriobj.root_object.id)) + except Uri.DoesNotExist: + pass + return super().get(*args, **kwargs) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx["object"] = self.object + ctx["uri"] = self.uri + return ctx + + def get_form_kwargs(self, *args, **kwargs): + kwargs = super().get_form_kwargs(*args, **kwargs) + kwargs["instance"] = self.object + try: + importer = self.importer_class(self.uri, self.model) + kwargs["data"] = importer.get_data() + except ImproperlyConfigured as e: + messages.error(self.request, e) + return kwargs + + def form_valid(self, form): + """ + Go through all the form fields and extract the ones that + start with `update_` and that are set (those are the checkboxes that + select which fields to update). + Then use the importers `import_into_instance` method to set those + fields values on the model instance. + """ + update_fields = [ + key.removeprefix("update_") + for (key, value) in self.request.POST.items() + if key.startswith("update_") and value + ] + importer = self.importer_class(self.uri, self.model) + importer.import_into_instance(self.object, fields=update_fields) + messages.info(self.request, f"Updated fields {update_fields}") + uri, created = Uri.objects.get_or_create(uri=self.uri, root_object=self.object) + if created: + messages.info(self.request, f"Added uri {self.uri} to {self.object}") + return super().form_valid(form) + def get_success_url(self): return self.object.get_absolute_url()