Skip to content

Commit

Permalink
feat(generic): add a view for enriching entities with external data
Browse files Browse the repository at this point in the history
This view uses the importer to merge data from an URI (which is passed
as a GET parameter). If the URI is connected to another model instance,
the view redirects to the merge view. Otherwise the data from the URI
is fetched and presented to the user as a list of checkboxes for the
values that should be copied into the model instance.
If the URI is not yet connected to the instance, a new Uri
instance will be created and connected to the model instance.
  • Loading branch information
b1rger committed Oct 2, 2024
1 parent 3232ea6 commit 9e8fe5c
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 3 deletions.
4 changes: 4 additions & 0 deletions apis_core/generic/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
18 changes: 18 additions & 0 deletions apis_core/generic/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
14 changes: 14 additions & 0 deletions apis_core/generic/templates/generic/generic_enrich.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends basetemplate|default:"base.html" %}
{% load generic %}
{% load crispy_forms_tags %}

{% block content %}
<div class="container">
{% if form.fields %}
<h4 class="mb-4">
Updating <a href="{{ object.get_absolute_url }}">{{ object }}</a> using the data from <a href="{{ uri }}">{{ uri }}</a>
</h4>
{% crispy form form.helper %}
{% endif %}
</div>
{% endblock %}
1 change: 1 addition & 0 deletions apis_core/generic/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def to_url(self, value):
views.MergeWith.as_view(),
name="merge",
),
path("enrich/<int:pk>", views.Enrich.as_view(), name="enrich"),
path("autocomplete", views.Autocomplete.as_view(), name="autocomplete"),
path("import", views.Import.as_view(), name="import"),
path(
Expand Down
86 changes: 83 additions & 3 deletions apis_core/generic/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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()

0 comments on commit 9e8fe5c

Please sign in to comment.