forked from enviPath/enviPy
Copy Objects between Packages (#59)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#59
This commit is contained in:
260
epdb/models.py
260
epdb/models.py
@ -533,15 +533,16 @@ class AliasMixin(models.Model):
|
||||
@transaction.atomic
|
||||
def add_alias(self, new_alias, set_as_default=False):
|
||||
if set_as_default:
|
||||
self.aliases.add(self.name)
|
||||
self.aliases.append(self.name)
|
||||
self.name = new_alias
|
||||
|
||||
if new_alias in self.aliases:
|
||||
self.aliases.remove(new_alias)
|
||||
else:
|
||||
if new_alias not in self.aliases:
|
||||
self.aliases.add(new_alias)
|
||||
self.aliases.append(new_alias)
|
||||
|
||||
self.aliases = sorted(list(set(self.aliases)), key=lambda x: x.lower())
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
@ -762,6 +763,64 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
|
||||
|
||||
return cs
|
||||
|
||||
@transaction.atomic
|
||||
def copy(self, target: 'Package', mapping: Dict):
|
||||
if self in mapping:
|
||||
return mapping[self]
|
||||
|
||||
new_compound = Compound.objects.create(
|
||||
package=target,
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
kv=self.kv.copy() if self.kv else {}
|
||||
)
|
||||
mapping[self] = new_compound
|
||||
|
||||
# Copy compound structures
|
||||
for structure in self.structures.all():
|
||||
if structure not in mapping:
|
||||
new_structure = CompoundStructure.objects.create(
|
||||
compound=new_compound,
|
||||
smiles=structure.smiles,
|
||||
canonical_smiles=structure.canonical_smiles,
|
||||
inchikey=structure.inchikey,
|
||||
normalized_structure=structure.normalized_structure,
|
||||
name=structure.name,
|
||||
description=structure.description,
|
||||
kv=structure.kv.copy() if structure.kv else {}
|
||||
)
|
||||
mapping[structure] = new_structure
|
||||
|
||||
# Copy external identifiers for structure
|
||||
for ext_id in structure.external_identifiers.all():
|
||||
ExternalIdentifier.objects.create(
|
||||
content_object=new_structure,
|
||||
database=ext_id.database,
|
||||
identifier_value=ext_id.identifier_value,
|
||||
url=ext_id.url,
|
||||
is_primary=ext_id.is_primary
|
||||
)
|
||||
|
||||
if self.default_structure:
|
||||
new_compound.default_structure = mapping.get(self.default_structure)
|
||||
new_compound.save()
|
||||
|
||||
for a in self.aliases:
|
||||
new_compound.add_alias(a)
|
||||
new_compound.save()
|
||||
|
||||
# Copy external identifiers for compound
|
||||
for ext_id in self.external_identifiers.all():
|
||||
ExternalIdentifier.objects.create(
|
||||
content_object=new_compound,
|
||||
database=ext_id.database,
|
||||
identifier_value=ext_id.identifier_value,
|
||||
url=ext_id.url,
|
||||
is_primary=ext_id.is_primary
|
||||
)
|
||||
|
||||
return new_compound
|
||||
|
||||
class Meta:
|
||||
unique_together = [('uuid', 'package')]
|
||||
|
||||
@ -817,6 +876,14 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
|
||||
|
||||
return cs
|
||||
|
||||
@transaction.atomic
|
||||
def copy(self, target: 'Package', mapping: Dict):
|
||||
if self in mapping:
|
||||
return mapping[self]
|
||||
|
||||
self.compound.copy(target, mapping)
|
||||
return mapping[self]
|
||||
|
||||
@property
|
||||
def as_svg(self, width: int = 800, height: int = 400):
|
||||
return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
|
||||
@ -856,22 +923,54 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
return cls.create(*args, **kwargs)
|
||||
|
||||
|
||||
#
|
||||
# @property
|
||||
# def related_pathways(self):
|
||||
# reaction_ids = self.related_reactions.values_list('id', flat=True)
|
||||
# pathways = Edge.objects.filter(edge_label__in=reaction_ids).values_list('pathway', flat=True)
|
||||
# return Pathway.objects.filter(package=self.package, id__in=set(pathways)).order_by('name')
|
||||
#
|
||||
# @property
|
||||
# def related_reactions(self):
|
||||
# return (
|
||||
# Reaction.objects.filter(package=self.package, rules__in=[self])
|
||||
# |
|
||||
# Reaction.objects.filter(package=self.package, rules__in=[self])
|
||||
# ).order_by('name')
|
||||
#
|
||||
#
|
||||
@transaction.atomic
|
||||
def copy(self, target: 'Package', mapping: Dict):
|
||||
"""Copy a rule to the target package."""
|
||||
if self in mapping:
|
||||
return mapping[self]
|
||||
|
||||
# Get the specific rule type and copy accordingly
|
||||
rule_type = type(self)
|
||||
|
||||
if rule_type == SimpleAmbitRule:
|
||||
new_rule = SimpleAmbitRule.objects.create(
|
||||
package=target,
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
smirks=self.smirks,
|
||||
reactant_filter_smarts=self.reactant_filter_smarts,
|
||||
product_filter_smarts=self.product_filter_smarts,
|
||||
kv=self.kv.copy() if self.kv else {}
|
||||
)
|
||||
elif rule_type == SimpleRDKitRule:
|
||||
new_rule = SimpleRDKitRule.objects.create(
|
||||
package=target,
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
reaction_smarts=self.reaction_smarts,
|
||||
kv=self.kv.copy() if self.kv else {}
|
||||
)
|
||||
elif rule_type == ParallelRule:
|
||||
new_rule = ParallelRule.objects.create(
|
||||
package=target,
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
kv=self.kv.copy() if self.kv else {}
|
||||
)
|
||||
# Copy simple rules relationships
|
||||
for simple_rule in self.simple_rules.all():
|
||||
copied_simple_rule = simple_rule.copy(target, mapping)
|
||||
new_rule.simple_rules.add(copied_simple_rule)
|
||||
elif rule_type == SequentialRule:
|
||||
raise ValueError("SequentialRule copy not implemented!")
|
||||
else:
|
||||
raise ValueError(f"Unknown rule type: {rule_type}")
|
||||
|
||||
mapping[self] = new_rule
|
||||
|
||||
return new_rule
|
||||
|
||||
|
||||
class SimpleRule(Rule):
|
||||
pass
|
||||
|
||||
@ -1141,6 +1240,50 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
|
||||
r.save()
|
||||
return r
|
||||
|
||||
@transaction.atomic
|
||||
def copy(self, target: 'Package', mapping: Dict ) -> 'Reaction':
|
||||
"""Copy a reaction to the target package."""
|
||||
if self in mapping:
|
||||
return mapping[self]
|
||||
|
||||
# Create new reaction
|
||||
new_reaction = Reaction.objects.create(
|
||||
package=target,
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
multi_step=self.multi_step,
|
||||
medline_references=self.medline_references,
|
||||
kv=self.kv.copy() if self.kv else {}
|
||||
)
|
||||
mapping[self] = new_reaction
|
||||
|
||||
# Copy educts (reactant compounds)
|
||||
for educt in self.educts.all():
|
||||
copied_educt = educt.copy(target, mapping)
|
||||
new_reaction.educts.add(copied_educt)
|
||||
|
||||
# Copy products
|
||||
for product in self.products.all():
|
||||
copied_product = product.copy(target, mapping)
|
||||
new_reaction.products.add(copied_product)
|
||||
|
||||
# Copy rules
|
||||
for rule in self.rules.all():
|
||||
copied_rule = rule.copy(target, mapping)
|
||||
new_reaction.rules.add(copied_rule)
|
||||
|
||||
# Copy external identifiers
|
||||
for ext_id in self.external_identifiers.all():
|
||||
ExternalIdentifier.objects.create(
|
||||
content_object=new_reaction,
|
||||
database=ext_id.database,
|
||||
identifier_value=ext_id.identifier_value,
|
||||
url=ext_id.url,
|
||||
is_primary=ext_id.is_primary
|
||||
)
|
||||
|
||||
return new_reaction
|
||||
|
||||
def smirks(self):
|
||||
return f"{'.'.join([cs.smiles for cs in self.educts.all()])}>>{'.'.join([cs.smiles for cs in self.products.all()])}"
|
||||
|
||||
@ -1375,6 +1518,87 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
|
||||
return pw
|
||||
|
||||
@transaction.atomic
|
||||
def copy(self, target: 'Package', mapping: Dict) -> 'Pathway':
|
||||
|
||||
if self in mapping:
|
||||
return mapping[self]
|
||||
|
||||
# Start copying the pathway
|
||||
new_pathway = Pathway.objects.create(
|
||||
package=target,
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
setting=self.setting, # TODO copy settings?
|
||||
kv=self.kv.copy() if self.kv else {}
|
||||
)
|
||||
|
||||
# # Copy aliases if they exist
|
||||
# if hasattr(self, 'aliases'):
|
||||
# new_pathway.aliases.set(self.aliases.all())
|
||||
#
|
||||
# # Copy scenarios if they exist
|
||||
# if hasattr(self, 'scenarios'):
|
||||
# new_pathway.scenarios.set(self.scenarios.all())
|
||||
|
||||
# Copy all nodes first
|
||||
for node in self.nodes.all():
|
||||
# Copy the compound structure for the node label
|
||||
copied_structure = None
|
||||
if node.default_node_label:
|
||||
copied_compound = node.default_node_label.compound.copy(target, mapping)
|
||||
# Find the corresponding structure in the copied compound
|
||||
for structure in copied_compound.structures.all():
|
||||
if structure.smiles == node.default_node_label.smiles:
|
||||
copied_structure = structure
|
||||
break
|
||||
|
||||
new_node = Node.objects.create(
|
||||
pathway=new_pathway,
|
||||
default_node_label=copied_structure,
|
||||
depth=node.depth,
|
||||
name=node.name,
|
||||
description=node.description,
|
||||
kv=node.kv.copy() if node.kv else {}
|
||||
)
|
||||
mapping[node] = new_node
|
||||
|
||||
# Copy node labels (many-to-many relationship)
|
||||
for label in node.node_labels.all():
|
||||
copied_label_compound = label.compound.copy(target, mapping)
|
||||
for structure in copied_label_compound.structures.all():
|
||||
if structure.smiles == label.smiles:
|
||||
new_node.node_labels.add(structure)
|
||||
break
|
||||
|
||||
# Copy all edges
|
||||
for edge in self.edges.all():
|
||||
# Copy the reaction for edge label if it exists
|
||||
copied_reaction = None
|
||||
if edge.edge_label:
|
||||
copied_reaction = edge.edge_label.copy(target, mapping)
|
||||
|
||||
new_edge = Edge.objects.create(
|
||||
pathway=new_pathway,
|
||||
edge_label=copied_reaction,
|
||||
name=edge.name,
|
||||
description=edge.description,
|
||||
kv=edge.kv.copy() if edge.kv else {}
|
||||
)
|
||||
|
||||
# Copy start and end nodes relationships
|
||||
for start_node in edge.start_nodes.all():
|
||||
if start_node in mapping:
|
||||
new_edge.start_nodes.add(mapping[start_node])
|
||||
|
||||
for end_node in edge.end_nodes.all():
|
||||
if end_node in mapping:
|
||||
new_edge.end_nodes.add(mapping[end_node])
|
||||
|
||||
mapping[self] = new_pathway
|
||||
|
||||
return new_pathway
|
||||
|
||||
@transaction.atomic
|
||||
def add_node(self, smiles: str, name: Optional[str] = None, description: Optional[str] = None):
|
||||
return Node.create(self, smiles, 0)
|
||||
|
||||
Reference in New Issue
Block a user