예제 #1
0
    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QLocale(QSettings().value('locale/userLocale'))
        locale_path = os.path.join(self.plugin_dir, 'i18n')
        self.translator = QTranslator()
        self.translator.load(locale, 'QFieldSync', '_', locale_path)

        QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&QFieldSync')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'QFieldSync')
        self.toolbar.setObjectName(u'QFieldSync')

        # instance of the QgsOfflineEditing
        self.offline_editing = QgsOfflineEditing()
        self.preferences = Preferences()

        QgsProject.instance().readProject.connect(self.update_button_enabled_status)

        # store warnings from last run
        self.last_action_warnings = []
예제 #2
0
    def test_updateFeatures(self):
        ol, offline_layer = self._testInit()
        # Edit feature 2
        feat2 = self._getFeatureByAttribute(offline_layer, 'name', "'name 2'")
        self.assertTrue(offline_layer.startEditing())
        self.assertTrue(offline_layer.changeAttributeValue(feat2.id(), offline_layer.fields().lookupField('name'), 'name 2 edited'))
        self.assertTrue(offline_layer.changeGeometry(feat2.id(), QgsGeometry.fromPointXY(QgsPointXY(33.0, 60.0))))
        self.assertTrue(offline_layer.commitChanges())
        feat2 = self._getFeatureByAttribute(offline_layer, 'name', "'name 2 edited'")
        self.assertTrue(ol.isOfflineProject())
        # Sync
        ol.synchronize()
        sleep(2)
        # Does anybody know why the sleep is needed? Is that a threaded WFS consequence?
        online_layer = list(self.registry.mapLayers().values())[0]
        self.assertTrue(online_layer.isValid())
        self.assertFalse(online_layer.name().find('(offline)') > -1)
        self.assertEqual(len([f for f in online_layer.getFeatures()]), len(TEST_FEATURES))
        # Check that data have changed in the backend (raise exception if not found)
        feat2 = self._getFeatureByAttribute(self._getLayer('test_point'), 'name', "'name 2 edited'")
        feat2 = self._getFeatureByAttribute(online_layer, 'name', "'name 2 edited'")
        self.assertEqual(feat2.geometry().asPoint().toString(), QgsPointXY(33.0, 60.0).toString())
        # Check that all other features have not changed
        layer = self._getLayer('test_point')
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[1 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[3 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[4 - 1]))

        # Test for regression on double sync (it was a SEGFAULT)
        # goes offline
        ol = QgsOfflineEditing()
        offline_layer = list(self.registry.mapLayers().values())[0]
        # Edit feature 2
        feat2 = self._getFeatureByAttribute(offline_layer, 'name', "'name 2 edited'")
        self.assertTrue(offline_layer.startEditing())
        self.assertTrue(offline_layer.changeAttributeValue(feat2.id(), offline_layer.fields().lookupField('name'), 'name 2'))
        self.assertTrue(offline_layer.changeGeometry(feat2.id(), QgsGeometry.fromPointXY(TEST_FEATURES[1][2])))
        # Edit feat 4
        feat4 = self._getFeatureByAttribute(offline_layer, 'name', "'name 4'")
        self.assertTrue(offline_layer.changeAttributeValue(feat4.id(), offline_layer.fields().lookupField('name'), 'name 4 edited'))
        self.assertTrue(offline_layer.commitChanges())
        # Sync
        ol.synchronize()
        # Does anybody knows why the sleep is needed? Is that a threaded WFS consequence?
        sleep(1)
        online_layer = list(self.registry.mapLayers().values())[0]
        layer = self._getLayer('test_point')
        # Check that data have changed in the backend (raise exception if not found)
        feat4 = self._getFeatureByAttribute(layer, 'name', "'name 4 edited'")
        feat4 = self._getFeatureByAttribute(online_layer, 'name', "'name 4 edited'")
        feat2 = self._getFeatureByAttribute(layer, 'name', "'name 2'")
        feat2 = self._getFeatureByAttribute(online_layer, 'name', "'name 2'")
        # Check that all other features have not changed
        layer = self._getLayer('test_point')
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[1 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[2 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[3 - 1]))
예제 #3
0
    def test_updateFeatures(self):
        ol, offline_layer = self._testInit()
        # Edit feature 2
        feat2 = self._getFeatureByAttribute(offline_layer, 'name', "'name 2'")
        self.assertTrue(offline_layer.startEditing())
        self.assertTrue(offline_layer.changeAttributeValue(feat2.id(), offline_layer.fields().lookupField('name'), 'name 2 edited'))
        self.assertTrue(offline_layer.changeGeometry(feat2.id(), QgsGeometry.fromPointXY(QgsPointXY(33.0, 60.0))))
        self.assertTrue(offline_layer.commitChanges())
        feat2 = self._getFeatureByAttribute(offline_layer, 'name', "'name 2 edited'")
        self.assertTrue(ol.isOfflineProject())
        # Sync
        ol.synchronize()
        sleep(2)
        # Does anybody know why the sleep is needed? Is that a threaded WFS consequence?
        online_layer = list(self.registry.mapLayers().values())[0]
        self.assertTrue(online_layer.isValid())
        self.assertFalse(online_layer.name().find('(offline)') > -1)
        self.assertEqual(len([f for f in online_layer.getFeatures()]), len(TEST_FEATURES))
        # Check that data have changed in the backend (raise exception if not found)
        feat2 = self._getFeatureByAttribute(self._getLayer('test_point'), 'name', "'name 2 edited'")
        feat2 = self._getFeatureByAttribute(online_layer, 'name', "'name 2 edited'")
        self.assertEqual(feat2.geometry().asPoint().toString(), QgsPointXY(33.0, 60.0).toString())
        # Check that all other features have not changed
        layer = self._getLayer('test_point')
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[1 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[3 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[4 - 1]))

        # Test for regression on double sync (it was a SEGFAULT)
        # goes offline
        ol = QgsOfflineEditing()
        offline_layer = list(self.registry.mapLayers().values())[0]
        # Edit feature 2
        feat2 = self._getFeatureByAttribute(offline_layer, 'name', "'name 2 edited'")
        self.assertTrue(offline_layer.startEditing())
        self.assertTrue(offline_layer.changeAttributeValue(feat2.id(), offline_layer.fields().lookupField('name'), 'name 2'))
        self.assertTrue(offline_layer.changeGeometry(feat2.id(), QgsGeometry.fromPointXY(TEST_FEATURES[1][2])))
        # Edit feat 4
        feat4 = self._getFeatureByAttribute(offline_layer, 'name', "'name 4'")
        self.assertTrue(offline_layer.changeAttributeValue(feat4.id(), offline_layer.fields().lookupField('name'), 'name 4 edited'))
        self.assertTrue(offline_layer.commitChanges())
        # Sync
        ol.synchronize()
        # Does anybody knows why the sleep is needed? Is that a threaded WFS consequence?
        sleep(1)
        online_layer = list(self.registry.mapLayers().values())[0]
        layer = self._getLayer('test_point')
        # Check that data have changed in the backend (raise exception if not found)
        feat4 = self._getFeatureByAttribute(layer, 'name', "'name 4 edited'")
        feat4 = self._getFeatureByAttribute(online_layer, 'name', "'name 4 edited'")
        feat2 = self._getFeatureByAttribute(layer, 'name', "'name 2'")
        feat2 = self._getFeatureByAttribute(online_layer, 'name', "'name 2'")
        # Check that all other features have not changed
        layer = self._getLayer('test_point')
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[1 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[2 - 1]))
        self.assertTrue(self._compareFeature(layer, TEST_FEATURES[3 - 1]))
예제 #4
0
    def test_primary_keys_custom_property(self):
        source_folder = tempfile.mkdtemp()
        export_folder = tempfile.mkdtemp()
        shutil.copytree(os.path.join(test_data_folder(), 'pk_project'),
                        os.path.join(source_folder, 'pk_project'))

        project = self.load_project(
            os.path.join(source_folder, 'pk_project', 'project.qgs'))
        extent = QgsRectangle()
        offline_editing = QgsOfflineEditing()
        offline_converter = OfflineConverter(project, export_folder, extent,
                                             offline_editing)
        offline_converter.convert()

        exported_project = self.load_project(
            os.path.join(export_folder, 'project_qfield.qgs'))
        if Qgis.QGIS_VERSION_INT < 31601:
            layer = exported_project.mapLayersByName('somedata (offline)')[0]
        else:
            layer = exported_project.mapLayersByName('somedata')[0]
        self.assertEqual(
            layer.customProperty('QFieldSync/sourceDataPrimaryKeys'), 'pk')

        shutil.rmtree(export_folder)
        shutil.rmtree(source_folder)
예제 #5
0
    def test_copy(self):
        source_folder = tempfile.mkdtemp()
        export_folder = tempfile.mkdtemp()
        shutil.copytree(os.path.join(test_data_folder(), 'simple_project'),
                        os.path.join(source_folder, 'simple_project'))

        project = self.load_project(
            os.path.join(source_folder, 'simple_project', 'project.qgs'))
        extent = QgsRectangle()
        offline_editing = QgsOfflineEditing()
        offline_converter = OfflineConverter(project, export_folder, extent,
                                             offline_editing)
        offline_converter.convert()

        files = os.listdir(export_folder)

        self.assertIn('project_qfield.qgs', files)
        self.assertIn('france_parts_shape.shp', files)
        self.assertIn('france_parts_shape.dbf', files)
        self.assertIn('curved_polys.gpkg', files)
        self.assertIn('spatialite.db', files)

        dcim_folder = os.path.join(export_folder, "DCIM")
        dcim_files = os.listdir(dcim_folder)
        self.assertIn('qfield-photo_1.jpg', dcim_files)
        self.assertIn('qfield-photo_2.jpg', dcim_files)
        self.assertIn('qfield-photo_3.jpg', dcim_files)
        dcim_subfolder = os.path.join(dcim_folder, "subfolder")
        dcim_subfiles = os.listdir(dcim_subfolder)
        self.assertIn('qfield-photo_sub_1.jpg', dcim_subfiles)
        self.assertIn('qfield-photo_sub_2.jpg', dcim_subfiles)
        self.assertIn('qfield-photo_sub_3.jpg', dcim_subfiles)

        shutil.rmtree(export_folder)
        shutil.rmtree(source_folder)
예제 #6
0
    def test_open_dialog(self):
        preferences = Preferences()
        offline_editing = QgsOfflineEditing()

        dlg = PackageDialog(self.iface, preferences, QgsProject.instance(),
                            offline_editing)
        dlg.show()
예제 #7
0
 def _testInit(self):
     """
     Preliminary checks for each test
     """
     # goes offline
     ol = QgsOfflineEditing()
     online_layer = list(self.registry.mapLayers().values())[0]
     self.assertTrue(online_layer.isSpatial())
     # Check we have features
     self.assertEqual(len([f for f in online_layer.getFeatures()]), len(TEST_FEATURES))
     self.assertTrue(ol.convertToOfflineProject(self.temp_path, 'offlineDbFile.sqlite', [online_layer.id()]))
     offline_layer = list(self.registry.mapLayers().values())[0]
     self.assertTrue(offline_layer.isSpatial())
     self.assertTrue(offline_layer.isValid())
     self.assertTrue(offline_layer.name().find('(offline)') > -1)
     self.assertEqual(len([f for f in offline_layer.getFeatures()]), len(TEST_FEATURES))
     return ol, offline_layer
예제 #8
0
 def _testInit(self):
     """
     Preliminary checks for each test
     """
     # goes offline
     ol = QgsOfflineEditing()
     online_layer = list(self.registry.mapLayers().values())[0]
     self.assertTrue(online_layer.isSpatial())
     # Check we have features
     self.assertEqual(len([f for f in online_layer.getFeatures()]), len(TEST_FEATURES))
     self.assertTrue(ol.convertToOfflineProject(self.temp_path, 'offlineDbFile.sqlite', [online_layer.id()]))
     offline_layer = list(self.registry.mapLayers().values())[0]
     self.assertTrue(offline_layer.isSpatial())
     self.assertTrue(offline_layer.isValid())
     self.assertTrue(offline_layer.name().find('(offline)') > -1)
     self.assertEqual(len([f for f in offline_layer.getFeatures()]), len(TEST_FEATURES))
     return ol, offline_layer
예제 #9
0
class QFieldSync(object):
    """QGIS Plugin Implementation."""
    QFIELD_SCOPE = "QFieldSync"

    push_dlg = None

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale_str = QSettings().value('locale/userLocale')
        if isinstance(locale_str, str):
            locale = QLocale(locale_str)
        else:
            locale = QLocale()

        locale_path = os.path.join(self.plugin_dir, 'i18n')
        self.translator = QTranslator()
        self.translator.load(locale, 'qfieldsync', '_', locale_path)

        QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr('&QFieldSync')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar('QFieldSync')
        self.toolbar.setObjectName('QFieldSync')

        # instance of the map config widget factory, shown in layer properties
        self.mapLayerConfigWidgetFactory = MapLayerConfigWidgetFactory(
            'QField',
            QIcon(
                os.path.join(os.path.dirname(__file__),
                             'resources/qfield_logo.svg')))

        # instance of the QgsOfflineEditing
        self.offline_editing = QgsOfflineEditing()
        self.preferences = Preferences()

        QgsProject.instance().readProject.connect(
            self.update_button_enabled_status)

        # store warnings from last run
        self.last_action_warnings = []

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('QFieldSync', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(self.menu, action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        self.push_action = self.add_action(QIcon(
            os.path.join(os.path.dirname(__file__), 'resources/package.svg')),
                                           text=self.tr('Package for QField'),
                                           callback=self.show_package_dialog,
                                           parent=self.iface.mainWindow())

        self.add_action(QIcon(
            os.path.join(os.path.dirname(__file__),
                         'resources/synchronize.svg')),
                        text=self.tr('Synchronize from QField'),
                        callback=self.show_synchronize_dialog,
                        parent=self.iface.mainWindow())

        self.add_action(
            QIcon(
                os.path.join(os.path.dirname(__file__),
                             './resources/project_properties.svg')),
            text=self.tr('Configure Current Project'),
            callback=self.show_project_configuration_dialog,
            parent=self.iface.mainWindow(),
        )

        actions = self.iface.pluginMenu().actions()
        for action in actions:
            if action.text() == self.menu:
                action.menu().addSeparator()

        self.add_action(QgsApplication.getThemeIcon("/mActionOptions.svg"),
                        text=self.tr('Preferences'),
                        callback=self.show_preferences_dialog,
                        parent=self.iface.mainWindow(),
                        add_to_toolbar=False)

        self.iface.registerMapLayerConfigWidgetFactory(
            self.mapLayerConfigWidgetFactory)

        if Qgis.QGIS_VERSION_INT >= 31500:
            self.project_properties_factory = QFieldSyncProjectPropertiesFactory(
            )
            self.project_properties_factory.setTitle('QField')
            self.iface.registerProjectPropertiesWidgetFactory(
                self.project_properties_factory)
        self.options_factory = QFieldSyncOptionsFactory()
        self.options_factory.setTitle(self.tr('QField'))
        self.iface.registerOptionsWidgetFactory(self.options_factory)

        self.update_button_enabled_status()

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(self.tr('&QFieldSync'), action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar

        self.iface.unregisterMapLayerConfigWidgetFactory(
            self.mapLayerConfigWidgetFactory)

        if Qgis.QGIS_VERSION_INT >= 31500:
            self.iface.unregisterProjectPropertiesWidgetFactory(
                self.project_properties_factory)
        self.iface.unregisterOptionsWidgetFactory(self.options_factory)

    def show_preferences_dialog(self):
        self.iface.showOptionsDialog(self.iface.mainWindow(),
                                     currentPage='QFieldPreferences')

    def show_synchronize_dialog(self):
        """
        Synchronize from QField
        """
        dlg = SynchronizeDialog(self.iface, self.offline_editing,
                                self.iface.mainWindow())
        dlg.exec_()

    def show_package_dialog(self):
        """
        Push to QField
        """
        self.push_dlg = PackageDialog(self.iface, QgsProject.instance(),
                                      self.offline_editing,
                                      self.iface.mainWindow())
        self.push_dlg.setAttribute(Qt.WA_DeleteOnClose)
        self.push_dlg.setWindowFlags(self.push_dlg.windowFlags() | Qt.Tool)
        self.push_dlg.show()

        self.push_dlg.finished.connect(self.push_dialog_finished)
        self.update_button_enabled_status()

    def show_project_configuration_dialog(self):
        """
        Show the project configuration dialog.
        """
        if Qgis.QGIS_VERSION_INT >= 31500:
            self.iface.showProjectPropertiesDialog('QField')
        else:
            dlg = ProjectConfigurationDialog(self.iface.mainWindow())
            dlg.exec_()

    def action_start(self):
        self.clear_last_action_warnings()

    def clear_last_action_warnings(self):
        self.last_action_warnings = []

    def push_dialog_finished(self):
        """
        When the push dialog is closed, make sure it's no longer
        enabled before entering update_button_enabled_status()
        """
        try:
            self.push_dlg.setEnabled(False)
        except RuntimeError:
            pass
        self.update_button_enabled_status()

    def update_button_enabled_status(self):
        """
        Will update the plugin buttons according to open dialog and project properties.
        """
        try:
            dialog_is_enabled = self.push_dlg and self.push_dlg.isEnabled()
        except RuntimeError:
            dialog_is_enabled = False

        if self.offline_editing.isOfflineProject() or dialog_is_enabled:
            self.push_action.setEnabled(False)
        else:
            self.push_action.setEnabled(True)
예제 #10
0
    def test_open_dialog(self):
        offline_editing = QgsOfflineEditing()

        dlg = SynchronizeDialog(self.iface, offline_editing)
        dlg.show()
예제 #11
0
class QFieldSync(object):
    """QGIS Plugin Implementation."""
    QFIELD_SCOPE = "QFieldSync"

    push_dlg = None

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QLocale(QSettings().value('locale/userLocale'))
        locale_path = os.path.join(self.plugin_dir, 'i18n')
        self.translator = QTranslator()
        self.translator.load(locale, 'QFieldSync', '_', locale_path)

        QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&QFieldSync')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'QFieldSync')
        self.toolbar.setObjectName(u'QFieldSync')

        # instance of the QgsOfflineEditing
        self.offline_editing = QgsOfflineEditing()
        self.preferences = Preferences()

        QgsProject.instance().readProject.connect(self.update_button_enabled_status)

        # store warnings from last run
        self.last_action_warnings = []

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('QFieldSync', message)

    def add_action(
            self,
            icon_path,
            text,
            callback,
            enabled_flag=True,
            add_to_menu=True,
            add_to_toolbar=True,
            status_tip=None,
            whats_this=None,
            parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        self.push_action = self.add_action(
            ':/plugins/qfieldsync/refresh.png',
            text=self.tr(u'Package for QField'),
            callback=self.show_package_dialog,
            parent=self.iface.mainWindow())

        self.add_action(
            ':/plugins/qfieldsync/refresh-reverse.png',
            text=self.tr(u'Synchronize from QField'),
            callback=self.show_synchronize_dialog,
            parent=self.iface.mainWindow())

        self.add_action(
            ':/plugins/qfieldsync/icon.png',
            text=self.tr(u'Project Configuration'),
            callback=self.show_project_configuration_dialog,
            parent=self.iface.mainWindow(),
            add_to_toolbar=False
        )

        self.add_action(
            ':/plugins/qfieldsync/icon.png',
            text=self.tr(u'Preferences'),
            callback=self.show_preferences_dialog,
            parent=self.iface.mainWindow(),
            add_to_toolbar=False)

        self.update_button_enabled_status()

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr(u'&QFieldSync'),
                action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar

    def show_preferences_dialog(self):
        dlg = PreferencesDialog(self.preferences, self.iface.mainWindow())
        dlg.exec_()

    def show_synchronize_dialog(self):
        """
        Synchronize from QField
        """
        dlg = SynchronizeDialog(self.iface, self.preferences, self.offline_editing, self.iface.mainWindow())
        dlg.exec_()

    def show_package_dialog(self):
        """
        Push to QField
        """
        self.push_dlg = PackageDialog(self.iface, self.preferences, QgsProject.instance(), self.offline_editing,
                                      self.iface.mainWindow())
        self.push_dlg.setAttribute(Qt.WA_DeleteOnClose)
        self.push_dlg.setWindowFlags(self.push_dlg.windowFlags() | Qt.Tool)
        self.push_dlg.show()

        self.push_dlg.finished.connect(self.push_dialog_finished)
        self.update_button_enabled_status()

    def show_project_configuration_dialog(self):
        """
        Show the project configuration dialog.
        """
        dlg = ProjectConfigurationDialog(self.iface, self.iface.mainWindow())
        dlg.exec_()

    def action_start(self):
        self.clear_last_action_warnings()

    def clear_last_action_warnings(self):
        self.last_action_warnings = []

    def push_dialog_finished(self):
        """
        When the push dialog is closed, make sure it's no longer
        enabled before entering update_button_enabled_status()
        """
        try:
            self.push_dlg.setEnabled(False)
        except RuntimeError:
            pass
        self.update_button_enabled_status()

    def update_button_enabled_status(self):
        """
        Will update the plugin buttons according to open dialog and project properties.
        """
        try:
            dialog_is_enabled = self.push_dlg and self.push_dlg.isEnabled()
        except RuntimeError:
            dialog_is_enabled = False

        if self.offline_editing.isOfflineProject() or dialog_is_enabled:
            self.push_action.setEnabled(False)
        else:
            self.push_action.setEnabled(True)
예제 #12
0
    def test_open_dialog(self):
        preferences = Preferences()
        offline_editing = QgsOfflineEditing()

        dlg = ProjectConfigurationDialog(self.iface)
        dlg.show()
예제 #13
0
    def test_open_dialog(self):
        offline_editing = QgsOfflineEditing()

        dlg = ProjectConfigurationDialog()
        dlg.show()
예제 #14
0
def isNotTemp(layer):
    return not layer.isTemporary()


# QgsApplication.setPrefixPath("/path/to/qgis/installation", True)

qgs = QgsApplication([], False)
qgs.initQgis()

project = QgsProject.instance()

project.read(SOURCE_PROJECT_PATH)

project.write(OFFLINE_PROJECT_PATH)

layers = [l for l in project.mapLayers().values()]

vectorLayers = filter(isVector, layers)

notTempLayers = filter(isNotTemp, vectorLayers)

ids = [l.id() for l in notTempLayers]

# print(ids)

offlineEditing = QgsOfflineEditing()

offlineEditing.convertToOfflineProject(OFFLINE_DATA_PATH, OFFLINE_DB_FILE, ids,
                                       False, QgsOfflineEditing.GPKG)

qgs.exitQgis()
예제 #15
0
    def convert_to_offline(self, db, surveyor_expression_dict, export_dir):
        sys.path.append(PLUGINS_DIR)
        from qfieldsync.core.layer import LayerSource, SyncAction
        from qfieldsync.core.offline_converter import OfflineConverter
        from qfieldsync.core.project import ProjectConfiguration

        project = QgsProject.instance()
        extent = QgsRectangle()
        offline_editing = QgsOfflineEditing()

        # Configure project
        project_configuration = ProjectConfiguration(project)
        project_configuration.create_base_map = False
        project_configuration.offline_copy_only_aoi = False
        project_configuration.use_layer_selection = True

        # Layer config
        layer_sync_action = LayerConfig.get_field_data_capture_layer_config(
            db.names)

        total_projects = len(surveyor_expression_dict)
        current_progress = 0

        for surveyor, layer_config in surveyor_expression_dict.items():
            export_folder = os.path.join(export_dir, surveyor)

            # Get layers (cannot be done out of this for loop because the project is closed and layers are deleted)
            layers = {
                layer_name: None
                for layer_name, _ in layer_sync_action.items()
            }
            self.app.core.get_layers(db, layers, True)
            if not layers:
                return False, QCoreApplication.translate(
                    "FieldDataCapture",
                    "At least one layer could not be found.")

            # Configure layers
            for layer_name, layer in layers.items():
                layer_source = LayerSource(layer)
                layer_source.action = layer_sync_action[layer_name]
                if layer_name in layer_config:
                    layer_source.select_expression = layer_config[layer_name]
                layer_source.apply()

            offline_converter = OfflineConverter(project, export_folder,
                                                 extent, offline_editing)
            offline_converter.convert()
            offline_editing.layerProgressUpdated.disconnect(
                offline_converter.on_offline_editing_next_layer)
            offline_editing.progressModeSet.disconnect(
                offline_converter.on_offline_editing_max_changed)
            offline_editing.progressUpdated.disconnect(
                offline_converter.offline_editing_task_progress)

            current_progress += 1
            self.total_progress_updated.emit(
                int(100 * current_progress / total_projects))

        return True, QCoreApplication.translate(
            "FieldDataCapture",
            "{count} offline projects have been successfully created in <a href='file:///{normalized_path}'>{path}</a>!"
        ).format(count=total_projects,
                 normalized_path=normalize_local_url(export_dir),
                 path=export_dir)