2014-02-25 47 views
1

我正在使用rdkit cheminformatics工具包,它提供了postgresql盒式磁帶以允許存儲化學分子。 我想創建一個Django模型如下:如何處理/將自定義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數據庫catridge型「摩爾」。在SQL的「摩爾」列中,從「微笑」的字符串使用像

[email protected]=# 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」數據庫功能創建的。

我應該在保存期間讓數據庫負責保存這一列。我可以在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處理這種自定義數據類型的方式。

回答

1

混合的方法是提供自定義字段實現 - 你已經在做什麼。沒有更多的東西。

自定義字段有quite extensive protocol用於自定義其行爲。您可以自定義在將值發送到數據庫之前發生的情況,收到時會發生什麼,使用特定查找時會發生什麼(例如mol__in=sth)。

在當前的開發版本中,Django允許提供自定義查找類型,因此甚至可以實現@>運算符(儘管我建議堅持使用官方穩定版本)。

最終它取決於什麼更容易爲你。提供良好,一致的MolField實施可能會耗費時間。因此,這取決於你需要多少地方。在少數幾個地方使用原始SQL可能更實用。

+0

謝謝,很高興知道我在正確的軌道上。對於如何創建映射到rdkit mol類的自定義模型字段,我很困惑。部分問題是我不知道ORM如何構造和解構數據庫的值。所有的例子都是以文本爲中心的,這似乎更加微不足道。在這裏,我的mol對象不是真正的文本正確嗎?除非微笑字符串持有創建自定義字段的關鍵。任何關於如何寫這個自定義字段的指針? – harijay

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)