Tool to iterate over django relation tree
Project description
django-relations-Iterator
Provides utilities for iterating over django model instances hierarchy. Provides easy out-of-the-box way to clone django instances.
Reasoning and solution for use case with cloning - https://hackernoon.com/the-smart-way-to-clone-django-instances
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 clone, CloneVisitor
from .models import Meeting
# because of config, tree will ignore comments, but will consider all participations and invitations
CLONE_STRUCTURE = {
'participations': {
'invitations': {}
}
}
meeting = Meeting.objects.last()
clone(meeting, CLONE_STRUCTURE, CloneVisitor())
Customizing cloning process
# Example: you want to set title for cloned Meeting as {original_title}-COPY
# and set time of the instance to None
class CustomCloneVisitor(CloneVisitor):
@singledispatchmethod
def customize(self, instance):
pass
@customize.register
def _(self, instance: Meeting):
instance.title = f'{instance.title}-COPY'
instance.time = None
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
Instances cloning feature
from relations_iterator import clone, CloneVisitor
Provides function to clone instances and simple CloneVisitor class, just as explained below in examples section.
clone
function accepts 3 arguments:
instance
- django Model instance, that needs to be clonedconfig
- config dictionary of the structure that needs to be clonedvisitor
- visitor instance.CloneVisitor
can be used directly or you can customize it and pass your own implementation
Config explanation:
Example:
# Config for cloning Meeting instance, we want to clone also participation's and invitations
config = {
'participations': { # related name for `Participation` model, that have fk to Meeting
'invitations': {} # related name for `Invitation` model, that have fk to `Participation` model
}
}
# All other relations will be skipped, as they are not listed in config
Examples:
Clone visitor full implementation
from django.db.models import Model
from relations_iterator import TreeNode, AbstractVisitor
class CloneVisitor(AbstractVisitor):
def visit(self, node: TreeNode):
node.instance.pk = None
if node.parent is not None:
parent_joining_field, instance_joining_field = node.relation.get_joining_fields()[0]
setattr(
node.instance,
instance_joining_field.attname,
parent_joining_field.value_from_object(node.parent.instance)
)
self.customize(node.instance)
node.instance.save()
def customize(self, instance: Model) -> None:
pass
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
File details
Details for the file django_relations_iterator-0.0.5.tar.gz
.
File metadata
- Download URL: django_relations_iterator-0.0.5.tar.gz
- Upload date:
- Size: 5.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.10.12
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 036e7d058f2782a37471b2b8135e6a2813edd1f1b0d59b543b609b449826c5e8 |
|
MD5 | 30fee5ef73115de53033e33029107f93 |
|
BLAKE2b-256 | 0f8dbcfed82be149531167506a2d5f5365c8eec8b06020baed255d4de7f83039 |
File details
Details for the file django_relations_iterator-0.0.5-py3-none-any.whl
.
File metadata
- Download URL: django_relations_iterator-0.0.5-py3-none-any.whl
- Upload date:
- Size: 7.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.10.12
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3f04306f77152bf37c0e3218c8bf655ef9a427b8f8f7406d457e76455e87e616 |
|
MD5 | d430777ee94e3aa5885f7ab79a2d6cbf |
|
BLAKE2b-256 | d84e9218ec63f6b95f0021505ecfb05b514785f170c8c2572eded752c30b5a8b |