我在基于 Linux 的系统上使用 PYQt 5; 目前,我需要让用户插入 USB 笔驱动器并将一些文件保存到其中,或从中打开一些文件(即软件升级或配置)。 显示一个文件对话框,我可以在其中保存或选择文件似乎是一个简单的任务。
但另外我希望用户仅浏览 USB 笔驱动器(即 /mnt/myUSB/..) 前进和后退,但不要返回系统目录并执行一些可能有害的操作。
有人这样做过吗?
执行此操作会在 /mnt 中打开对话框,但不会阻止用户浏览回 /usr/local/bin 或类似的“危险”路径。
dialog = QtWidgets.QFileDialog(self)
dialog.setDirectory('/mnt')
dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
dialog.exec_()
可以通过使用 QFileDialog 的一些已知功能和一些未记录的方面来实现。
为此有两个主要要求:
setProxyModel()
和实现 filterAcceptsRow()
的 QSortFilterProxyModel 子类来完成:
class LimitedAccessProxy(QSortFilterProxyModel):
def __init__(self, basePath):
super().__init__()
self.base = basePath
def lessThan(self, left, right):
# required to keep the original order
return left.row() < right.row()
def filterAcceptsRow(self, row, parent):
if row < 0:
return super().filterAcceptsRow(row, parent)
path = self.sourceModel().filePath(self.sourceModel().index(row, 0, parent))
return path.startswith(self.base) or self.base.startswith(path)
这将确保仅显示“base”目录内的路径。
self.base.startswith(path)
部分还确保所有父目录都可供视图使用,这是访问基本路径本身所必需的:如果任何父目录被过滤掉,则永远不会到达基本路径。
使用方法如下:
dialog = QFileDialog()
dialog.setFileMode(QFileDialog.AnyFile)
dialog.setOption(QFileDialog.DontUseNativeDialog)
dialog.setDirectory('/tmp')
dialog.setProxyModel(LimitedAccessProxy('/tmp'))
dialog.open()
现在,这并不能完全阻止用户在目录结构中“向上”移动。在您的具体情况下,这可能不是一个大问题,因为
/tmp
的父目录是根目录,它不向标准用户提供写访问权限。 但是,如果目标目录是另一个
可写目录的子目录,这可能是一个问题。
为了限制该访问,我们可以在当前目录发生更改时进行检查,并防止用户永远导航父目录。为此,我们:
当前路径为“基础”路径时禁用“向上”按钮;
每当用户能够浏览不允许的目录时,就在历史记录中“后退”(并禁用“前进”按钮);accept()
class RestrictedFileDialog(QFileDialog):
def __init__(self, path, parent=None):
super().__init__(parent)
self.setFileMode(QFileDialog.AnyFile)
self.setOption(QFileDialog.DontUseNativeDialog)
self.setDirectory(path)
if path != QDir.rootPath():
self.path = path.rstrip(QDir.separator())
self.proxy = LimitedAccessProxy(self.path)
self.setProxyModel(self.proxy)
self.lookInCombo = self.findChild(QComboBox, 'lookInCombo')
self.backButton = self.findChild(QToolButton, 'backButton')
self.forwardButton = self.findChild(QToolButton, 'forwardButton')
self.toParentButton = self.findChild(QToolButton, 'toParentButton')
self.directoryEntered.connect(self._checkDirectory)
self.backButton.clicked.connect(self._historyFix)
self.forwardButton.clicked.connect(self._historyFix)
self.lookInCombo.model().rowsInserted.connect(self._checkLookInCombo)
def _checkLookInCombo(self):
model = self.lookInCombo.model()
for r in range(model.rowCount()):
index = model.index(r, 0)
url = index.data(Qt.UserRole + 1)
if isinstance(url, QUrl):
path = url.toLocalFile()
if not path or not path.startswith(self.path):
model.itemFromIndex(index).setEnabled(False)
def _historyFix(self):
self._checkDirectory(self.directory().absolutePath())
def _checkDirectory(self, path):
isSub = path.startswith(self.path + QDir.separator())
self.toParentButton.setEnabled(isSub)
if not isSub and path != self.path:
self.backButton.click()
self.forwardButton.setEnabled(False)
def accept(self):
files = self.selectedFiles()
if files:
info = QFileInfo(files[0])
if info.absoluteFilePath().startswith(self.path):
super().accept()
app = QApplication(sys.argv)
dialog = RestrictedFileDialog('/tmp/coldcase')
dialog.open()
sys.exit(app.exec_())