Tool to iterate over django relation tree
Project description
django-relations-Iterator
Provides utilities for iterating over django model instances hierarchy
Example:
Simple instances tree clone
#models.py
from django.conf import settings
from django.db import models
class Meeting(models.Model):
title = models.CharField(max_length=200)
time = models.DateTimeField(null=True, blank=True)
participants = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Participation', blank=True)
class Participation(models.Model):
meeting = models.ForeignKey('Meeting', on_delete=models.CASCADE, related_name='participations')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='participations')
class Invitation(models.Model):
STATUS_SENT = 'sent'
STATUS_ACCEPTED = 'accepted'
STATUS_DECLINED = 'declined'
STATUS_CHOICES = (
(STATUS_SENT, STATUS_SENT),
(STATUS_ACCEPTED, STATUS_ACCEPTED),
(STATUS_DECLINED, STATUS_DECLINED),
)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_SENT)
participation = models.ForeignKey('Participation', on_delete=models.CASCADE, related_name='invitations')
class Comment(models.Model):
meeting = models.ForeignKey('Meeting', on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
description = models.TextField(max_length=3000)
#clone.py
from relations_iterator import TreeNode, AbstractVisitor, RelationTreeIterator, ConfigurableRelationTree
from .models import Meeting
# because of config, tree will ignore comments, but will consider all participations and invitations
CLONE_STRUCTURE = {
'participations': {
'invitations': {}
}
}
class CloneVisitor(AbstractVisitor):
def visit(self, node: TreeNode):
node.instance.pk = None
if node.parent is not None:
parent_joining_column, instance_joining_column = node.relation.get_joining_columns()[0]
setattr(
node.instance,
instance_joining_column,
getattr(node.parent.instance, parent_joining_column)
)
node.instance.save()
def clone(instance, config):
visitor = CloneVisitor()
tree = ConfigurableRelationTree(root=instance, structure=config)
for node in RelationTreeIterator(tree=tree):
visitor.visit(node)
meeting = Meeting.objects.last()
clone(meeting, CLONE_STRUCTURE)
Installation
pip install django-relations-iterator
Features
Instance tree
from relations_iterator import ConfigurableRelationTree
Collects all related instances from model hierarchy accordingly to the provided config
from pprint import pprint
from django.contrib.auth.models import User
from relations_iterator import ConfigurableRelationTree
from tests.meetings.models import Meeting, Participation, Invitation, Comment
tom = User.objects.create(username='Tom')
jerry = User.objects.create(username='Jerry')
meeting = Meeting.objects.create(title='dinner')
tom_participation = Participation.objects.create(user_id=tom.id, meeting_id=meeting.id)
jerry_participation = Participation.objects.create(user_id=jerry.id, meeting_id=meeting.id)
Invitation.objects.create(user_id=jerry.id, meeting_id=meeting.id)
Comment.objects.create(user_id=jerry.id, meeting_id=meeting.id)
config = {
'participations': {
'invitations': {}
}
}
meeting = Meeting.objects.last()
tree = ConfigurableRelationTree(root=instance, structure=config)
pprint(tree.tree)
# Output:
{
<TreeNode for Meeting: Meeting object (1)>: {
<ManyToOneRel: meetings.participation>: {
<TreeNode for Participation: Participation object (1)>: {
<ManyToOneRel: meetings.invitation>: {
<TreeNode for Invitation: Invitation object (1)>: {}
}
},
<TreeNode for Participation: Participation object (2)>: {
<ManyToOneRel: meetings.invitation>: {}
}
}
}
}
For provided config tree will build himself only with participations
and invitations
relations and will ignore any other relations.
Tree iterator
from relations_iterator import RelationTreeIterator
Iterates over provided tree and yieds nodes one by one
For example above it will look like
pprint(list(node for node in RelationTreeIterator(tree)))
# Output
[<TreeNode for Meeting: Meeting object (1)>,
<TreeNode for Participation: Participation object (1)>,
<TreeNode for Invitation: Invitation object (1)>,
<TreeNode for Participation: Participation object (2)>]
Abstract Visitor iterator
from relations_iterator import AbstractVisitor
Provides abstract class, with interface to implement visitor pattern. You must implement .visit(node)
method, to complete implementation
Examples:
Clone visitor
from relations_iterator import TreeNode
class CloneVisitor(AbstractVisitor):
def visit(self, node: TreeNode):
node.instance.pk = None
if node.parent is not None:
parent_joining_column, instance_joining_column = node.relation.get_joining_columns()[0]
setattr(
node.instance,
instance_joining_column,
getattr(node.parent.instance, parent_joining_column)
)
node.instance.save()
Clone visitor will clone every instance in hierarchy and set proper parent, so it can be used to implement instance hierarchy clone
CLONE_STRUCTURE = {
'participations': {
'invitations': {}
}
}
def clone(instance, config):
visitor = CloneVisitor()
tree = ConfigurableRelationTree(root=instance, structure=config)
for node in RelationTreeIterator(tree=tree):
visitor.visit(node)
clone(meeting, CLONE_STRUCTURE)
cloned_meeting = Meeting.objects.last()
tree = ConfigurableRelationTree(root=cloned_meeting, structure=CLONE_STRUCTURE)
pprint(tree.tree)
# Output
{
<TreeNode for Meeting: Meeting object (2)>: {
<ManyToOneRel: meetings.participation>: {
<TreeNode for Participation: Participation object (3)>: {
<ManyToOneRel: meetings.invitation>: {
<TreeNode for Invitation: Invitation object (2)>: {}
}
},
<TreeNode for Participation: Participation object (3)>: {
<ManyToOneRel: meetings.invitation>: {}
}
}
}
}
Path print visitor
Path print visitor will print all parent nodes from root to curent node
class PathPrintVisitor(AbstractVisitor):
def visit(self, node: TreeNode):
print(list(reversed(self.get_path(node))))
def get_path(self, node: TreeNode):
path = [node]
if node.parent:
path.extend(self.get_path(node.parent))
return path
visitor = PathPrintVisitor()
for node in RelationTreeIterator(tree):
visitor.visit(node)
# Output
[<TreeNode for Meeting: Meeting object (2)>]
[<TreeNode for Meeting: Meeting object (2)>, <TreeNode for Participation: Participation object (3)>]
[<TreeNode for Meeting: Meeting object (2)>, <TreeNode for Participation: Participation object (3)>, <TreeNode for Invitation: Invitation object (2)>]
[<TreeNode for Meeting: Meeting object (2)>, <TreeNode for Participation: Participation object (4)>]
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for django-relations-iterator-0.0.4.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 53441ae513d44527e4c757bc1b28fe1fe1466000cadafa5fd3458ed4da401ca1 |
|
MD5 | e25fd52f5a5ff3d4f123ba21d3949b24 |
|
BLAKE2b-256 | 579c3ba202ab0d30aedfb4b5237e959ccaeb60972a2bb13c32f027b7723361af |
Hashes for django_relations_iterator-0.0.4-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3f4bb357f879eb29b49c137e0e9de40dd153d92095d31f4f564d30cf2c9e318d |
|
MD5 | fe90da2d11b94eced0d66f635f4f395d |
|
BLAKE2b-256 | 9fc7a27b8dd0474b8e5a6f16b59649797f2fcac7cb933f36ac80ff68dd445686 |