forked from enviPath/enviPy
Implement Compound CRUD (#22)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#22
This commit is contained in:
115
epdb/models.py
115
epdb/models.py
@ -281,58 +281,121 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
def structures(self):
|
||||
return CompoundStructure.objects.filter(compound=self)
|
||||
|
||||
@property
|
||||
def normalized_structure(self):
|
||||
return CompoundStructure.objects.get(compound=self, normalized_structure=True)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return '{}/compound/{}'.format(self.package.url, self.uuid)
|
||||
|
||||
# @property
|
||||
# def related_pathways(self):
|
||||
# pathways = Node.objects.filter(node_labels__in=[self.default_structure]).values_list('pathway', flat=True)
|
||||
# return Pathway.objects.filter(package=self.package, id__in=set(pathways)).order_by('name')
|
||||
@transaction.atomic
|
||||
def set_default_structure(self, cs: 'CompoundStructure'):
|
||||
if cs.compound != self:
|
||||
raise ValueError("Attempt to set a CompoundStructure stored in a different compound as default")
|
||||
|
||||
# @property
|
||||
# def related_reactions(self):
|
||||
# return (
|
||||
# Reaction.objects.filter(package=self.package, educts__in=[self.default_structure])
|
||||
# |
|
||||
# Reaction.objects.filter(package=self.package, products__in=[self.default_structure])
|
||||
# ).order_by('name')
|
||||
self.default_structure = cs
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def related_pathways(self):
|
||||
pathways = Node.objects.filter(node_labels__in=[self.default_structure]).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, educts__in=[self.default_structure])
|
||||
|
|
||||
Reaction.objects.filter(package=self.package, products__in=[self.default_structure])
|
||||
).order_by('name')
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create(package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs):
|
||||
def create(package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs) -> 'Compound':
|
||||
|
||||
# Pre check
|
||||
# Validity of SMILES etc
|
||||
if smiles is None or smiles == '':
|
||||
raise ValueError('SMILES is required')
|
||||
|
||||
smiles = smiles.strip()
|
||||
|
||||
parsed = FormatConverter.from_smiles(smiles)
|
||||
if parsed is None:
|
||||
raise ValueError('Given SMILES is invalid')
|
||||
|
||||
standardized_smiles = FormatConverter.standardize(smiles)
|
||||
|
||||
# Check if we find a direct match for a given SMILES
|
||||
if CompoundStructure.objects.filter(smiles=smiles, compound__package=package).exists():
|
||||
return CompoundStructure.objects.get(smiles=smiles, compound__package=package).compound
|
||||
|
||||
# Check if we can find the standardized one
|
||||
if CompoundStructure.objects.filter(smiles=standardized_smiles, compound__package=package).exists():
|
||||
# TODO should we add a structure?
|
||||
return CompoundStructure.objects.get(smiles=standardized_smiles, compound__package=package).compound
|
||||
|
||||
# Generate Compound
|
||||
c = Compound()
|
||||
c.package = package
|
||||
if name is not None:
|
||||
|
||||
# For name and description we have defaults so only set them if they carry a real value
|
||||
if name is not None and name != '':
|
||||
c.name = name
|
||||
|
||||
if description is not None:
|
||||
if description is not None and description != '':
|
||||
c.description = description
|
||||
|
||||
c.save()
|
||||
|
||||
normalized_smiles = smiles # chem.normalize(smiles)
|
||||
is_standardized = standardized_smiles == smiles
|
||||
|
||||
if normalized_smiles != smiles:
|
||||
_ = CompoundStructure.create(c, normalized_smiles, name='Normalized structure of {}'.format(name),
|
||||
if not is_standardized:
|
||||
_ = CompoundStructure.create(c, standardized_smiles, name='Normalized structure of {}'.format(name),
|
||||
description='{} (in its normalized form)'.format(description),
|
||||
normalized_structure=True)
|
||||
|
||||
cs = CompoundStructure.create(c, smiles, name=name, description=description)
|
||||
cs = CompoundStructure.create(c, smiles, name=name, description=description, normalized_structure=is_standardized)
|
||||
|
||||
c.default_structure = cs
|
||||
c.save()
|
||||
|
||||
return c
|
||||
|
||||
@transaction.atomic
|
||||
def add_structure(self, smiles: str, name: str = None, description: str = None, default_structure: bool = False,
|
||||
*args, **kwargs) -> 'CompoundStructure':
|
||||
|
||||
if smiles is None or smiles == '':
|
||||
raise ValueError('SMILES is required')
|
||||
|
||||
smiles = smiles.strip()
|
||||
|
||||
parsed = FormatConverter.from_smiles(smiles)
|
||||
if parsed is None:
|
||||
raise ValueError('Given SMILES is invalid')
|
||||
|
||||
standardized_smiles = FormatConverter.standardize(smiles)
|
||||
|
||||
is_standardized = standardized_smiles == smiles
|
||||
|
||||
if self.normalized_structure.smiles != standardized_smiles:
|
||||
raise ValueError('The standardized SMILES does not match the compounds standardized one!')
|
||||
|
||||
if is_standardized:
|
||||
CompoundStructure.objects.get(smiles__in=smiles, compound__package=self.package)
|
||||
|
||||
# Check if we find a direct match for a given SMILES and/or its standardized SMILES
|
||||
if CompoundStructure.objects.filter(smiles__in=smiles, compound__package=self.package).exists():
|
||||
return CompoundStructure.objects.get(smiles__in=smiles, compound__package=self.package)
|
||||
|
||||
cs = CompoundStructure.create(self, smiles, name=name, description=description, normalized_structure=is_standardized)
|
||||
|
||||
if default_structure:
|
||||
self.default_structure = cs
|
||||
self.save()
|
||||
|
||||
return cs
|
||||
|
||||
class Meta:
|
||||
unique_together = [('uuid', 'package')]
|
||||
|
||||
@ -391,6 +454,10 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
def InChIKey(self):
|
||||
return FormatConverter.InChIKey(self.smiles)
|
||||
|
||||
@property
|
||||
def canonical_smiles(self):
|
||||
return FormatConverter.canonicalize(self.smiles)
|
||||
|
||||
@property
|
||||
def as_svg(self):
|
||||
return IndigoUtils.mol_to_svg(self.smiles)
|
||||
@ -401,10 +468,10 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
|
||||
|
||||
# I think this only affects Django Admin which we are barely using
|
||||
# # https://github.com/django-polymorphic/django-polymorphic/issues/229
|
||||
# _non_polymorphic = models.Manager()
|
||||
#
|
||||
# class Meta:
|
||||
# base_manager_name = '_non_polymorphic'
|
||||
_non_polymorphic = models.Manager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = '_non_polymorphic'
|
||||
|
||||
@abc.abstractmethod
|
||||
def apply(self, *args, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user