-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip(relations): new relation implementation
- Loading branch information
Showing
20 changed files
with
570 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.contrib import admin | ||
|
||
from .models import Relation | ||
|
||
admin.site.register(Relation) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class ApisRelationsConfig(AppConfig): | ||
default_auto_field = "django.db.models.AutoField" | ||
name = "apis_core.relations" | ||
|
||
def ready(self): | ||
from . import signals |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import urllib.parse | ||
from django.urls import reverse | ||
from django.contrib.contenttypes.models import ContentType | ||
|
||
from apis_core.generic.forms import GenericModelForm | ||
|
||
|
||
class RelationForm(GenericModelForm): | ||
|
||
def __init__(self, *args, **kwargs): | ||
print(kwargs) | ||
fromcontenttype = kwargs.pop("fromcontenttype", None) | ||
frompk = kwargs.pop("frompk", None) | ||
tocontenttype = kwargs.pop("tocontenttype", None) | ||
listonly = kwargs.pop("listonly", False) | ||
relreverse = kwargs.pop("reverse", False) | ||
|
||
super().__init__(*args, **kwargs) | ||
|
||
subj, obj = "subj", "obj" | ||
if relreverse: | ||
subj, obj = "obj", "subj" | ||
|
||
if fromcontenttype: | ||
self.fields[subj].label = fromcontenttype.name + " " + subj | ||
if fromcontenttype and frompk: | ||
self.fields[subj].disabled = True | ||
self.fields[subj].initial = fromcontenttype.model_class().objects.get(pk=frompk) | ||
self.fields[subj].widget = self.fields[subj].hidden_widget() | ||
if tocontenttype: | ||
self.fields[obj].widget.url = reverse("apis:generic:autocomplete", args=[tocontenttype]) | ||
self.fields[obj].label = tocontenttype.name + " " + obj | ||
|
||
content_type = ContentType.objects.get_for_model(self.Meta.model) | ||
url = reverse("apis:relationform", args=[content_type, fromcontenttype, frompk, tocontenttype]) | ||
params = {"reverse": relreverse, "listonly": listonly} | ||
self.helper.form_action = url + "?" + urllib.parse.urlencode(params) | ||
if listonly: | ||
self.helper.attrs = {"hx-post": self.helper.form_action, "hx-swap": "none"} |
10 changes: 10 additions & 0 deletions
10
apis_core/relations/management/commands/converttemptriples.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from django.core.management.base import BaseCommand | ||
from apis_core.apis_relations.models import TempTriple | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Create relations based on all existing TempTriples" | ||
|
||
def handle(self, *args, **options): | ||
for tt in TempTriple.objects.all(): | ||
tt.save() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Generated by Django 4.1.13 on 2023-12-18 06:09 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
("apis_metainfo", "0006_delete_text"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="Relation", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"obj", | ||
models.ForeignKey( | ||
null=True, | ||
on_delete=django.db.models.deletion.SET_NULL, | ||
related_name="relations_as_obj", | ||
to="apis_metainfo.rootobject", | ||
), | ||
), | ||
( | ||
"subj", | ||
models.ForeignKey( | ||
null=True, | ||
on_delete=django.db.models.deletion.SET_NULL, | ||
related_name="relations_as_subj", | ||
to="apis_metainfo.rootobject", | ||
), | ||
), | ||
], | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
from apis_core.apis_metainfo.models import RootObject | ||
from django.db import models | ||
from django.db.models.base import ModelBase | ||
from django.core.exceptions import ValidationError | ||
from model_utils.managers import InheritanceManager | ||
from apis_core.generic.abc import GenericModel | ||
|
||
|
||
# This ModelBase is simply there to check if the needed attributes | ||
# are set in the Relation child classes. | ||
class RelationModelBase(ModelBase): | ||
def __new__(metacls, name, bases, attrs): | ||
if name == "Relation": | ||
return super().__new__(metacls, name, bases, attrs) | ||
else: | ||
new_class = super().__new__(metacls, name, bases, attrs) | ||
if not hasattr(new_class, "subj_model"): | ||
raise ValueError( | ||
"%s inherits from Relation and must therefore specify subj_model" | ||
% name | ||
) | ||
if not hasattr(new_class, "obj_model"): | ||
raise ValueError( | ||
"%s inherits from Relation and must therefore specify obj_model" | ||
% name | ||
) | ||
|
||
return new_class | ||
|
||
|
||
class Relation(models.Model, GenericModel, metaclass=RelationModelBase): | ||
subj = models.ForeignKey( | ||
RootObject, | ||
on_delete=models.SET_NULL, | ||
null=True, | ||
related_name="relations_as_subj", | ||
) | ||
obj = models.ForeignKey( | ||
RootObject, | ||
on_delete=models.SET_NULL, | ||
null=True, | ||
related_name="relations_as_obj", | ||
) | ||
_reverse_name = None | ||
|
||
objects = InheritanceManager() | ||
|
||
def save(self, *args, **kwargs): | ||
if self.subj: | ||
subj = RootObject.objects_inheritance.get_subclass(id=self.subj.id) | ||
if not type(subj) in self.subj_list(): | ||
raise ValidationError( | ||
f"{self.subj} is not of any type in {self.subj_model}" | ||
) | ||
if self.obj: | ||
obj = RootObject.objects_inheritance.get_subclass(id=self.obj.id) | ||
if not type(obj) in self.obj_list(): | ||
raise ValidationError( | ||
f"{self.obj} is not of any type in {self.obj_model}" | ||
) | ||
super().save(*args, **kwargs) | ||
|
||
@property | ||
def subj_to_obj_text(self): | ||
return f"{self.subj_inheritance} {self.name()} {self.obj_inheritance}" | ||
|
||
@property | ||
def obj_to_subj_text(self): | ||
return f"{self.subj_inheritance} {self.reverse_name()} {self.obj_inheritance}" | ||
|
||
@property | ||
def subj_inheritance(self): | ||
return RootObject.objects_inheritance.get_subclass(pk=self.subj.id) | ||
|
||
@property | ||
def obj_inheritance(self): | ||
return RootObject.objects_inheritance.get_subclass(pk=self.obj.id) | ||
|
||
def __str__(self): | ||
return self.subj_to_obj_text | ||
|
||
@classmethod | ||
def is_subj(cls, something): | ||
return something in cls.subj_list() | ||
|
||
@classmethod | ||
def is_obj(cls, something): | ||
return something in cls.obj_list() | ||
|
||
@classmethod | ||
def subj_list(cls): | ||
return cls.subj_model if isinstance(cls.subj_model, list) else [cls.subj_model] | ||
|
||
@classmethod | ||
def obj_list(cls): | ||
return cls.obj_model if isinstance(cls.obj_model, list) else [cls.obj_model] | ||
|
||
def clean(self): | ||
if self.subj: | ||
subj = RootObject.objects_inheritance.get_subclass(id=self.subj.id) | ||
if not type(subj) in self.subj_list(): | ||
raise ValidationError( | ||
f"{self.subj} is not of any type in {self.subj_model}" | ||
) | ||
if self.obj: | ||
obj = RootObject.objects_inheritance.get_subclass(id=self.obj.id) | ||
if not type(obj) in self.obj_list(): | ||
raise ValidationError( | ||
f"{self.obj} is not of any type in {self.obj_model}" | ||
) | ||
|
||
@classmethod | ||
def name(cls): | ||
return cls._meta.verbose_name | ||
|
||
@classmethod | ||
def reverse_name(cls): | ||
return cls._reverse_name or cls._meta.verbose_name + " reverse" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from django.db.models.signals import post_save | ||
from django.dispatch import receiver | ||
from apis_core.apis_relations.models import TempTriple | ||
from django.apps import apps | ||
|
||
|
||
def find_relationtype(tt: TempTriple): | ||
obj_model = type(tt.obj) | ||
subj_model = type(tt.subj) | ||
name = tt.prop.name_forward | ||
reverse = tt.prop.name_reverse | ||
for model in apps.get_models(): | ||
if ( | ||
getattr(model, "obj_model", None) == obj_model | ||
and getattr(model, "subj_model", None) == subj_model | ||
and getattr(model, "temptriple_name", None) == name | ||
and getattr(model, "temptriple_name_reverse", None) == reverse | ||
): | ||
print(f"TempTriple {tt.pk} matches with {model}") | ||
return model | ||
print("Found none.") | ||
return None | ||
|
||
|
||
# this function is a helper function that adds a relation for every legacy temptriple that is created or updated | ||
@receiver(post_save, sender=TempTriple) | ||
def addrelation(sender, instance, created, **kwargs): | ||
model = find_relationtype(instance) | ||
if model is not None: | ||
rel, created = model.objects.get_or_create( | ||
subj=instance.subj, obj=instance.obj, metadata={"temptriple": instance.pk} | ||
) | ||
for field in getattr(model, "temptriple_field_list", []): | ||
setattr(rel, field, getattr(instance, field, None)) | ||
for field, newfield in getattr(model, "temptriple_field_mapping", {}).items(): | ||
setattr(rel, newfield, getattr(instance, field, None)) | ||
rel.save() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import django_tables2 as tables | ||
from django.template.loader import get_template | ||
from apis_core.generic.tables import GenericTable | ||
|
||
|
||
class GenericRelationsTable(GenericTable): | ||
prop = tables.Column(empty_values=()) | ||
related_instance = tables.Column(empty_values=()) | ||
|
||
class Meta(GenericTable.Meta): | ||
exclude = ["desc"] | ||
|
||
def __init__(self, data, instance, *args, **kwargs): | ||
self.instance = instance | ||
return super().__init__(data, *args, **kwargs) | ||
|
||
def render_prop(self, record): | ||
if record.subj_inheritance == self.instance: | ||
return record.name | ||
return record.reverse_name | ||
|
||
def render_related_instance(self, record): | ||
template = get_template("relations/columns/link_to_absolute_url.html") | ||
if record.subj_inheritance == self.instance: | ||
return template.render({"record": record.obj_inheritance}) | ||
return template.render({"record": record.subj_inheritance}) |
9 changes: 9 additions & 0 deletions
9
apis_core/relations/templates/apis_core/relations/relation_form.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{% extends "generic/generic_form.html" %} | ||
|
||
{% block title %} | ||
{{ object }} | ||
{% endblock %} | ||
|
||
{% block content %} | ||
{{ block.super }} | ||
{% endblock %} |
1 change: 1 addition & 0 deletions
1
apis_core/relations/templates/relations/columns/link_to_absolute_url.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<a href="{{ record.get_absolute_url }}">{{ record }}</a> |
8 changes: 8 additions & 0 deletions
8
apis_core/relations/templates/relations/partials/relation_table.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{% load relations %} | ||
{% load django_tables2 %} | ||
|
||
<div id="table_{{object.pk}}_{{tocontenttype.name}}" hx-swap-oob="true"> | ||
{% relations_table instance=object to_content_type=tocontenttype as table %} | ||
{% render_table table %} | ||
{% relations_links target_content_type=tocontenttype %} | ||
</div> |
27 changes: 27 additions & 0 deletions
27
apis_core/relations/templates/relations/partials/relations_links.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{% for relation_content_type in relation_types %} | ||
{% url "apis_core:relationform" relation_content_type source_content_type object.id target_content_type as hrefurl %} | ||
{% with relation_content_type.model|add:"-"|add:target_content_type.model as relation_id %} | ||
|
||
<a href="{{ hrefurl }}" data-toggle="collapse" data-target="#collapse-{{ relation_id }}" hx-get="{{ hrefurl }}?listonly=True" hx-target="#collapse-{{ relation_id }}" hx-select="form"> | ||
<button type="button" class="btn btn-sm"> | ||
{{ relation_content_type.model_class.name }} | ||
</button> | ||
</a> | ||
|
||
{% if relation_content_type.model_class.name != relation_content_type.model_class.reverse_name %} | ||
|
||
<a href="{{ hrefurl }}?reverse" data-toggle="collapse" data-target="#collapse-{{ relation_id }}-reverse" hx-get="{{ hrefurl }}?listonly=True&reverse=True" hx-target="#collapse-{{ relation_id }}-reverse" hx-select="form"> | ||
<button type="button" class="btn btn-sm"> | ||
{{ relation_content_type.model_class.reverse_name }} | ||
</button> | ||
</a> | ||
|
||
{% endif %} | ||
{% endwith %} | ||
{% endfor %} | ||
{% for relation_content_type in relation_types %} | ||
{% with relation_content_type.model|add:"-"|add:target_content_type.model as relation_id %} | ||
<div class="collapse" id="collapse-{{ relation_id }}"></div> | ||
<div class="collapse" id="collapse-{{ relation_id }}-reverse"></div> | ||
{% endwith %} | ||
{% endfor %} |
15 changes: 15 additions & 0 deletions
15
apis_core/relations/templates/relations/partials/relations_multiple_cards.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{% load django_tables2 %} | ||
{% load relations %} | ||
{% instance_can_be_related_to instance=object as contenttypes %} | ||
{% for contenttype in contenttypes %} | ||
<div class="card card-default mb-2"> | ||
<div class="card-header" role="tab" id="heading{{ contenttype.model }}"> | ||
<div class="card-title"> | ||
<a data-toggle="collapse" href="#collapse{{ contenttype.model }}">{{ contenttype.name }}</a> | ||
</div> | ||
</div> | ||
<div id="collapse{{ contenttype.model }}" class="card-collapse collapse"> | ||
<div id="tab_{{ contenttype.model }}" class="card-body">{% include "relations/partials/relation_table.html" with object=object tocontenttype=contenttype %}</div> | ||
</div> | ||
</div> | ||
{% endfor %} |
Oops, something went wrong.