Skip to content

Commit

Permalink
relations: add relations-ng to 0.17.3
Browse files Browse the repository at this point in the history
  • Loading branch information
gythaogg committed May 12, 2024
1 parent d0c081b commit 9cfa4ec
Show file tree
Hide file tree
Showing 20 changed files with 788 additions and 0 deletions.
Empty file added apis_core/relations/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions apis_core/relations/admin.py
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)
9 changes: 9 additions & 0 deletions apis_core/relations/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.apps import AppConfig


class ApisRelations2Config(AppConfig):
default_auto_field = "django.db.models.AutoField"
name = "apis_core.relations"

def ready(self):
from . import signals
84 changes: 84 additions & 0 deletions apis_core/relations/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from django.forms import ModelForm
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.contrib.contenttypes.models import ContentType


from crispy_forms.layout import Submit, Layout, Div, HTML
from crispy_forms.helper import FormHelper


class RelationForm(ModelForm):
def __init__(
self,
frominstance=None,
tocontenttype=None,
inverted=False,
embedded=True,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)

subj, obj = "subj", "obj"
if inverted:
subj = "obj"
obj = "subj"

if frominstance:
self.fields[subj].disabled = True
self.fields[subj].initial = frominstance
self.fields[subj].label = ContentType.objects.get_for_model(
frominstance
).name

if tocontenttype:
self.fields[obj].queryset = tocontenttype.model_class().objects.all()
self.fields[obj].label = tocontenttype.name

self.helper = FormHelper(self)

relcontenttype = ContentType.objects.get_for_model(self._meta.model)

args = [
relcontenttype.pk,
]
if frominstance:
args.append(ContentType.objects.get_for_model(frominstance).pk)
args.append(frominstance.pk)
if tocontenttype:
args.append(tocontenttype.pk)
hx_post = reverse("apis:relation", args=args)
if inverted:
hx_post = reverse("apis:relationinverted", args=args)

hx_post += "?partial"

if embedded:
self.helper.attrs = {
"hx-post": hx_post,
"hx-swap": "outerHTML",
}

# layout stuff:
div = Div(
Div("subj", css_class="col-md-6"),
Div("obj", css_class="col-md-6"),
css_class="row",
)
if inverted:
div = Div(
Div("obj", css_class="col-md-6"),
Div("subj", css_class="col-md-6"),
css_class="row",
)

# we have to explicetly add the rest of the fields
fields = {k: v for k, v in self.fields.items() if k not in ["obj", "subj"]}

self.helper.layout = Layout(
HTML(f"<h3>{self._meta.model.__name__}</h3>"),
div,
*fields,
)
self.helper.add_input(Submit("submit", "Submit", css_class="btn-primary"))
10 changes: 10 additions & 0 deletions apis_core/relations/management/commands/converttemptriples.py
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()
48 changes: 48 additions & 0 deletions apis_core/relations/migrations/0001_initial.py
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.
104 changes: 104 additions & 0 deletions apis_core/relations/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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


# 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, 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",
)

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):
if hasattr(self, "name"):
return f"{self.subj} {self.name} {self.obj}"
return f"{self.subj} relation to {self.obj}"

@property
def obj_to_subj_text(self):
if hasattr(self, "reverse_name"):
return f"{self.subj} {self.reverse_name} {self.obj}"
return f"{self.obj} relation to {self.subj}"

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}"
)
37 changes: 37 additions & 0 deletions apis_core/relations/signals.py
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()
5 changes: 5 additions & 0 deletions apis_core/relations/static/relations.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tr.htmx-swapping td {
opacity: 0;
transition: opacity 1s ease-out;
color: red;
}
23 changes: 23 additions & 0 deletions apis_core/relations/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import django_tables2 as tables

from django.contrib.contenttypes.models import ContentType

from .models import Relation


class RelationTable(tables.Table):

id = tables.TemplateColumn(
"<a href='{% url 'apis:relationupdate' record.id %}'>{{ record.id }}</a>"
)

description = tables.TemplateColumn("{{ record }}")
edit = tables.TemplateColumn(
"<a href='{% url 'apis:relationupdate' record.id %}'>Edit</a>"
)
delete = tables.TemplateColumn(template_name="tables/delete.html")

class Meta:
model = Relation
fields = ["id", "description", "edit"]
sequence = tuple(fields)
24 changes: 24 additions & 0 deletions apis_core/relations/templates/relations/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends basetemplate %}
{% load relations %}

{% block content %}

{% if model or object %}
<h1 class="text-center">{{ model.name }}{{ object }}</h1>
{% else %}
{% relations_links %}
{% endif %}

{% if table %}
{% load render_table from django_tables2 %}
{% render_table table %}
{% endif %}

{% if form %}
{{ form.errors }}
{{ form.non_field_errors }}
{% load crispy_forms_tags %}
{% crispy form %}
{% endif %}

{% endblock content %}
9 changes: 9 additions & 0 deletions apis_core/relations/templates/relations/partial.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% if table %}
{% load render_table from django_tables2 %}
{% render_table table %}
{% endif %}

{% if form %}
{% load crispy_forms_tags %}
{% crispy form %}
{% endif %}
5 changes: 5 additions & 0 deletions apis_core/relations/templates/tables/delete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<a href='{% url 'apis:relationdelete' record.id %}?next={{ request.GET.next|default:request.path }}'
hx-delete='{% url 'apis:relationdelete' record.id %}?status_only'
hx-target='closest tr'
hx-confirm='Are your sure you want to delete {{ record }}?'
hx-swap="outerHTML swap:1s">Delete</a>
Loading

0 comments on commit 9cfa4ec

Please sign in to comment.