mirror of
https://github.com/Findus23/RPGnotes.git
synced 2024-09-19 15:43:45 +02:00
store linked pages in DB and show in graph
This commit is contained in:
parent
19aa3e0722
commit
1d7d713243
12 changed files with 177 additions and 15 deletions
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-25 18:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("characters", "0016_remove_character_nickname_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="character",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalcharacter",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
]
|
|
@ -36,9 +36,10 @@ class Command(BaseCommand):
|
||||||
objects.extend(list(Note.objects.all()))
|
objects.extend(list(Note.objects.all()))
|
||||||
objects.extend(list(IngameDay.objects.all()))
|
objects.extend(list(IngameDay.objects.all()))
|
||||||
for object in objects:
|
for object in objects:
|
||||||
fresh_html = md_to_html(object.description_md, replacements=replacements)
|
fresh_html, linked_objects = md_to_html(object.description_md, replacements=replacements)
|
||||||
if object.description_html != fresh_html:
|
if object.description_html != fresh_html:
|
||||||
print_diff_call(object.description_html, fresh_html, str(object))
|
print_diff_call(object.description_html, fresh_html, str(object))
|
||||||
if store:
|
if store:
|
||||||
object.description_html = fresh_html
|
object.description_html = fresh_html
|
||||||
|
object.linked_objects = ",".join(linked_objects)
|
||||||
object.save()
|
object.save()
|
||||||
|
|
|
@ -7,11 +7,13 @@ from utils.markdown import md_to_html
|
||||||
class DescriptionModel(models.Model):
|
class DescriptionModel(models.Model):
|
||||||
description_md = models.TextField(_("Description"), blank=True)
|
description_md = models.TextField(_("Description"), blank=True)
|
||||||
description_html = models.TextField(_("Description (HTML)"), blank=True, editable=False)
|
description_html = models.TextField(_("Description (HTML)"), blank=True, editable=False)
|
||||||
|
linked_objects = models.TextField(max_length=1000, blank=True, default="")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.description_html = md_to_html(self.description_md)
|
self.description_html, linked_objects = md_to_html(self.description_md)
|
||||||
|
self.linked_objects = ",".join(linked_objects)
|
||||||
|
|
||||||
super(DescriptionModel, self).save(*args, **kwargs)
|
super(DescriptionModel, self).save(*args, **kwargs)
|
||||||
|
|
|
@ -28,20 +28,20 @@ class ColorsTests(SimpleTestCase):
|
||||||
class MarkdownTests(SimpleTestCase):
|
class MarkdownTests(SimpleTestCase):
|
||||||
def test_basic_markdown(self):
|
def test_basic_markdown(self):
|
||||||
self.assertHTMLEqual(
|
self.assertHTMLEqual(
|
||||||
md_to_html("**test** *it*", replacements={}),
|
md_to_html("**test** *it*", replacements={})[0],
|
||||||
"<p><strong>test</strong> <em>it</em></p>"
|
"<p><strong>test</strong> <em>it</em></p>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_nb_md(self):
|
def test_nb_md(self):
|
||||||
self.assertHTMLEqual(
|
self.assertHTMLEqual(
|
||||||
md_to_html("This\nis\nTest", replacements={}),
|
md_to_html("This\nis\nTest", replacements={})[0],
|
||||||
"<p>This<br>is<br>Test</p>"
|
"<p>This<br>is<br>Test</p>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_bleach(self):
|
def test_bleach(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
md_to_html(
|
md_to_html(
|
||||||
"<script>console.log()</script> <a onclick='console.log()'>Hi</button>", replacements={}),
|
"<script>console.log()</script> <a onclick='console.log()'>Hi</button>", replacements={})[0],
|
||||||
"<script>console.log()</script>\n<p><a>Hi</button></a></p>"
|
"<script>console.log()</script>\n<p><a>Hi</button></a></p>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-25 18:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("days", "0006_alter_historicalsession_date_alter_session_date"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalingameday",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="ingameday",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
]
|
23
factions/migrations/0005_faction_linked_objects_and_more.py
Normal file
23
factions/migrations/0005_faction_linked_objects_and_more.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-25 18:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("factions", "0004_faction_aliases_historicalfaction_aliases"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="faction",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalfaction",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
]
|
|
@ -37,6 +37,12 @@ class Graph:
|
||||||
"target": target.graphkey
|
"target": target.graphkey
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def add_edge_str(self, source: str, target: str):
|
||||||
|
self.edges.append({
|
||||||
|
"source": source,
|
||||||
|
"target": target
|
||||||
|
})
|
||||||
|
|
||||||
def prune(self) -> None:
|
def prune(self) -> None:
|
||||||
connected_nodes = set()
|
connected_nodes = set()
|
||||||
for e in self.edges:
|
for e in self.edges:
|
||||||
|
@ -62,14 +68,22 @@ class GraphView(TemplateView):
|
||||||
template_name = "graph/graph.jinja"
|
template_name = "graph/graph.jinja"
|
||||||
|
|
||||||
|
|
||||||
|
def get_description_links(el: GraphModelEl, g: Graph):
|
||||||
|
if el.linked_objects:
|
||||||
|
for lo in el.linked_objects.split(","):
|
||||||
|
g.add_edge_str(el.graphkey, lo)
|
||||||
|
|
||||||
|
|
||||||
def get_graph(request: HttpRequest) -> HttpResponse:
|
def get_graph(request: HttpRequest) -> HttpResponse:
|
||||||
g = Graph()
|
g = Graph()
|
||||||
for loc in list(Location.objects.all()) + list(Note.objects.all()):
|
for loc in list(Location.objects.all()) + list(Note.objects.all()):
|
||||||
g.add_node(loc)
|
g.add_node(loc)
|
||||||
if loc.parent:
|
if loc.parent:
|
||||||
g.add_edge(loc, loc.parent)
|
g.add_edge(loc, loc.parent)
|
||||||
|
get_description_links(loc, g)
|
||||||
for faction in Faction.objects.all():
|
for faction in Faction.objects.all():
|
||||||
g.add_node(faction, faction.name)
|
g.add_node(faction, faction.name)
|
||||||
|
get_description_links(faction, g)
|
||||||
for user in TenantUser.objects \
|
for user in TenantUser.objects \
|
||||||
.filter(tenants=connection.get_tenant()) \
|
.filter(tenants=connection.get_tenant()) \
|
||||||
.exclude(pk__in=[1, 2]):
|
.exclude(pk__in=[1, 2]):
|
||||||
|
@ -82,6 +96,7 @@ def get_graph(request: HttpRequest) -> HttpResponse:
|
||||||
g.add_edge(char, char.faction)
|
g.add_edge(char, char.faction)
|
||||||
if char.player:
|
if char.player:
|
||||||
g.add_edge(char, char.player)
|
g.add_edge(char, char.player)
|
||||||
|
get_description_links(char, g)
|
||||||
|
|
||||||
for loottype in LootType.objects.all():
|
for loottype in LootType.objects.all():
|
||||||
g.add_node(loottype)
|
g.add_node(loottype)
|
||||||
|
@ -94,6 +109,8 @@ def get_graph(request: HttpRequest) -> HttpResponse:
|
||||||
g.add_edge(loot, loot.owner)
|
g.add_edge(loot, loot.owner)
|
||||||
if loot.type:
|
if loot.type:
|
||||||
g.add_edge(loot, loot.type)
|
g.add_edge(loot, loot.type)
|
||||||
|
get_description_links(loot, g)
|
||||||
|
|
||||||
g.prune()
|
g.prune()
|
||||||
|
|
||||||
return JsonResponse(g.export())
|
return JsonResponse(g.export())
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-25 18:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("locations", "0010_historicallocation_aliases_location_aliases"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicallocation",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="location",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-25 18:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("loot", "0015_loottype_slug"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalloot",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="loot",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-25 18:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("notes", "0005_historicalnote_aliases_note_aliases"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalnote",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="note",
|
||||||
|
name="linked_objects",
|
||||||
|
field=models.TextField(blank=True, default="", max_length=1000),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
|
from typing import Tuple, Set
|
||||||
|
|
||||||
import bleach
|
import bleach
|
||||||
import markdown
|
import markdown
|
||||||
|
@ -8,8 +9,8 @@ from bleach_allowlist import markdown_tags, markdown_attrs
|
||||||
custom_allowed_tags = ["del", "ins"]
|
custom_allowed_tags = ["del", "ins"]
|
||||||
|
|
||||||
|
|
||||||
def md_to_html(md: str, replacements=None) -> str:
|
def md_to_html(md: str, replacements=None) -> Tuple[str, Set[str]]:
|
||||||
md = autolink(md, replacements=replacements)
|
md, linked_objects = autolink(md, replacements=replacements)
|
||||||
html = markdown.markdown(
|
html = markdown.markdown(
|
||||||
md,
|
md,
|
||||||
output_format="html",
|
output_format="html",
|
||||||
|
@ -22,25 +23,28 @@ def md_to_html(md: str, replacements=None) -> str:
|
||||||
tags=markdown_tags + custom_allowed_tags,
|
tags=markdown_tags + custom_allowed_tags,
|
||||||
attributes=markdown_attrs
|
attributes=markdown_attrs
|
||||||
)
|
)
|
||||||
return html
|
return html, linked_objects
|
||||||
|
|
||||||
|
|
||||||
def autolink(md: str, replacements=None) -> str:
|
def autolink(md: str, replacements=None) -> Tuple[str, Set[str]]:
|
||||||
if replacements is None:
|
if replacements is None:
|
||||||
from utils.urls import name2url
|
from utils.urls import name2url
|
||||||
replacements = name2url()
|
replacements = name2url()
|
||||||
links = {}
|
links = {}
|
||||||
|
linked_objects = set()
|
||||||
i = 0
|
i = 0
|
||||||
for name, url in replacements.items():
|
for name, (url, obj) in replacements.items():
|
||||||
regex = r"\bWORD\b".replace("WORD", name)
|
regex = r"\bWORD\b".replace("WORD", name)
|
||||||
placeholder = f"SOME{i}LINK"
|
placeholder = f"SOME{i}LINK"
|
||||||
md = re.sub(regex, placeholder, md)
|
md, n_replacements = re.subn(regex, placeholder, md)
|
||||||
|
if n_replacements > 0:
|
||||||
|
linked_objects.add(obj.graphkey)
|
||||||
links[placeholder] = f"[{name}]({url})"
|
links[placeholder] = f"[{name}]({url})"
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
for placeholder, value in links.items():
|
for placeholder, value in links.items():
|
||||||
md = md.replace(placeholder, value)
|
md = md.replace(placeholder, value)
|
||||||
return md
|
return md, linked_objects
|
||||||
|
|
||||||
|
|
||||||
class HTMLFilter(HTMLParser):
|
class HTMLFilter(HTMLParser):
|
||||||
|
|
|
@ -9,17 +9,17 @@ from notes.models import Note
|
||||||
|
|
||||||
def name2url() -> Dict[str, str]:
|
def name2url() -> Dict[str, str]:
|
||||||
data = {}
|
data = {}
|
||||||
objects=[]
|
objects = []
|
||||||
objects.extend(Character.objects.all())
|
objects.extend(Character.objects.all())
|
||||||
objects.extend(Location.objects.all())
|
objects.extend(Location.objects.all())
|
||||||
objects.extend(Loot.objects.all())
|
objects.extend(Loot.objects.all())
|
||||||
objects.extend(Faction.objects.all())
|
objects.extend(Faction.objects.all())
|
||||||
objects.extend(Note.objects.all())
|
objects.extend(Note.objects.all())
|
||||||
for object in objects:
|
for object in objects:
|
||||||
data[object.name] = object.get_absolute_url()
|
data[object.name] = (object.get_absolute_url(), object)
|
||||||
if object.aliases:
|
if object.aliases:
|
||||||
for alias in object.aliases:
|
for alias in object.aliases:
|
||||||
data[alias] = object.get_absolute_url()
|
data[alias] = (object.get_absolute_url(), object)
|
||||||
|
|
||||||
# longer replacements first
|
# longer replacements first
|
||||||
data = {k: v for k, v in sorted(data.items(), key=lambda item: -len(item[0]))}
|
data = {k: v for k, v in sorted(data.items(), key=lambda item: -len(item[0]))}
|
||||||
|
|
Loading…
Reference in a new issue