django不像java框架那样有诸多配置文件,django更依赖于约定 ,比如,
- models.py,存放数据库相关model
- tests.py,存放单元测试代码
基于这种简单约定,创建一个django项目后几乎马上就可以从命令行启动。但将所有model存放于一个models.py文件中并不是一个特别好的选择,随着项目代码的不断增加,models.py很容易就会膨胀。相较而言,更喜欢java的一个class一个文件的做法,项目结构目录清晰,方便阅读查找。
拆分models.py
定义model,增加app label
django支持将models存放于单个文件中,在django app中创建models目录,在models中创建不同文件来存放model,比如,
- app/models/post.py
- app/models/category.py
在model定义的时候需要增加指明其从属的app,
class Category(models.Model):
name = models.CharField(max_length=100, db_index=True)
created_time = models.DateTimeField(auto_now_add=True, db_index=True)
def __unicode__(self):
return self.name
class Meta:
app_label = 'blog'
在定义完上述model之后,django还不能找到这个model,还需要进一步操作。
修改models/init.py
__init__.py
的作用是告诉python当前目录是一个python package。django默认从models里面读取定义的各model,因此在__init__.py
中我们需要import这些model,这样django就能依照其约定读取到正确的代码。
手动import
在__init__.py中可以手动import定义的各model,
from blog.models.post import Post
from blog.models.category import Category
自动import
手动import繁琐且容易遗忘,所以我们得寻找自动import model的方法。借助python的importlib,我们可以做到这一点。在models/init.py中添加如下代码,
class_list = fetch_all_classes(settings.HOME_DIR, os.path.join(settings.HOME_DIR, 'blog/models/'))
current_global = globals()
for clazz in class_list:
current_global[clazz.__name__] = clazz
上述代码片段中的fetch_all_classes函数用于寻找指定目录下的所有python class。其代码也并不长,
import importlib
import inspect
import re
import os
def list_py_files(source_folder):
paths = []
for base, folders, files in os.walk(source_folder):
for py_file in files:
if not py_file.endswith('.py'):
continue
path = os.path.join(base, py_file)
paths.append(path)
return paths
def build_relative_path(root_path, absolute_path_list):
return [path[len(root_path):] for path in absolute_path_list]
def build_import_name(path):
if path.startswith('/'):
path = path[1:-3]
return re.sub('/', '.', path)
def get_module_classes(module_instance):
class_list = filter(lambda instance: inspect.isclass(instance), module_instance.__dict__.values())
return {clazz.__name__: clazz for clazz in class_list}
def fetch_all_classes(root_path, source_folder):
absolute_path_list = list_py_files(source_folder)
relative_path_list = build_relative_path(root_path, absolute_path_list)
clazz_dict = {}
for path in relative_path_list:
module_name = build_import_name(path)
instance = importlib.import_module(module_name)
clazz_dict.update(get_module_classes(instance))
return clazz_dict.values()
完成上述两步之后,在models目录中就可以在任意文件中定义model了。
拆分tests.py
接着来分拆单元测试代码,思路与拆分models.py一样。只要让django在tests中找到所有的testcase就可以了。在tests/__init__.py
中添加如下代码,
import os
from solilo.commons.importer import fetch_all_classes
import settings
class_list = fetch_all_classes(settings.HOME_DIR, os.path.join(settings.HOME_DIR, 'blog/tests/'))
current_global = globals()
for clazz in class_list:
current_global[clazz.__name__] = clazz
分拆之后整个项目又有点接近java项目文件的繁杂,但至少个人看来结构清晰了一些,一定程度上提升了项目的可维护性。