forked from CiviWiki/OpenCiviWiki
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(threads): threads.models, threads.graphs
Introduces a CiviLink model to represent the graph's directed edges a la CiviWiki#1438 (comment) added a basic graph template at threads/templates/graph.html added library cytoscapejs for graph viz added networkx library for graph (python) graph visual is still kinda wonky, need to fix it but the basic gist of it is there kinda sorta a start at closing CiviWiki#149
- Loading branch information
Showing
14 changed files
with
510 additions
and
46 deletions.
There are no files selected for viewing
File renamed without changes.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,62 @@ | ||
# data_loader.py | ||
from django.core.management.base import BaseCommand | ||
from threads.models import Civi, CiviLink, Thread # Adjust import to your app's name | ||
|
||
from django.contrib.auth import get_user_model | ||
|
||
User = get_user_model() | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Load dummy data for Civis and CiviLinks" | ||
|
||
def handle(self, *args, **kwargs): | ||
# Create a thread | ||
thread, _ = Thread.objects.get_or_create( | ||
title="Net Neutrality", | ||
) | ||
user = User.objects.first() | ||
# Create dummy Civis | ||
civi1 = Civi.objects.create( | ||
title="Civi 1: Importance of Net Neutrality", | ||
body="Net neutrality ensures that all users have equal access to information and services online.", | ||
author=user, | ||
votes_pos=10, | ||
votes_neg=2, | ||
thread=thread, | ||
) | ||
|
||
civi2 = Civi.objects.create( | ||
title="Civi 2: Risks of Removing Net Neutrality", | ||
body="Without net neutrality, ISPs could prioritize their own content or the content of those who pay for faster access.", | ||
author=user, | ||
votes_pos=15, | ||
votes_neg=1, | ||
thread=thread, | ||
) | ||
|
||
civi3 = Civi.objects.create( | ||
title="Civi 3: Public Opinion on Net Neutrality", | ||
body="A significant portion of the public supports net neutrality regulations to protect free internet access.", | ||
author=user, | ||
votes_pos=20, | ||
votes_neg=3, | ||
thread=thread, | ||
) | ||
|
||
# Create dummy CiviLinks | ||
CiviLink.objects.create( | ||
from_civi=civi1, to_civi=civi2, relation_type="response" | ||
) | ||
|
||
CiviLink.objects.create( | ||
from_civi=civi2, to_civi=civi1, relation_type="rebuttal" | ||
) | ||
|
||
CiviLink.objects.create(from_civi=civi1, to_civi=civi3, relation_type="support") | ||
|
||
CiviLink.objects.create( | ||
from_civi=civi3, to_civi=civi2, relation_type="challenge" | ||
) | ||
|
||
self.stdout.write(self.style.SUCCESS("Successfully loaded dummy data")) |
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
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,87 @@ | ||
from rest_framework.decorators import api_view | ||
from rest_framework.response import Response | ||
from .graphs import ( | ||
load_graph_from_db, | ||
most_caused_problems, | ||
most_effective_solution, | ||
shortest_path_problem_to_solution, | ||
) | ||
|
||
|
||
@api_view(["GET"]) | ||
def get_most_caused_problem(request): | ||
with_score = request.GET.get("with_score", False) | ||
G = load_graph_from_db(with_score) | ||
problem = most_caused_problems(G) | ||
if problem: | ||
if with_score: | ||
return Response( | ||
{ | ||
"problem": G.nodes[problem]["label"], | ||
"score": G.nodes[problem]["score"], | ||
} | ||
) | ||
else: | ||
return Response({"problem": G.nodes[problem]["label"]}) | ||
return Response({"error": "No problem found"}, status=404) | ||
|
||
|
||
@api_view(["GET"]) | ||
def get_most_effective_solution(request): | ||
with_score = request.GET.get("with_score", False) | ||
G = load_graph_from_db(with_score) | ||
solution = most_effective_solution(G) | ||
if solution: | ||
if with_score: | ||
return Response( | ||
{ | ||
"solution": G.nodes[solution]["label"], | ||
"score": G.nodes[solution]["score"], | ||
} | ||
) | ||
else: | ||
return Response({"solution": G.nodes[solution]["label"]}) | ||
return Response({"error": "No solution found"}, status=404) | ||
|
||
|
||
@api_view(["GET"]) | ||
def get_shortest_path(request, problem_id, solution_id): | ||
with_score = request.GET.get("with_score", False) | ||
G = load_graph_from_db(with_score) | ||
path = shortest_path_problem_to_solution(G, int(problem_id), int(solution_id)) | ||
if path: | ||
path_labels = [G.nodes[node]["label"] for node in path] | ||
return Response({"path_labels": path_labels, "path":path}) | ||
return Response({"error": "No path found"}, status=404) | ||
|
||
|
||
|
||
|
||
@api_view(['GET']) | ||
def get_graph_data(request): | ||
G = load_graph_from_db() # Load the graph from your DB or other data source | ||
nodes = [] | ||
edges = [] | ||
|
||
# Format nodes and edges to match Cytoscape format | ||
for node, attr in G.nodes(data=True): | ||
nodes.append({ | ||
'data': { | ||
'id': node, | ||
'label': attr.get('label', node), | ||
'type': attr.get('type'), | ||
'score': attr.get('score', 0), | ||
} | ||
}) | ||
|
||
for source, target, attr in G.edges(data=True): | ||
edges.append({ | ||
'data': { | ||
'id': f'{source}-{target}', | ||
'source': source, | ||
'target': target, | ||
'label': attr.get('label', 'related') | ||
} | ||
}) | ||
|
||
return Response({'nodes': nodes, 'edges': edges}) |
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,42 @@ | ||
import networkx as nx | ||
from .models import Civi, CiviLink | ||
|
||
|
||
def load_graph_from_db(with_score:bool=False): | ||
# Create a directed graph | ||
G = nx.DiGraph() | ||
|
||
# Add all Civis as nodes | ||
for civi in Civi.objects.all(): | ||
node_args, node_kwargs = civi.__node__(with_score=with_score) | ||
G.add_node(*node_args, **node_kwargs) | ||
|
||
# Add CiviLinks as edges | ||
for link in CiviLink.objects.all(): | ||
edge_args, edge_kwargs = link.__edge__() | ||
G.add_edge(*edge_args, **edge_kwargs) | ||
|
||
return G | ||
|
||
|
||
def most_caused_problems(G, with_score:bool=False): | ||
problem_nodes = [n for n, attr in G.nodes(data=True) if attr["type"] == "Problem"] | ||
if with_score: | ||
return max(problem_nodes, key=lambda n: (G.in_degree(n), G.nodes[n]['score']), default=None) | ||
return max(problem_nodes, key=lambda n: G.in_degree(n), default=None) | ||
|
||
|
||
def most_effective_solution(G, with_score:bool=False): | ||
solution_nodes = [n for n, attr in G.nodes(data=True) if attr["type"] == "Solution"] | ||
if with_score: | ||
return max(solution_nodes, key=lambda n: (G.out_degree(n), G.nodes[n]['score']), default=None) | ||
return max(solution_nodes, key=lambda n: G.out_degree(n), default=None) | ||
|
||
|
||
def shortest_path_problem_to_solution(G, problem_id, solution_id, with_score:bool=False): | ||
try: | ||
if with_score: | ||
return nx.shortest_path(G, source=problem_id, target=solution_id, weight='weight') | ||
return nx.shortest_path(G, source=problem_id, target=solution_id) | ||
except nx.NetworkXNoPath: | ||
return None |
Oops, something went wrong.