0

We are looking at deploying a PyQt application on an Azure server, and the application works well enough, albeit a little slow to respond to user actions.

We have a problem, however, and that is that the QFileDialog allows pretty much any explore action: copy a file from the virtual machine to the user's local drive, open a file within 'Program Files (x86)' in Notepad, etc.

Approaches already considered:

  1. As the python application has to have read and write permissions to run under 'Program Files (x86)', we can't use file permissions to control access.

  2. We can turn the Python into an inscrutable .exe, but this could still be copied using the context menus in the file dialog.

  3. We could use the file filters and then hide them, so you can only see (and mess with) the relevant files, but the user could still copy entire directories.

The only thing we can think of is to create our own file dialog from scratch, but that's very tedious. Are there any 'out of the box' solutions?

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Mike Sadler
  • 1,750
  • 1
  • 20
  • 37
  • First guess: Create your own Class that is derived from QFileDialog and override the right click event to do nothing. – Mailerdaimon Mar 23 '16 at 12:08
  • 1
    Or use QWidgets: `setContextMenuPolicy` and set the policy to `Qt::NoContextMenu` – Mailerdaimon Mar 23 '16 at 12:12
  • I presumed that would be impractical, as I'm using the PyQt bindings. Would I have to go to the C++ Qt code and edit that? My C++ is perfectly good enough, but I'd have to rebuild and rewrap everything, wouldn't I? – Mike Sadler Mar 23 '16 at 12:13
  • Sorry - the last comment coincided! The setContextMenuPolicy sounds promising - but presumably Ctrl+C & Ctrl+V would have to be disabled as well. Is there a similar command? – Mike Sadler Mar 23 '16 at 12:14
  • Regarding PyQt: No problems there. Just search the web for tutorial. It works well in Python. Regarding Ctrl+V: This is a new question, so please open up a new one so that answers don't mix. I would generally advise you to carefully read the documentation of QFileDialog including the `List of all members, including inherited members` page. – Mailerdaimon Mar 23 '16 at 12:21
  • You're right - the original question was overly restrictive. I've edited the title to better reflect the contents of the question. – Mike Sadler Mar 23 '16 at 12:33
  • Unfortunately, setContextMenuPolicy doesn't appear to affect the file dialog: dialog = QtGui.QFileDialog(parent=self, caption='Load Session') dialog.setContextMenuPolicy(QtCore.Qt.NoContextMenu) still enables you to rename, delete and so on via a context menu. – Mike Sadler Mar 23 '16 at 12:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107145/discussion-between-mike-sadler-and-mailerdaimon). – Mike Sadler Mar 23 '16 at 14:45

3 Answers3

3

The QFileDialog class already has this functionality:

    dialog = QtGui.QFileDialog()
    dialog.setOption(QtGui.QFileDialog.ReadOnly, True)
    dialog.exec_()

This only seems to work with Qt's built-in file-dialog, though. If you use the static functions to open a native file-dialog, the ReadOnly option seems to be ignored (I've only tested this on Linux, though).

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks, @ekhumoro (again!) - this appears to nullify the Ctrl+C & Ctrl+V as well as the context menu (on Windows, which I'm working on). – Mike Sadler Mar 24 '16 at 09:09
1

looking at exemple of qtreeview they show a file explorer so i think it's actually not a big task to implement a simple file system explorer. it's specialy easy thanks to QFileSystemModel http://doc.qt.io/qt-5/model-view-programming.html#using-models-and-views

bobzer
  • 99
  • 1
  • 7
  • Thanks, @bobzer. I think that I will keep this as the back-up plan, as ekhumoro's answer above appears to do the job. This approach would have two advantages for me: 1) I would be absolutely 100% confident that there is no "back door" left by the dialog, and 2) if I made the dialog myself, I could stop it even looking in folders I don't want it to (such as C:\Program Files (x86)). – Mike Sadler Mar 24 '16 at 09:11
  • 1
    @MikeSadler. I think Qt already has all your use cases covered. You can achieve (2) with [QFileDialog.setProxyModel](http://doc.qt.io/qt-4.8/qfiledialog.html#setProxyModel). – ekhumoro Mar 24 '16 at 17:54
1

Here's what I actually did, based on @ekhumoro's advice:

from PyQt4 import QtGui
import guidata
import re

class _DirectoryFilterProxyModel(QtGui.QSortFilterProxyModel):
    """ A basic filter to be applied to the file items to be displayed.
     Based on C++ example at:
      https://stackoverflow.com/questions/2101100/qfiledialog-filtering-folders. """

    def __init__(self, ignore_directories=[], *args, **kw):
        """ Constructor
        :param ignore_directories: A list of directories to exclude.  These
        can be regular expressions or straight names. """
        QtGui.QSortFilterProxyModel.__init__(self, *args, **kw)
        self.ignore_directories = ignore_directories

    def filterAcceptsRow(self, sourceRow, sourceParent):
        fileModel = self.sourceModel()
        index0 = fileModel.index(sourceRow, 0, sourceParent)

        if fileModel:
            if fileModel.isDir(index0):
                for directory in self.ignore_directories:
                    if re.match(directory, fileModel.fileName(index0)):
                        return False
                return True
            else:    # For files
                return True
        else:
            return False

And instantiated:

app = guidata.qapplication()
dialog = QtGui.QFileDialog()
proxyModel = _DirectoryFilterProxyModel(ignore_directories=["Program Files", "Program Files (x86)", "Windows"])
dialog.setProxyModel(proxyModel)
dialog.setOption(QtGui.QFileDialog.ReadOnly, True)
dialog.setOption(QtGui.QFileDialog.HideNameFilterDetails, True)
dialog.exec_()

My thanks to @serge_gubenko and @Gayan on the page qfiledialog - Filtering Folders? for providing the C++ implementation, from which I derived the above.

Community
  • 1
  • 1
Mike Sadler
  • 1,750
  • 1
  • 20
  • 37