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)
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)
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 = []
def test_open_dialog(self): preferences = Preferences() offline_editing = QgsOfflineEditing() dlg = PackageDialog(self.iface, preferences, QgsProject.instance(), offline_editing) dlg.show()
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]))
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
def test_open_dialog(self): offline_editing = QgsOfflineEditing() dlg = SynchronizeDialog(self.iface, offline_editing) dlg.show()
def test_open_dialog(self): preferences = Preferences() offline_editing = QgsOfflineEditing() dlg = ProjectConfigurationDialog(self.iface) dlg.show()
def test_open_dialog(self): offline_editing = QgsOfflineEditing() dlg = ProjectConfigurationDialog() dlg.show()
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()
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)