首先我使用了pythoncomplete的最新的0.9版,在環境變數裡面添加了DJANGO_SETTINGS_MODULE=settings,設定export PYTHONPATH=~/workspace/my_project/src。這時候我編寫一個model類比如:
# file: ~/workspace/my_project/src/main/models.py
class Book(models.Model):
name = models.CharField(max_length=100)
title = 'book title'
然後在同一目錄裡面建立一個tt.py檔案在裡面建立這個類的執行個體
from main.models import *
b = Book()
b.<C-x><C-o>
這時候只能提示title這個欄位而不會出現name欄位,這個問題我反覆實驗了多次,發現和PYTHONPATH,tags都設定都沒有關係。而是pythoncomplete這個外掛程式自身編寫有問題,如果你不讓Book繼承models.Model而是普通的object是可以提示出name欄位,看了它的代碼不是很明白所以我只是用一種補丁的方式來解決這個問題。開啟$VIM/vim72/autoload/pythoncomplete.vim檔案編輯其中的get_completions函數在
match = stmt[ridx+1:]
stmt = _sanitize(stmt[:ridx])
result = eval(stmt, self.compldict)
all = dir(result)
後面加入:
if result.__class__.__name__ == 'ModelBase':
module_name = result.__module__
class_name = result.__name__
module_instance = __import__(module_name, {}, {}, [class_name])
class_instance = getattr(module_instance, class_name)
instance = class_instance()
result = instance
all = dir(instance)
這裡面的result就是b這個對象執行個體,all是它的所有屬性。在上面的代碼中我首先判斷result的基類是否為django.db.models.base.ModelBase然後根據Book的__module__再重新動態建立一個執行個體出來,這樣就可以得到所有的欄位了。你可以把親手修改以下看看是不是真的有效果了,如果起作用了你也不要太高興,因為這隻完成了第一步,不信你把b = Book()放到一個函數裡面比如我們views.py中經常定義的函數中去你就發現改了上面的代碼也沒有什麼作用,經過反覆使用print絕技,終於定位到問題了,在evalsource函數的這段代碼身上:
for l in sc.locals:
try:
exec(l) in self.compldict
except:
print "locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l)
dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l))
print這句是我加的,方便查看具體錯誤。應該報錯的錯誤是NameError,使用了未定義的Book。經過實驗發現在一個函數裡面如果加入import就會正常,比如:
def index(request):
from main.models import *
b = Book()
這是因為exec在執行的時候是要結合內容相關的,如果你不在index函數中匯入需要的module,Book肯定是未定義,所以解決這個問題其實很簡單,將上面這段改為:
namespace = []
def evalsource(self,text,line=0):
sc = self.parser.parse(text,line)
src = sc.get_code()
dbg("source: %s" % src)
try: exec(src) in self.compldict
except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1]))
# patch for Model SubClass in fuction
if not namespace:
for item in src.split('/n'):
if item.startswith('from') or item.startswith('import'):
namespace.append(item)
for l in sc.locals:
try:
namespace.append(l)
l = '/n'.join(namespace)
exec(l) in self.compldict
except:
print "locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l)
dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l))
因為在src這個變數已經包含了前面的源碼,所以可以簡單的過濾出來你源檔案中的所有import,from語句,然後再在每次exec的時候把namespace加到它的前面就大功告成了。
說起來簡單,做起來難。就這兩個小問題折騰了我兩天還好是搞定了,看上面的代碼應該是有效能問題,好在neocomplcache外掛程式有緩衝不會讓vim反應過慢。最後還要說個問題就是現在的index函數是在太簡單了,因為正常的編碼都會從資料庫中取值比如:
b = Book.object.get(id = 1)
這時候是肯定沒有任何提示了,當然有一個弱點的解決方案就是為了提示在定義一個b = Book() 先把代碼寫完了,然後在把這句刪除了。不過更好的方發應該是根據django的源碼想辦法讓pythoncomplete可以動態得到query後的執行個體對象,這個問題就等我以後有熱情和精力再解決把。現在的這個提示功能我已經很滿足了。