Skip to content

Commit

Permalink
dynamic network visualisation
Browse files Browse the repository at this point in the history
  • Loading branch information
csae8092 authored Nov 26, 2024
2 parents 0285a5b + d585570 commit a8526c3
Show file tree
Hide file tree
Showing 36 changed files with 2,548 additions and 1,348 deletions.
6 changes: 5 additions & 1 deletion apis_core/apis_entities/autocomplete3.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ def get(self, request, *args, **kwargs):
Q(**{x + search_type: q})
for x in settings.APIS_ENTITIES[ac_type.title()]["search"]
]
res = ent_model.objects.filter(reduce(operator.or_, arg_list)).order_by('id').distinct()
res = (
ent_model.objects.filter(reduce(operator.or_, arg_list))
.order_by("id")
.distinct()
)
if q3:
f_dict2 = {}
for fd in q3:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 5.1.1 on 2024-11-23 07:42

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
(
"apis_entities",
"0003_alter_event_options_alter_institution_options_and_more",
),
]

operations = [
migrations.AlterModelOptions(
name="event",
options={
"ordering": ["-id"],
"verbose_name": "Ereignis",
"verbose_name_plural": "Ereignisse",
},
),
migrations.AlterModelOptions(
name="institution",
options={
"ordering": ["-id"],
"verbose_name": "Institution",
"verbose_name_plural": "Institutionen",
},
),
migrations.AlterModelOptions(
name="person",
options={
"ordering": ["-id"],
"verbose_name": "Person",
"verbose_name_plural": "Personen",
},
),
migrations.AlterModelOptions(
name="place",
options={
"ordering": ["-id"],
"verbose_name": "Ort",
"verbose_name_plural": "Orte",
},
),
migrations.AlterModelOptions(
name="work",
options={
"ordering": ["-id"],
"verbose_name": "Werk",
"verbose_name_plural": "Werke",
},
),
]
2 changes: 1 addition & 1 deletion apis_core/apis_metainfo/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def beacon(request, domain="d-nb.info/gnd"):
df["entity_id"] = df.apply(
lambda row: f'https://pmb.acdh.oeaw.ac.at/entity/{row["entity_id"]}/', axis=1
)
beacon_lines = df[['uri', 'entity__name', 'entity_id']].agg('|'.join, axis=1)
beacon_lines = df[["uri", "entity__name", "entity_id"]].agg("|".join, axis=1)
beacon_str = result + "\n".join(beacon_lines)
return HttpResponse(beacon_str, content_type="text/plain; charset=utf-8")

Expand Down
1 change: 1 addition & 0 deletions crontab
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
1 7 * * * root cd /opt/app && /usr/local/bin/python3 manage.py fetch_images >> /var/log/cron.log 2>&1
30 7 * * * root cd /opt/app && /usr/local/bin/python3 manage.py find_duplicted_persons >> /var/log/cron.log 2>&1
50 7 * * * root cd /opt/app && /usr/local/bin/python3 manage.py find_duplicted_places >> /var/log/cron.log 2>&1
5 23 * * * root cd /opt/app && /usr/local/bin/python3 manage.py edges >> /var/log/cron.log 2>&1
#
22 changes: 21 additions & 1 deletion dumper/templates/dumper/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ <h2 class="display-3">Personen der Moderne Basis </h2>
<p class="text-center">
<a class="icon-link" href="{% url 'apis:apis_entities:person_list_view' %}">
<i class="bi bi-people apis-person big-icons"></i>
<span class="visually-hidden">Personen</span>
</a>
</p>
<h2>{{ person_count|intcomma }} Personen</h2>
Expand All @@ -35,6 +36,7 @@ <h2>{{ person_count|intcomma }} Personen</h2>
<p class="text-center">
<a class="icon-link" href="{% url 'apis:apis_entities:place_list_view' %}">
<i class="bi bi-map apis-place big-icons"></i>
<span class="visually-hidden">Orte</span>
</a>
</p>
<h2>{{ place_count|intcomma }} Orte</h2>
Expand All @@ -46,6 +48,7 @@ <h2>{{ place_count|intcomma }} Orte</h2>
<p class="text-center">
<a class="icon-link" href="{% url 'apis:apis_entities:work_list_view' %}">
<i class="bi bi-book apis-work big-icons"></i>
<span class="visually-hidden">Werke</span>
</a>
</p>
<h2>{{ work_count|intcomma }} Werke</h2>
Expand All @@ -55,12 +58,27 @@ <h2>{{ work_count|intcomma }} Werke</h2>
</p>
</div>
</div>

<div class="row text-center pb-4">
<div class="col-lg-2"></div>
<div class="col-lg-8">
<p class="text-center">
<a class="icon-link" href="{% url 'network:edges_browse' %}">
<i class="bi bi-share big-icons"></i>
<span class="visually-hidden">Relationen</span>
</a>
</p>
<h2>{{ edges_count|intcomma }} Relationen</h2>
<p>Relationen zwischen Personen, Werken, Ereignissen, Institutionen und Orten.
</p>
</div>
</div>
<div class="col-lg-2"></div>
<div class="row">
<div class="col-lg-4">
<p class="text-center">
<a class="icon-link" href="{% url 'apis:apis_entities:event_list_view' %}">
<i class="bi bi-calendar3 apis-event big-icons"></i>
<span class="visually-hidden">Ereignisse</span>
</a>
</p>
<h2>{{ event_count|intcomma }} Ereignisse</h2>
Expand All @@ -72,6 +90,7 @@ <h2>{{ event_count|intcomma }} Ereignisse</h2>
<p class="text-center">
<a class="icon-link" href="{% url 'apis:apis_entities:institution_list_view' %}">
<i class="bi bi-building-gear apis-institution big-icons"></i>
<span class="visually-hidden">Institutionen</span>
</a>
</p>
<h2>{{ institution_count|intcomma }} Institutionen</h2>
Expand All @@ -83,6 +102,7 @@ <h2>{{ institution_count|intcomma }} Institutionen</h2>
<p class="text-center">
<a class="icon-link" href="{% url 'apis:apis_metainfo:uri_browse' %}">
<i class="bi bi-link-45deg apis-uri big-icons"></i>
<span class="visually-hidden">Uris</span>
</a>
</p>
<h2>{{ uri_count|intcomma }} URIs</h2>
Expand Down
47 changes: 17 additions & 30 deletions dumper/templates/dumper/network.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,26 @@
{% endblock scripts %}

{% block content %}
<div class="container pt-4">
<h1 class="display-3 text-center">Netzwerk aller Verbindungen</h1>
<div class="d-flex justify-content-center">
<div class="alert alert-warning d-flex align-items-center" role="alert">
<div>
Hierbei handelt es sich nur um ein Proof of Concept
</div>
<div class="container-fluid p3-4">
<h1 class="display-3 text-center">Netzwerk aller Verbindungen</h1>
<div class="d-flex justify-content-center">
<div id="alert" class="alert alert-warning d-flex align-items-center">
<div>
Hierbei handelt es sich nur um ein Proof of Concept
</div>
</div>

<div class="d-flex justify-content-center" id="spinner">
<div class="spinner-border" role="status">
<span class="visually-hidden">Lade die Netzwerkdaten...</span>
</div>
</div>
<div class="row">
<div class="col-2" >
<div class="p2" style="visibility: hidden;" id="legend">
<ul>
{% for x in model_list %}
<li>
<i class="{{ x.icon }}"></i> {{ x.name }}
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="col-10">
<div id="app"></div>
</div>

</div>
<div class="d-flex justify-content-center" id="spinner">
<div class="spinner-border" role="status">
<span class="visually-hidden">Lade die Netzwerkdaten...</span>
</div>


</div>
<div class="p2 text-center" style="visibility: hidden;" id="legend">
{% for x in model_list %}
<span class="p2"><i class="{{ x.icon }}"></i> {{ x.name }}</span>
{% endfor %}
</div>
<div id="app" class="pt-2 pe-2 ps-2 pb-2" style="height: 750px;"></div>
</div>
{% endblock %}
9 changes: 7 additions & 2 deletions dumper/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import requests
from typing import Any


from django.apps import apps
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.views.generic.base import TemplateView

from apis_core.apis_entities.models import Event, Institution, Person, Place, Work
from django.conf import settings
from apis_core.apis_metainfo.models import Uri
import requests

from network.models import Edge


from .forms import form_user_login
Expand Down Expand Up @@ -80,6 +84,7 @@ def get_context_data(self, *args, **kwargs):
context["work_count"] = Work.objects.all().count()
context["institution_count"] = Institution.objects.all().count()
context["uri_count"] = Uri.objects.all().count()
context["edges_count"] = Edge.objects.all().count()
return context


Expand Down
Empty file added network/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions network/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin
from network.models import Edge


admin.site.register(Edge)
6 changes: 6 additions & 0 deletions network/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class NetworkConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "network"
68 changes: 68 additions & 0 deletions network/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import django_filters
from django.core.exceptions import FieldDoesNotExist
from django.db.models import CharField, Q
import django_filters.widgets

from network.models import Edge


def safe_int_conversion(value):
try:
return int(value)
except ValueError:
pass


class EdgeListFilter(django_filters.FilterSet):

node = django_filters.CharFilter(
field_name="source_label",
method="nodes_icontains_filter",
label="Quell- oder Zielknoten",
help_text="Sucht im Label des Ziel, oder des Quellknotens",
)
node_id = django_filters.BaseInFilter(
field_name="source_id",
method="nodes_id_filter",
label="IDs eines oder mehrerer Quell- oder Zielknoten",
help_text="IDs eines oder mehrerer Quell- oder Zielknoten, z.B. '2121,10815'",
widget=django_filters.widgets.CSVWidget(),
)
edge_label = django_filters.AllValuesMultipleFilter()

class Meta:
model = Edge
fields = "__all__"

def nodes_icontains_filter(self, queryset, name, value):
return queryset.filter(
Q(source_label__icontains=value) | Q(target_label__icontains=value)
)

def nodes_id_filter(self, queryset, name, value):
sane_values = [safe_int_conversion(x) for x in value]
return queryset.filter(
Q(source_id__in=sane_values) | Q(target_id__in=sane_values)
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, filter_obj in self.filters.items():
try:
model_field = self.Meta.model._meta.get_field(field_name)
self.filters[field_name].label = model_field.verbose_name
self.filters[field_name].help_text = model_field.help_text
except FieldDoesNotExist:
continue
if isinstance(model_field, CharField) and not field_name == "edge_label":
if (
model_field.choices
): # Keep the default filter logic for choice fields
continue
else:
self.filters[field_name] = django_filters.CharFilter(
field_name=field_name,
lookup_expr="icontains",
help_text=model_field.help_text,
label=model_field.verbose_name,
)
37 changes: 37 additions & 0 deletions network/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from crispy_forms.helper import FormHelper

from crispy_forms.layout import Layout
from crispy_forms.bootstrap import AccordionGroup
from crispy_bootstrap5.bootstrap5 import BS5Accordion


class EdgeFilterFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(EdgeFilterFormHelper, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.form_class = "genericFilterForm"
self.form_method = "GET"
self.form_tag = False
self.layout = Layout(
"node",
"node_id",
BS5Accordion(
AccordionGroup(
"Quellknoten",
"source_label",
"source_kind",
),
AccordionGroup(
"Zielknoten",
"target_label",
"target_kind",
),
AccordionGroup(
"Beziehung",
"edge_label",
"edge_kind",
"start_date",
"end_date",
),
),
)
Loading

0 comments on commit a8526c3

Please sign in to comment.