@@ -11,300 +11,15 @@ NOTE: **This project is a work in progress. Again, this project is a work in pro
11
11
12
12
Currently, it provides numerous methods for retrieving nodes, and a few for retrieving edges within the graph. In progress are filters within the in order to limit the area of the graph to be searched, ability to easily export to NetworkX, and other improvements and utilities.
13
13
14
- ## Most Simple Example:
14
+ ## Demo
15
15
16
- ### models.py
16
+ [ Quickstart example ] ( https://django-postgresql-dag.readthedocs.io/en/latest/quickstart.html )
17
17
18
- from django.db import models
19
- from django_postgresql_dag.models import node_factory, edge_factory
20
-
21
- class EdgeSet(models.Model):
22
- name = models.CharField(max_length=100, unique=True)
23
-
24
- def __str__(self):
25
- return self.name
26
-
27
-
28
- class NodeSet(models.Model):
29
- name = models.CharField(max_length=100, unique=True)
30
-
31
- def __str__(self):
32
- return self.name
33
-
34
-
35
- class NetworkEdge(edge_factory("NetworkNode", concrete=False)):
36
- name = models.CharField(max_length=100, unique=True)
37
-
38
- edge_set = models.ForeignKey(
39
- EdgeSet,
40
- on_delete=models.CASCADE,
41
- null=True,
42
- blank=True,
43
- related_name="edge_set_edges",
44
- )
45
-
46
- def __str__(self):
47
- return self.name
48
-
49
- def save(self, *args, **kwargs):
50
- self.name = f"{self.parent.name} {self.child.name}"
51
- super().save(*args, **kwargs)
52
-
53
-
54
- class NetworkNode(node_factory(NetworkEdge)):
55
- name = models.CharField(max_length=100)
56
-
57
- node_set = models.ForeignKey(
58
- NodeSet,
59
- on_delete=models.CASCADE,
60
- null=True,
61
- blank=True,
62
- related_name="node_set_nodes",
63
- )
64
-
65
- def __str__(self):
66
- return self.name
67
-
68
- ### Add some Instances via the Shell (or in views, etc)
69
-
70
- ~/myapp$ python manage.py shell
71
- >>> from myapp.models import NetworkNode, NetworkEdge
72
-
73
- >>> root = NetworkNode.objects.create(name="root")
74
-
75
- >>> a1 = NetworkNode.objects.create(name="a1")
76
- >>> a2 = NetworkNode.objects.create(name="a2")
77
- >>> a3 = NetworkNode.objects.create(name="a3")
78
-
79
- >>> b1 = NetworkNode.objects.create(name="b1")
80
- >>> b2 = NetworkNode.objects.create(name="b2")
81
- >>> b3 = NetworkNode.objects.create(name="b3")
82
- >>> b4 = NetworkNode.objects.create(name="b4")
83
-
84
- >>> c1 = NetworkNode.objects.create(name="c1")
85
- >>> c2 = NetworkNode.objects.create(name="c2")
86
-
87
- >>> root.add_child(a1)
88
- >>> root.add_child(a2)
89
- >>> a3.add_parent(root) # You can add from either side of the relationship
90
-
91
- >>> b1.add_parent(a1)
92
- >>> a1.add_child(b2)
93
- >>> a2.add_child(b2)
94
- >>> a3.add_child(b3)
95
- >>> a3.add_child(b4)
96
-
97
- >>> b3.add_child(c2)
98
- >>> b3.add_child(c1)
99
- >>> b4.add_child(c1)
100
-
101
- ### Add Edges and Nodes to EdgeSet and NodeSet models (FK)
102
-
103
- >>> y = EdgeSet.objects.create()
104
- >>> y.save()
105
-
106
- >>> c1_ancestors = c1.ancestors_edges()
107
-
108
- >>> for ancestor in c1_ancestors:
109
- >>> ancestor.edge_set = y
110
- >>> ancestor.save()
111
-
112
- >>> x = NodeSet.objects.create()
113
- >>> x.save()
114
- >>> root.node_set = x
115
- >>> root.save()
116
- >>> a1.node_set = x
117
- >>> a1.save()
118
- >>> b1.node_set = x
119
- >>> b1.save()
120
- >>> b2.node_set = x
121
- >>> b2.save()
122
-
123
- ### Resulting Database Tables
124
-
125
- #### myapp_networknode
126
-
127
- id | name
128
- ----+------
129
- 1 | root
130
- 2 | a1
131
- 3 | a2
132
- 4 | a3
133
- 5 | b1
134
- 6 | b2
135
- 7 | b3
136
- 8 | b4
137
- 9 | c1
138
- 10 | c2
139
-
140
- #### myapp_networkedge
141
-
142
- id | child_id | parent_id | name
143
- ----+----------+-----------+---------
144
- 1 | 2 | 1 | root a1
145
- 2 | 3 | 1 | root a2
146
- 3 | 4 | 1 | root a3
147
- 4 | 5 | 2 | a1 b1
148
- 5 | 6 | 2 | a1 b2
149
- 6 | 6 | 3 | a2 b2
150
- 7 | 7 | 4 | a3 b3
151
- 8 | 8 | 4 | a3 b4
152
- 9 | 10 | 7 | b3 c2
153
- 10 | 9 | 7 | b3 c1
154
- 11 | 9 | 8 | b4 c1
155
-
156
- ### Diagramatic View
157
-
158
- ![ Diagram of Resulting Graph] ( https://raw.githubusercontent.com/OmenApps/django-postgresql-dag/master/docs/images/graph.png )
159
-
160
- ### Work with the Graph in the Shell (or in views, etc)
161
-
162
- ~/myapp$ python manage.py shell
163
- >>> from myapp.models import NetworkNode, NetworkEdge
164
-
165
- # Descendant methods which return a queryset
166
-
167
- >>> root.descendants()
168
- <QuerySet [<NetworkNode: a1>, <NetworkNode: a2>, <NetworkNode: a3>, <NetworkNode: b1>, <NetworkNode: b2>, <NetworkNode: b3>, <NetworkNode: b4>, <NetworkNode: c1>, <NetworkNode: c2>]>
169
- >>> root.descendants(max_depth=1)
170
- <QuerySet [<NetworkNode: a1>, <NetworkNode: a2>, <NetworkNode: a3>]>
171
- >>> root.self_and_descendants()
172
- <QuerySet [<NetworkNode: root>, <NetworkNode: a1>, <NetworkNode: a2>, <NetworkNode: a3>, <NetworkNode: b1>, <NetworkNode: b2>, <NetworkNode: b3>, <NetworkNode: b4>, <NetworkNode: c1>, <NetworkNode: c2>]>
173
- >>> root.descendants_and_self()
174
- [<NetworkNode: c2>, <NetworkNode: c1>, <NetworkNode: b4>, <NetworkNode: b3>, <NetworkNode: b2>, <NetworkNode: b1>, <NetworkNode: a3>, <NetworkNode: a2>, <NetworkNode: a1>, <NetworkNode: root>]
175
-
176
- # Ancestor methods which return a queryset
177
-
178
- >>> c1.ancestors()
179
- <QuerySet [<NetworkNode: root>, <NetworkNode: a3>, <NetworkNode: b3>, <NetworkNode: b4>]>
180
- >>> c1.ancestors(max_depth=2)
181
- <QuerySet [<NetworkNode: a3>, <NetworkNode: b3>, <NetworkNode: b4>]>
182
- >>> c1.ancestors_and_self()
183
- <QuerySet [<NetworkNode: root>, <NetworkNode: a3>, <NetworkNode: b3>, <NetworkNode: b4>, <NetworkNode: c1>]>
184
- >>> c1.self_and_ancestors()
185
- [<NetworkNode: c1>, <NetworkNode: b4>, <NetworkNode: b3>, <NetworkNode: a3>, <NetworkNode: root>]
186
-
187
- # Get the node's clan (all ancestors, self, and all descendants)
188
-
189
- >>> b3.clan()
190
- <QuerySet [<NetworkNode: root>, <NetworkNode: a3>, <NetworkNode: b3>, <NetworkNode: c1>, <NetworkNode: c2>]>
191
-
192
- # Get all roots or leaves associated with the node
193
-
194
- >>> b3.roots()
195
- {<NetworkNode: root>}
196
- >>> b3.leaves()
197
- {<NetworkNode: c1>, <NetworkNode: c2>}
198
-
199
- # Perform path search
200
-
201
- >>> root.path(c1)
202
- <QuerySet [<NetworkNode: root>, <NetworkNode: a3>, <NetworkNode: b3>, <NetworkNode: c1>]>
203
- >>> root.path(c1, max_depth=2) # c1 is 3 levels deep from root
204
- Traceback (most recent call last):
205
- File "<input>", line 1, in <module>
206
- root.path(c1, max_depth=2)
207
- File "/home/runner/pgdagtest/pg/models.py", line 550, in path
208
- ids = [item.id for item in self.path_raw(target_node, **kwargs)]
209
- File "/home/runner/pgdagtest/pg/models.py", line 546, in path_raw
210
- raise NodeNotReachableException
211
- pg.models.NodeNotReachableException
212
- >>> root.path(c1, max_depth=3)
213
- <QuerySet [<NetworkNode: root>, <NetworkNode: a3>, <NetworkNode: b3>, <NetworkNode: c1>]>
214
-
215
- # Reverse (upward) path search
216
-
217
- >>> c1.path(root) # Path defaults to top-down search, unless `directional` is set to False
218
- Traceback (most recent call last):
219
- File "<input>", line 1, in <module>
220
- c1.path(root)
221
- File "/home/runner/pgdagtest/pg/models.py", line 548, in path
222
- ids = [item.id for item in self.path_raw(target_node, **kwargs)]
223
- File "/home/runner/pgdagtest/pg/models.py", line 544, in path_raw
224
- raise NodeNotReachableException
225
- pg.models.NodeNotReachableException
226
- >>> c1.path(root, directional=False)
227
- <QuerySet [<NetworkNode: c1>, <NetworkNode: b3>, <NetworkNode: a3>, <NetworkNode: root>]>
228
- >>> root.distance(c1)
229
- 3
230
-
231
- # Check node properties
232
-
233
- >>> root.is_root()
234
- True
235
- >>> root.is_leaf()
236
- False
237
- >>> root.is_island()
238
- False
239
- >>> c1.is_root()
240
- False
241
- >>> c1.is_leaf()
242
- True
243
- >>> c1.is_island()
244
- False
245
-
246
- # Get ancestors/descendants tree output
247
-
248
- >>> a2.descendants_tree()
249
- {<NetworkNode: b2>: {}}
250
- >>> root.descendants_tree()
251
- {<NetworkNode: a1>: {<NetworkNode: b1>: {}, <NetworkNode: b2>: {}}, <NetworkNode: a2>: {<NetworkNode: b2>: {}}, <NetworkNode: a3>: {<NetworkNode: b3>: {<NetworkNode: c2>: {}, <NetworkNode: c1>: {}}, <NetworkNode: b4>: <NetworkNode: c1>: {}}}}
252
- >>> root.ancestors_tree()
253
- {}
254
- >>> c1.ancestors_tree()
255
- {<NetworkNode: b3>: {<NetworkNode: a3>: {<NetworkNode: root>: {}}}, <NetworkNode: b4>: {<NetworkNode: a3>: {<NetworkNode: root>: {}}}}
256
- >>> c2.ancestors_tree()
257
- {<NetworkNode: b3>: {<NetworkNode: a3>: {<NetworkNode: root>: {}}}}
258
-
259
- # Get a queryset of edges relatd to a particular node
260
-
261
- >>> a1.ancestors_edges()
262
- <QuerySet [<NetworkEdge: root a1>]>
263
- >>> b4.descendants_edges()
264
- <QuerySet [<NetworkEdge: b4 c1>]>
265
- >>> b4.clan_edges()
266
- <QuerySet [<NetworkEdge: root a3>, <NetworkEdge: a3 b4>, <NetworkEdge: b4 c1>]>
267
-
268
- # Get the nodes at the start or end of an edge
269
-
270
- >>> e1.parent
271
- <NetworkNode: root>
272
- >>> e1.child
273
- <NetworkNode: a1>
274
-
275
- >>> e2.parent
276
- <NetworkNode: b4>
277
- >>> e2.child
278
- <NetworkNode: c1>
279
-
280
- # Edge-specific Manager methods
281
-
282
- >>> NetworkEdge.objects.descendants(b3)
283
- <QuerySet [<NetworkEdge: b3 c2>, <NetworkEdge: b3 c1>]>
284
- >>> NetworkEdge.objects.ancestors(b3)
285
- <QuerySet [<NetworkEdge: root a3>, <NetworkEdge: a3 b3>]>
286
- >>> NetworkEdge.objects.clan(b3)
287
- <QuerySet [<NetworkEdge: root a3>, <NetworkEdge: a3 b3>, <NetworkEdge: b3 c2>, <NetworkEdge: b3 c1>]>
288
- >>> NetworkEdge.objects.path(root, c1)
289
- <QuerySet [<NetworkEdge: root a3>, <NetworkEdge: a3 b3>, <NetworkEdge: b3 c1>]>
290
- >>> NetworkEdge.objects.path(c1, root) # Path defaults to top-down search, unless `directional` is set to False
291
- Traceback (most recent call last):
292
- File "<input>", line 1, in <module>
293
- NetworkEdge.objects.path(c1, root)
294
- File "/home/runner/pgdagtest/pg/models.py", line 677, in path
295
- start_node.path(end_node),
296
- File "/home/runner/pgdagtest/pg/models.py", line 548, in path
297
- ids = [item.id for item in self.path_raw(target_node, **kwargs)]
298
- File "/home/runner/pgdagtest/pg/models.py", line 544, in path_raw
299
- raise NodeNotReachableException
300
- pg.models.NodeNotReachableException
301
- >>> NetworkEdge.objects.path(c1, root, directional=False)
302
- <QuerySet [<NetworkEdge: b3 c1>, <NetworkEdge: a3 b3>, <NetworkEdge: root a3>]>
303
18
304
19
## ToDo
305
20
306
21
- Describe methods of filtering nodes and edges within the CTE.
307
- - Finish creating proper docs, since this is getting complex .
22
+ - Finish creating proper docs.
308
23
309
24
310
25
## Credits:
0 commit comments