Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

birger/512 uri #1463

Merged
merged 7 commits into from
Jan 8, 2025
8 changes: 7 additions & 1 deletion apis_core/apis_entities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import NoReverseMatch, reverse
Expand Down Expand Up @@ -102,4 +103,9 @@ def create_default_uri(sender, instance, created, raw, using, update_fields, **k
"apis_core:GetEntityGeneric", kwargs={"pk": instance.pk}
)
uri = f"{base}{route}"
Uri.objects.create(uri=uri, root_object=instance)
content_type = ContentType.objects.get_for_model(instance)
Uri.objects.create(
uri=uri,
content_type=content_type,
object_id=instance.id,
)
2 changes: 2 additions & 0 deletions apis_core/apis_metainfo/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class MetainfoConfig(AppConfig):
name = "apis_core.apis_metainfo"

def ready(self):
from . import signals # noqa: F401

if getattr(settings, "APIS_BASE_URI", None) is None:
logger.warning(
"You should set the APIS_BASE_URI setting - we are using https://example.org as a fallback!"
Expand Down
3 changes: 0 additions & 3 deletions apis_core/apis_metainfo/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@

class UriFilterSet(GenericFilterSet):
uri = django_filters.CharFilter(lookup_expr="icontains")

class Meta(GenericFilterSet.Meta):
exclude = ["root_object"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 5.1.1 on 2024-11-29 08:31

import django.db.models.deletion
from django.db import migrations, models


def copy_root_object(apps, schema_editor):
Uri = apps.get_model("apis_metainfo", "Uri")
RootObject = apps.get_model("apis_metainfo", "RootObject")
for uri in Uri.objects.all():
if uri.root_object_id:
root_object = RootObject.objects.get(pk=uri.root_object_id)
uri.content_type_id = root_object.self_contenttype_id
uri.object_id = root_object.id
uri.save()


def reverse_copy_root_object(apps, schema_editor):
Uri = apps.get_model("apis_metainfo", "Uri")
for uri in Uri.objects.all():
if uri.content_type_id and uri.object_id:
uri.root_object_id = uri.object_id
uri.save()


class Migration(migrations.Migration):
dependencies = [
("apis_metainfo", "0014_remove_uri_domain_remove_uri_loaded_and_more"),
("contenttypes", "0002_remove_content_type_name"),
]

operations = [
migrations.AddField(
model_name="uri",
name="content_type",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
),
),
migrations.AddField(
model_name="uri",
name="object_id",
field=models.PositiveIntegerField(null=True),
),
migrations.RunPython(copy_root_object, reverse_copy_root_object),
migrations.RemoveField(model_name="uri", name="root_object"),
]
18 changes: 10 additions & 8 deletions apis_core/apis_metainfo/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from urllib.parse import urlsplit

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models
Expand Down Expand Up @@ -66,9 +67,9 @@ def get_queryset(self):

class Uri(GenericModel, models.Model):
uri = models.URLField(blank=True, null=True, unique=True, max_length=255)
root_object = InheritanceForeignKey(
RootObject, blank=True, null=True, on_delete=models.CASCADE
)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey()

objects = UriManager()

Expand All @@ -79,9 +80,9 @@ def get_web_object(self):
result = {
"relation_pk": self.pk,
"relation_type": "uri",
"related_root_object": self.root_object.name,
"related_root_object_url": self.root_object.get_absolute_url(),
"related_root_object_class_name": self.root_object.__class__.__name__.lower(),
"related_root_object": self.content_object.name,
"related_root_object_url": self.content_object.get_absolute_url(),
"related_root_object_class_name": self.content_object.__class__.__name__.lower(),
"uri": self.uri,
}
return result
Expand All @@ -92,7 +93,7 @@ def save(self, *args, **kwargs):

def clean(self):
self.uri = clean_uri(self.uri)
if self.uri and not hasattr(self, "root_object"):
if self.uri and not hasattr(self, "content_object"):
try:
definition, attributes = rdf.get_definition_and_attributes_from_uri(
self.uri
Expand All @@ -102,7 +103,8 @@ def clean(self):
ct = ContentType.objects.get_by_natural_key(app_label, model)
obj = ct.model_class()(**attributes)
obj.save()
self.root_object = obj
self.content_type = ContentType.objects.get_for_model(obj)
self.object_id = obj.id
else:
raise ImproperlyConfigured(
f"{self.uri}: did not find matching rdf defintion"
Expand Down
20 changes: 20 additions & 0 deletions apis_core/apis_metainfo/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging

from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_delete
from django.dispatch import receiver

from apis_core.apis_metainfo.models import Uri

logger = logging.getLogger(__name__)


@receiver(post_delete)
def remove_stale_uris(sender, instance, *args, **kwargs):
content_type = ContentType.objects.get_for_model(instance)
uris = Uri.objects.filter(content_type=content_type, object_id=instance.id)
for uri in uris:
logger.info(
"Deleting uri %s as a result of deleting %s", repr(uri), repr(instance)
)
uri.delete()
17 changes: 5 additions & 12 deletions apis_core/apis_metainfo/tables.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import django_tables2 as tables
from django.db.models import F

from apis_core.generic.tables import GenericTable

Expand All @@ -8,22 +7,16 @@

class UriTable(GenericTable):
entity = tables.TemplateColumn(
"<a href='{{ record.root_object.get_absolute_url }}'>{{ record.root_object }}</a>",
orderable=True,
"<a href='{{ record.content_object.get_absolute_url }}'>{{ record.content_object }}</a>",
orderable=False,
verbose_name="related Entity",
)
ent_type = tables.TemplateColumn(
"{{ record.root_object.self_contenttype.model }}",
content_type = tables.TemplateColumn(
"{{ record.content_type.model }}",
verbose_name="Entity Type",
)

class Meta(GenericTable.Meta):
model = Uri
fields = ["id", "uri", "entity", "ent_type"]
fields = ["id", "uri", "entity", "content_type"]
exclude = ("desc",)

def order_ent_type(self, queryset, is_descending):
queryset = queryset.annotate(
ent_type=F("root_object__self_contenttype__model")
).order_by(("-" if is_descending else "") + "ent_type")
return (queryset, True)
2 changes: 1 addition & 1 deletion apis_core/apis_metainfo/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def list(self, request):
uri = params.pop("uri", None)
if uri:
u = get_object_or_404(Uri, uri=request.query_params.get("uri"))
r = u.root_object.get_api_detail_endpoint()
r = u.content_object.get_api_detail_endpoint()
if params:
r += "?" + QueryDict.from_keys(params).urlencode()
return HttpResponseRedirect(r)
Expand Down
3 changes: 2 additions & 1 deletion apis_core/apis_relations/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,11 @@ def load_subj_obj_prop(
self.fields["property"].initial = property_initial_value
self.fields["property"].choices = [property_initial_value]

content_type = ContentType.objects.get_for_model(entity_instance_other)
other_entity_initial_value = (
str(
Uri.objects.filter(
root_object=entity_instance_other,
content_type=content_type, object_id=entity_instance_other.id
).first()
),
f"<span ><small>db</small> {str(entity_instance_other)}</span>",
Expand Down
15 changes: 14 additions & 1 deletion apis_core/generic/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,11 @@ def merge_with(self, entities):
for s in getattr(ent, f.name).all():
if s not in sl:
getattr(self, f.name).add(s)
Uri.objects.filter(root_object=ent).update(root_object=self)
self_content_type = ContentType.objects.get_for_model(self)
ent_content_type = ContentType.objects.get_for_model(ent)
Uri.objects.filter(content_type=ent_content_type, object_id=ent.id).update(
content_type=self_content_type, object_id=self.id
)

for ent in entities:
self.merge_fields(ent)
Expand Down Expand Up @@ -214,3 +218,12 @@ def duplicate(self):
return newobj

duplicate.alters_data = True

def uri_set(self):
ct = ContentType.objects.get_for_model(self)
return (
ContentType.objects.get(app_label="apis_metainfo", model="uri")
.model_class()
.objects.filter(content_type=ct, object_id=self.id)
.all()
)
12 changes: 9 additions & 3 deletions apis_core/generic/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
from django.forms import modelform_factory
from django.forms.utils import pretty_name
Expand Down Expand Up @@ -458,12 +459,12 @@ def get(self, *args, **kwargs):
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:
if uriobj.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))
return redirect(self.object.get_merge_url(uriobj.object_id))
except Uri.DoesNotExist:
pass
return super().get(*args, **kwargs)
Expand Down Expand Up @@ -500,7 +501,12 @@ def form_valid(self, form):
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)
content_type = ContentType.objects.get_for_model(self.model)
uri, created = Uri.objects.get_or_create(
uri=self.uri,
content_type=content_type,
object_id=self.object.id,
)
if created:
messages.info(self.request, f"Added uri {self.uri} to {self.object}")
return super().form_valid(form)
Expand Down
9 changes: 7 additions & 2 deletions apis_core/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,17 @@ def create_object_from_uri(uri: str, model: object, raise_on_fail=False) -> obje
if uri.startswith("http"):
try:
uri = Uri.objects.get(uri=uri)
return uri.root_object
return uri.content_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)
content_type = ContentType.objects.get_for_model(instance)
uri = Uri.objects.create(
uri=importer.get_uri,
content_type=content_type,
object_id=instance.id,
)
return instance
if raise_on_fail:
content_type = ContentType.objects.get_for_model(model)
Expand Down
Loading