1. 概述:
Django 导包的绝对路径应该和manage.py的位置有关,具体原理尚不清楚。
2. 相关测试:
步骤1:
原Django项目的目录结构为:
test_project
├── LICENSE
├── main.py
└── test_django # Django项目根目录
├── db.sqlite3
├── manage.py # manage.py的位置
└── test_django
├── apps
│ └── app1
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
manage.py的默认位置为 test_project/test_django/manage.py
然后添加文件test.py 路径:test_project/test_django/test_django/utilities/test.py
test_project
├── LICENSE
├── main.py
└── test_django # Django项目根目录
├── db.sqlite3
├── manage.py # manage.py的位置
└── test_django
├── apps
│ └── app1
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── utilities # 在此位置增加utilities目录
│ └── test.py # 在utilities目录下增加test.py
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
test.py的内容:
class TestClass(object):
def test(self):
print('TestClass')
在/test_project/test_django/test_django/apps/app1/models.py中进行测试(因为models.py在启动项目时就会加载)
models.py的内容:
from test_django.test_django.utilis.test import TestClass
TestClass().test()
执行
/test_project/test_django/manage.py runserver
报错:
ModuleNotFoundError: No module named 'test_django.test_django'
此时,若将test_django.test_django.utilis.test的父目录加入sys.path
models.py的内容:
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))))
from test_django.test_django.utilis.test import TestClass
TestClass().test()
也仍然报相同的错误。
若将models.py改为
# 导包路径中去掉manage.py所在的一级目录
from test_django.utilis.test import TestClass
TestClass().test()
则不报错。
所以,猜测Django 导包的绝对路径应该从manage.py所在目录的子目录开始。
步骤2:
将manage.py移动到Django项目根目录的上一级目录 test_project/manage.py
并将settings.py中“ROOT_URLCONF”等路径均再加上上一级目录,否则会报错。
test_project
├── LICENSE
├── main.py
├── manage.py # 将manage.py移动到Django项目根目录的上一级目录
└── test_django # Django项目根目录
├── db.sqlite3
└── test_django
├── apps
│ └── app1
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── utilis
│ └── test.py
├── asgi.py
├── __init__.py
├── settings.py # settings.py中“ROOT_URLCONF”等路径均需再加上上一级目录,否则会报错
├── urls.py
└── wsgi.py
models.py的内容仍为:
from test_django.test_django.utilis.test import TestClass
TestClass().test()
此时再执行
/test_project/manage.py runserver
则不再报错。
3. 结论:
Django 导包的绝对路径应该从manage.py所在目录的子目录开始。
在PyCharm中,为避免导包报错出现波浪线,在manage.py所在的目录上,右键单击,然后“Mark Directory as”为“Sources Root”。之后当前程序所在目录颜色会变化,表示已经完成标记。就可以消除波浪线了。
如果波浪线仍存在并提示“在 '__init__.py' 中找不到引用 'xxx' (Cannot find reference 'xxx' in __init__.py)”,删除manage.py所在目录中的__init__.py即可。
参考链接:
1. In Django absolute imports works in respect to the manage.py.
https://stackoverflow.com/questions/62588858/modulenotfounderror-no-module-named-ten-project-ten-app-models
2.
https://stackoverflow.com/questions/62588858/modulenotfounderror-no-module-named-ten-project-ten-app-models/62589294#62589294
内容摘录:
Lets say there is a system wide module named foobar
, and a django app named foobar
too, and I cannot edit none of them, because they are external projects.
Now, I would like to use the system foobar module, not the application, but it does not work:
$ python --version
Python 2.7.3
$ ./manage.py --version
1.3.1
$ ./manage.py shell
>>> import foobar
>>> print (foobar)
<module 'foobar' from '/path/to/myproject/foobar/__init__.pyc'>
How can I do ? I suppose I would have <module 'foobar' from '/usr/lib/python2.7/dist-packages/foobar/__init__.pyc'>
instead.
#
Python looks for packages at the locations defined in the environment variable PYTHONPATH
. You can modify this from within Python quite easily:
https://docs.python.org/2/install/index.html#modifying-python-s-search-path
You'll want to change sys.path
so that the directory containing the system foobar module is before the application directory.
Assuming your sys.path
looks like:
>>> import sys
>>> sys.path
['', '/usr/local/lib/python2.7/site-packages']
and foobar
is in both the current working directory (indicated with ''
), and the system site-packages
directory, you want to get the system foobar
closer to the front of the list:
>>> sys.path.insert(0, '/usr/local/lib/python2.7/site-packages/foobar')
>>> sys.path
['/usr/local/lib/python2.7/site-packages/foobar',
'', '/usr/local/lib/python2.7/site-packages']
Note that this change to the path will only apply for the running process/instance of Python (in this case, the interactive session), and later processes will start up with the original path.
Solution without modifying the path
If you can't or don't want to modify sys.path, you can use the imp
module to manually import a module:
import imp
name = 'foobar'
path = '/usr/local/lib/python2.7/site-packages/foobar'
fp, pathname, description = imp.find_module(name, path)
try:
imp.load_module(name, fp, pathname, description)
finally:
if fp:
fp.close()
Solution with modifying the path only temporarily
This solution doesn't need to know where the system foobar
package is ahead of time, only that you want the one that isn't in the current directory:
import imp
import sys
cwd = sys.path.pop(0)
import foobar
sys.path.insert(0, cwd)
Try this, no hard code path
import os
import sys
PROJECT_DIR = os.path.dirname(__file__)
syspath = sys.path
syspath_set = set(syspath)
syspath_set.discard(PROJECT_DIR)
sys.path = list(syspath_set)
sys.path.append(PROJECT_DIR)
import foobar
sys.path = syspath