2

我正在使用 rdkit 一个化学信息学工具包,它提供了一个 postgresql 墨盒以允许存储化学分子。我想创建一个 django 模型如下:

from rdkit.Chem import Mol

class compound(models.Model):
    internal = models.CharField(max_length=10 ,db_index=True)
    external = models.CharField(max_length=15,db_index=True)
    smiles   = models.TextField()
    # This is my proposed custom "mol" type defined by rdkit cartridge and that probably maps
    # to the Mol object imported from rdkit.Chem
    rdkit_mol = models.MyCustomMolField()

所以我想将“rdkit_mol”映射到 rdkit postgres 数据库目录类型“mol”。在 SQL 中,“mol”列是从“smiles”字符串使用类似语法创建的

postgres@compounds=# insert into compound (smiles,rdkit_mol,internal,external)  VALUES ('C1=CC=C[N]1',mol_from_smiles('C1=CC=C[N]1'), 'MYID-111111', 'E-2222222');

这些调用由墨盒定义的“mol_from_smiles”数据库函数来创建 mol 对象。

我是否应该让数据库在保存期间处理此列的创建。我可以让他们在 postgres 中定义一个自定义 TRIGGER,它运行 mol_from_smiles 函数来填充 rdkit_mol 列。

我还希望能够使用返回 django 模型的 mol 自定义功能执行查询。例如,其中一个 SQL 查询可以让我返回化学上看起来像这样的复合模型。目前在 SQL 我做

select * from compound where rdkit_mol @> 'C1=CC=C[N]1';

然后,这基本上返回了化学“化合物”对象。

我的问题是:鉴于我的领域的自定义性质。有没有办法将数据库“mol”类型的特征与 django 复合模型混合和匹配?有什么方法可以实现这一目标。

目前我倾向于不使用 Django ORM,而只使用原始 SQL 来回数据库。我想知道是否有使用这种自定义类型的 django 方式。

在我目前的混合方法中,我的观点看起来像这样。

def get_similar_compounds(request):
    # code to get the raw smiles string for eg 'C1=CC=C[N]1' from a form
    db_cursor.execute("select internal from compound where rdkit_mol @> 'C1=CC=C[N]1';")
    # code to get internal ids from database cursor
    similar_compounds = compound.objects.filter(internal__in = ids_from_query_above)
    # Then process queryset

这种混合方法是否可取,或者是否有更多的 pythonic/django 方式来处理这种自定义数据类型。

4

2 回答 2

1

混合的方法是提供自定义字段实现 - 您已经在做的事情。没有更多的了。

自定义字段具有相当广泛的协议来自定义其行为。您可以自定义在将值发送到数据库之前发生的情况、接收时发生的情况、使用特定查找(例如mol__in=sth)时发生的情况。

在当前的开发版本中,Django 允许提供自定义查找类型,因此您甚至可以实现@>运算符(尽管我建议坚持使用官方稳定版本)。

最后,这取决于对您来说更容易的是什么。提供良好、连贯的实施MolField可能会非常耗时。因此,这实际上取决于您需要多少地方。在这几个地方只使用原始 SQL 可能更实用。

于 2014-02-25T18:57:57.097 回答
0

我的问题主要涉及创建 django 自定义字段以处理 postgres rdkit 数据盒定义的“mol”数据类型的机制。

我制定的解决方案包括一个自定义字段,该字段将与我的模型共存,然后使用原始 SQL 对 mol 类型运行查询。

由于每次实例化包含模型实例的 SMILES 时,我都需要创建一个 rdkit “mol”类型,我创建了一个数据库过程和一个在表插入或更新时触发的触发器。

# A south migration that defines a function called write_rdkit_mol_south in PL/PGSQL

from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models


class Migration(DataMigration):
    def forwards(self, orm):
        "Write your forwards methods here."
        db.execute("""create function write_rdkit_mol_south() RETURNS trigger as $write_rdkit_mol_south$
BEGIN
NEW.rdkit_mol := mol_from_smiles(NEW.smiles::cstring);
RETURN NEW;
END;
$write_rdkit_mol_south$ LANGUAGE plpgsql;""")
        db.execute(
            "create TRIGGER write_rdkit_mol_trig BEFORE INSERT OR UPDATE on strucinfo_compound  FOR EACH ROW EXECUTE PROCEDURE write_rdkit_mol_south();")

        # Note: Don't use "from appname.models import ModelName".
        # Use orm.ModelName to refer to models in this application,
        # and orm['appname.ModelName'] for models in other applications.

    def backwards(self, orm):
        "Write your backwards methods here."
        db.execute("drop TRIGGER write_rdkit_mol_trig ON strucinfo_compound;")
        db.execute("DROP FUNCTION write_rdkit_mol_south();")

接下来我创建了自定义字段和模型。

# My Django model:
class compound(models.Model):
    internalid = models.CharField(max_length=10 ,db_index=True)
    externalid = models.CharField(max_length=15,db_index=True)
    smiles = models.TextField()
    rdkit_mol = RdkitMolField()

    def save(self,*args,**kwargs):
        self.rdkit_mol = ""
        super(compound,self).save(*args,**kwargs)

# The custom field


class RdkitMolField(models.Field):

    description = "Rdkit molecule field"

    def __init__(self,*args,**kwds):
        super(RdkitMolField,self).__init__(*args,**kwds)

    def db_type(self, connection):
        if connection.settings_dict['ENGINE'] == 'django.db.backends.postgresql_psycopg2':
            return None
        else:
            raise DatabaseError('Field type only supported for Postgres with rdkit cartridge')


    def to_python(self, value):
        if isinstance(value,Chem.Mol):
            return value
        if isinstance(value,basestring):
            # The database normally returns the smiles string
            return Chem.MolFromSmiles(str(value))
        else:
            if value:
            #if mol_send was used then we will have a pickled object
                return Chem.Mol(str(value))
            else:
            # The None Case
                return "NO MOL"
   def get_prep_value(self, value):
        # This gets called during save
        # the method should return data in a format that has been prepared for use as a parameter in a query : say the docs
        # rdkit_mol queries do accept smiles strings

        if isinstance(value,basestring):
            db_smiles = str(value)
            if db_smiles:
                my_mol = Chem.MolFromSmiles(db_smiles)
            else:
                return None
            if my_mol:
                # Roundtrip with object could be avoided
                return str(Chem.MolToSmiles(my_mol))
        elif isinstance(value,(str,unicode)):
            valid_smiles = str(Chem.MolToSmiles(Chem.MolFromSmiles(str(value))))
            if valid_smiles:
                return valid_smiles
            else:
                # This is the None case
                # The database trigger will handle this as this should happen only during insert or update
                return None

    def validate(self, value, model_instance):
        # This field is handled by database trigger so we do not want it to be used for object initiation
        if value is None:
            return
        else:
            super(RdkitMolField,self).validate(value,model_instance)
于 2014-03-11T01:47:20.010 回答