def validate(self): """ :return: Return True if the source document directory exists, otherwise False. :rtype: bool """ source_doc_path = self.txtRootFolder.text() # Clear previous notifications self._notif_bar.clear() if not source_doc_path: msg = self.tr('Please set the root directory of source documents.') self._notif_bar.insertErrorNotification(msg) return False dir = QDir() if not dir.exists(source_doc_path): msg = self.tr( "'{0}' directory does not exist.".format(source_doc_path)) self._notif_bar.insertErrorNotification(msg) return False return True
def _check_path_exists(self, path, text_box): # Validates if the specified folder exists dir = QDir() if not dir.exists(path): msg = self.tr("'{0}' directory does not exist.".format(path)) self.notif_bar.insertErrorNotification(msg) # Highlight textbox control text_box.setStyleSheet(INVALIDATESTYLESHEET) timer = QTimer(self) # Sync interval with that of the notification bar timer.setInterval(self.notif_bar.interval) timer.setSingleShot(True) # Remove previous connected slots (if any) receivers = timer.receivers(QTimer.timeout) if receivers > 0: self._timer.timeout.disconnect() timer.start() timer.timeout.connect(lambda: self._restore_stylesheet( text_box) ) return False return True
def setup_logger(): from stdm.settings.registryconfig import debug_logging logger = logging.getLogger('stdm') logger.setLevel(logging.ERROR) # Create log directory if it does not exist log_folder = QDir() if not log_folder.exists(LOG_DIR): status = log_folder.mkpath(LOG_DIR) # Log directory could not be created if not status: raise IOError('Log directory for STDM could not be created.') # File handler for logging debug messages file_handler = TimedRotatingFileHandler(LOG_FILE_PATH, when='D', interval=1, backupCount=14) file_handler.setLevel(logging.DEBUG) # Create formatter and add it to the handler formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) # Add handler to the logger logger.addHandler(file_handler) # Enable/disable debugging. Defaults to ERROR level. lvl = debug_logging() if lvl: file_handler.setLevel(logging.DEBUG) else: file_handler.setLevel(logging.ERROR)
def uploadDocument(self, entity_source, doc_type, fileinfo): """ Upload document in central repository """ self._entity_source = entity_source self._doc_type = doc_type self.fileID = self.generateFileID() self.sourcePath = str(fileinfo.filePath()) profile_name = self.curr_profile.name root_dir = QDir(self.networkPath) doc_dir = QDir('{}/{}/{}/{}'.format( self.networkPath, profile_name.lower(), self._entity_source, self._doc_type.lower().replace(' ', '_'))) doc_path_str = '{}/{}/{}/{}'.format( self.networkPath, profile_name.lower(), self._entity_source, self._doc_type.lower().replace(' ', '_')).lower() if not doc_dir.exists(): res = root_dir.mkpath(doc_path_str) if res: root_doc_type_path = doc_path_str else: root_doc_type_path = self.networkPath else: root_doc_type_path = doc_path_str self.destinationPath = '{}/{}.{}'.format(root_doc_type_path, self.fileID, fileinfo.completeSuffix()) srcFile = open(self.sourcePath, 'rb') destinationFile = open(self.destinationPath, 'wb') # srcLen = self.sourceFile.bytesAvailable() totalRead = 0 while True: inbytes = srcFile.read(4096) if not inbytes: break destinationFile.write(inbytes) totalRead += len(inbytes) # Raise signal on each block written self.blockWritten.emit(totalRead) self.completed.emit(self.fileID) srcFile.close() destinationFile.close() return self.fileID
def _on_add_supporting_document(self): # Slot raised when the user select to add a supporting document if self.count == 0: return select = self.tr('Select') supporting_docs_str = 'Supporting Documents' title = '{0} {1} {2}'.format(select, self.current_document_type(), supporting_docs_str) filter_str = '{0} (*.jpg *.jpeg *.png *.bmp *.tiff *.svg *.pdf)'.format( supporting_docs_str) # Get last path for supporting documents last_path = last_document_path() if last_path is None: last_path = '/home' else: dir = QDir(last_path) if not dir.exists(): last_path = '/home' source_docs, _ = QFileDialog.getOpenFileNames(self, title, last_path, filter_str) doc_type_id = self._cbo_doc_type.itemData( self._cbo_doc_type.currentIndex()) parent_entity = self._entity_supporting_doc.parent_entity for doc in source_docs: self.source_document_manager.insertDocumentFromFile( doc, doc_type_id, parent_entity) # Set last path if len(source_docs) > 0: doc = source_docs[0] fi = QFileInfo(doc) dir_path = fi.absolutePath() set_last_document_path(dir_path)
def set_source_document_location(doc_path): """ Set the latest source directory of uploaded source documents. :param doc_path: Directory path or file path. The system will attempt to extract the directory path from the file name. """ doc_dir_path = "" # Check if it is a file or directory doc_dir = QDir(doc_path) if not doc_dir.exists(): doc_file_info = QFileInfo(doc_path) if doc_file_info.exists(): doc_dir_path = doc_file_info.dir().path() else: doc_dir_path = doc_path if len(doc_dir_path) > 0: reg_config = RegistryConfig() reg_config.write({LOCAL_SOURCE_DOC: doc_dir_path})
def accept(self): project_name = self.project_name_le.text() if project_name.endswith('.qgs'): project_name = project_name[:-4] if not project_name: QMessageBox.critical(self, self.tr("OQ-Consolidate: Error"), self.tr("Please specify the project name")) return outputDir = self.leOutputDir.text() if not outputDir: QMessageBox.critical( self, self.tr("OQ-Consolidate: Error"), self.tr("Please specify the output directory.")) return outputDir = os.path.join(outputDir, get_valid_filename(project_name)) # create main directory if not exists d = QDir(outputDir) if not d.exists(): if not d.mkpath("."): QMessageBox.critical( self, self.tr("OQ-Consolidate: Error"), self.tr("Can't create directory to store the project.")) return # create directory for layers if not exists if d.exists("layers"): res = QMessageBox.question( self, self.tr("Directory exists"), self.tr("Output directory already contains 'layers'" " subdirectory. Maybe this directory was used to" " consolidate another project. Continue?"), QMessageBox.Yes | QMessageBox.No) if res == QMessageBox.No: return else: if not d.mkdir("layers"): QMessageBox.critical( self, self.tr("OQ-Consolidate: Error"), self.tr("Can't create directory for layers.")) return # copy project file projectFile = QgsProject.instance().fileName() if projectFile: f = QFile(projectFile) newProjectFile = os.path.join(outputDir, '%s.qgs' % project_name) f.copy(newProjectFile) else: newProjectFile = os.path.join(outputDir, '%s.qgs' % project_name) f = QFileInfo(newProjectFile) p = QgsProject.instance() p.write(f) # start consolidate thread that does all real work self.workThread = consolidatethread.ConsolidateThread( self.iface, outputDir, newProjectFile, self.checkBoxZip.isChecked()) self.workThread.rangeChanged.connect(self.setProgressRange) self.workThread.updateProgress.connect(self.updateProgress) self.workThread.processFinished.connect(self.processFinished) self.workThread.processInterrupted.connect(self.processInterrupted) self.workThread.processError.connect(self.processError) self.workThread.exceptionOccurred.connect(self.exceptionOccurred) self.btnClose.setText(self.tr("Cancel")) self.btnOk.setEnabled(False) self.buttonBox.rejected.disconnect(self.reject) self.btnClose.clicked.connect(self.stopProcessing) self.workThread.start()
def referencing_column_value(self, field_values): """ Uploads documents based on the file name specified in the source column. :param field_values: Pair of field names and corresponding values i.e. {field1:value1, field2:value2, field3:value3...} :type field_values: dict :return: Ignore type since te source document manager will handle the supporting document uploads. :rtype: IgnoreType """ if self.source_document_manager is None or self.entity is None: return IgnoreType if self.document_type_id is None: msg = QApplication.translate( 'SourceDocumentTranslator', 'Document type has not been set for the source document ' 'translator.') raise RuntimeError(msg) if self.source_directory is None: msg = QApplication.translate( 'SourceDocumentTranslator', 'Source directory for {0} has not been set.'.format( self.document_type)) raise RuntimeError(msg) source_dir = QDir() if not source_dir.exists(self.source_directory): msg = QApplication.translate( 'SourceDocumentTranslator', 'Source directory for {0} does not exist.'.format( self.document_type)) raise IOError(msg) if len(field_values) == 0: return IgnoreType source_column = list(field_values.keys())[0] # Check if the source column is in the field_values if not source_column in field_values: return IgnoreType # Get file name doc_file_name = field_values.get(source_column) if not doc_file_name: return IgnoreType # Separate files docs = doc_file_name.split(';') # Create document container doc_container = QVBoxLayout() # Register container self.source_document_manager.registerContainer(doc_container, self.document_type_id) for d in docs: if not d: continue # Normalize slashes d_name = d.replace('\\', '/').strip() # Build absolute document path abs_doc_path = '{0}/{1}'.format(self.source_directory, d_name) if not QFile.exists(abs_doc_path): msg = QApplication.translate( 'SourceDocumentTranslator', 'Supporting document {0} does not exist.'.format( abs_doc_path)) raise IOError(msg) # Upload supporting document self.source_document_manager.insertDocumentFromFile( abs_doc_path, self.document_type_id, self.entity) # Move file to 'uploaded' directory # Documents are handles by the source document manager so we just # instruct the system to ignore the return value return IgnoreType
def run(self, *args, **kwargs): """ :param templatePath: The file path to the user-defined template. :param entityFieldName: The name of the column for the specified entity which must exist in the data source view or table. :param entityFieldValue: The value for filtering the records in the data source view or table. :param outputMode: Whether the output composition should be an image or PDF. :param filePath: The output file where the composition will be written to. Applies to single mode output generation. :param dataFields: List containing the field names whose values will be used to name the files. This is used in multiple mode configuration. :param fileExtension: The output file format. Used in multiple mode configuration. :param data_source: Name of the data source table or view whose row values will be used to name output files if the options has been specified by the user. """ templatePath = args[0] entityFieldName = args[1] entityFieldValue = args[2] outputMode = args[3] filePath = kwargs.get("filePath", None) dataFields = kwargs.get("dataFields", []) fileExtension = kwargs.get("fileExtension", "") data_source = kwargs.get("data_source", "") templateFile = QFile(templatePath) if not templateFile.open(QIODevice.ReadOnly): return False, QApplication.translate("DocumentGenerator", "Cannot read template file.") templateDoc = QDomDocument() if templateDoc.setContent(templateFile): composerDS = ComposerDataSource.create(templateDoc) spatialFieldsConfig = SpatialFieldsConfiguration.create(templateDoc) composerDS.setSpatialFieldsConfig(spatialFieldsConfig) #Check if data source exists and return if it doesn't if not self.data_source_exists(composerDS): msg = QApplication.translate("DocumentGenerator", "'{0}' data source does not exist in the database." "\nPlease contact your database " "administrator.".format(composerDS.name())) return False, msg #Set file name value formatter self._file_name_value_formatter = EntityValueFormatter( name=data_source ) #Register field names to be used for file naming self._file_name_value_formatter.register_columns(dataFields) #TODO: Need to automatically register custom configuration collections #Photo config collection ph_config_collection = PhotoConfigurationCollection.create(templateDoc) #Table configuration collection table_config_collection = TableConfigurationCollection.create(templateDoc) #Create chart configuration collection object chart_config_collection = ChartConfigurationCollection.create(templateDoc) # Create QR code configuration collection object qrc_config_collection = QRCodeConfigurationCollection.create(templateDoc) #Load the layers required by the table composer items self._table_mem_layers = load_table_layers(table_config_collection) entityFieldName = self.format_entity_field_name(composerDS.name(), data_source) #Execute query dsTable,records = self._exec_query(composerDS.name(), entityFieldName, entityFieldValue) if records is None or len(records) == 0: return False, QApplication.translate("DocumentGenerator", "No matching records in the database") """ Iterate through records where a single file output will be generated for each matching record. """ for rec in records: composition = QgsPrintLayout(self._map_settings) composition.loadFromTemplate(templateDoc) ref_layer = None #Set value of composer items based on the corresponding db values for composerId in composerDS.dataFieldMappings().reverse: #Use composer item id since the uuid is stripped off composerItem = composition.getComposerItemById(composerId) if not composerItem is None: fieldName = composerDS.dataFieldName(composerId) fieldValue = getattr(rec,fieldName) self._composeritem_value_handler(composerItem, fieldValue) # Extract photo information self._extract_photo_info(composition, ph_config_collection, rec) # Set table item values based on configuration information self._set_table_data(composition, table_config_collection, rec) # Refresh non-custom map composer items self._refresh_composer_maps(composition, list(spatialFieldsConfig.spatialFieldsMapping().keys())) # Set use fixed scale to false i.e. relative zoom use_fixed_scale = False # Create memory layers for spatial features and add them to the map for mapId,spfmList in spatialFieldsConfig.spatialFieldsMapping().items(): map_item = composition.getComposerItemById(mapId) if not map_item is None: # Clear any previous map memory layer # self.clear_temporary_map_layers() for spfm in spfmList: #Use the value of the label field to name the layer lbl_field = spfm.labelField() spatial_field = spfm.spatialField() if not spatial_field: continue if lbl_field: if hasattr(rec, spfm.labelField()): layerName = getattr(rec, spfm.labelField()) else: layerName = self._random_feature_layer_name(spatial_field) else: layerName = self._random_feature_layer_name(spatial_field) #Extract the geometry using geoalchemy spatial capabilities geom_value = getattr(rec, spatial_field) if geom_value is None: continue geom_func = geom_value.ST_AsText() geomWKT = self._dbSession.scalar(geom_func) #Get geometry type geom_type, srid = geometryType(composerDS.name(), spatial_field) #Create reference layer with feature ref_layer = self._build_vector_layer(layerName, geom_type, srid) if ref_layer is None or not ref_layer.isValid(): continue # Add feature bbox = self._add_feature_to_layer(ref_layer, geomWKT) zoom_type = spfm.zoom_type # Only scale the extents if zoom type is relative if zoom_type == 'RELATIVE': bbox.scale(spfm.zoomLevel()) #Workaround for zooming to single point extent if ref_layer.wkbType() == QgsWkbTypes.Point: canvas_extent = self._iface.mapCanvas().fullExtent() cnt_pnt = bbox.center() canvas_extent.scale(1.0/32, cnt_pnt) bbox = canvas_extent #Style layer based on the spatial field mapping symbol layer symbol_layer = spfm.symbolLayer() if not symbol_layer is None: ref_layer.rendererV2().symbols()[0].changeSymbolLayer(0,spfm.symbolLayer()) ''' Add layer to map and ensure its always added at the top ''' self.map_registry.addMapLayer(ref_layer) self._iface.mapCanvas().setExtent(bbox) # Set scale if type is FIXED if zoom_type == 'FIXED': self._iface.mapCanvas().zoomScale(spfm.zoomLevel()) use_fixed_scale = True self._iface.mapCanvas().refresh() # Add layer to map memory layer list self._map_memory_layers.append(ref_layer.id()) self._hide_layer(ref_layer) ''' Use root layer tree to get the correct ordering of layers in the legend ''' self._refresh_map_item(map_item, use_fixed_scale) # Extract chart information and generate chart self._generate_charts(composition, chart_config_collection, rec) # Extract QR code information in order to generate QR codes self._generate_qr_codes(composition, qrc_config_collection,rec) #Build output path and generate composition if not filePath is None and len(dataFields) == 0: self._write_output(composition, outputMode, filePath) elif filePath is None and len(dataFields) > 0: entityFieldName = 'id' docFileName = self._build_file_name(data_source, entityFieldName, entityFieldValue, dataFields, fileExtension) # Replace unsupported characters in Windows file naming docFileName = docFileName.replace('/', '_').replace \ ('\\', '_').replace(':', '_').strip('*?"<>|') if not docFileName: return (False, QApplication.translate("DocumentGenerator", "File name could not be generated from the data fields.")) outputDir = self._composer_output_path() if outputDir is None: return (False, QApplication.translate("DocumentGenerator", "System could not read the location of the output directory in the registry.")) qDir = QDir() if not qDir.exists(outputDir): return (False, QApplication.translate("DocumentGenerator", "Output directory does not exist")) absDocPath = "{0}/{1}".format(outputDir, docFileName) self._write_output(composition, outputMode, absDocPath) return True, "Success" return False, "Document composition could not be generated"
def accept(self): self.btnOk.setEnabled(False) self.btnCancel.setEnabled(False) project_name = self.project_name_le.text() if project_name.endswith('.qgs'): project_name = project_name[:-4] if not project_name: msg = tr("Please specify the project name") log_msg(msg, level='C', message_bar=iface.messageBar()) self.restoreGui() return outputDir = self.leOutputDir.text() if not outputDir: msg = tr("Please specify the output directory.") log_msg(msg, level='C', message_bar=iface.messageBar()) self.restoreGui() return outputDir = os.path.join(outputDir, get_valid_filename(project_name)) # create main directory if not exists d = QDir(outputDir) if not d.exists(): if not d.mkpath("."): msg = tr("Can't create directory to store the project.") log_msg(msg, level='C', message_bar=iface.messageBar()) self.restoreGui() return # create directory for layers if not exists if d.exists("layers"): res = QMessageBox.question( self, self.tr("Directory exists"), self.tr("Output directory already contains 'layers'" " subdirectory. Maybe this directory was used to" " consolidate another project. Continue?"), QMessageBox.Yes | QMessageBox.No) if res == QMessageBox.No: self.restoreGui() return else: if not d.mkdir("layers"): msg = tr("Can't create directory for layers.") log_msg(msg, level='C', message_bar=iface.messageBar()) self.restoreGui() return # copy project file projectFile = QgsProject.instance().fileName() try: if projectFile: f = QFile(projectFile) newProjectFile = os.path.join(outputDir, '%s.qgs' % project_name) f.copy(newProjectFile) else: newProjectFile = os.path.join(outputDir, '%s.qgs' % project_name) p = QgsProject.instance() p.write(newProjectFile) except Exception as exc: self.restoreGui() log_msg(str(exc), level='C', message_bar=iface.messageBar(), exception=exc) return # start consolidate task that does all real work self.consolidateTask = ConsolidateTask('Consolidation', QgsTask.CanCancel, outputDir, newProjectFile, self.checkBoxZip.isChecked(), self.cb.currentText()) self.consolidateTask.begun.connect(self.on_consolidation_begun) QgsApplication.taskManager().addTask(self.consolidateTask) super().accept()
def insertDocumentFromFile(self, path, doc_type_id, entity, record_count=1): """ Insert a new document into one of the registered containers with the document type id. This document is registered :param path: The local user path of the document :type path: String :param doc_type_id: The entity document type id :type doc_type_id: Integer :param entity: The entity in which the document is inserted into. :type entity: Entity class :param record_count: The number of records for which a document is uploaded. Default is 1. For instance, more records could be used in STR wizard in multi-party. :type record_count: Integer :return: None :rtype: NoneType """ if len(self.containers) > 0: if doc_type_id in self.containers: container = self.containers[doc_type_id] # Check if the file exists if QFile.exists(path): network_location = network_document_path() if not network_location: self._doc_repository_error() return # Check if the directory exists doc_dir = QDir(network_location) if not doc_dir.exists(): msg = QApplication.translate( "sourceDocumentManager", "The root document " "repository '{0}' does " "not exist.\nPlease " "check the path settings." ) parent = self.parent() if not isinstance(parent, QWidget): parent = None QMessageBox.critical( parent, QApplication.translate( "sourceDocumentManager", "Document Manager" ), msg.format(network_location) ) return for i in range(record_count): # Use the default network file manager networkManager = NetworkFileManager( network_location, self.parent() ) # Add document widget docWidg = DocumentWidget( self.document_model, networkManager, parent=self.parent(), view_manager=self._doc_view_manager ) # Connect slot once the document # has been successfully uploaded. docWidg.fileUploadComplete.connect( lambda: self.onFileUploadComplete(doc_type_id) ) self._linkWidgetRemovedSignal(docWidg) doc_type_entity = entity.supporting_doc.document_type_entity doc_type_value = entity_id_to_attr( doc_type_entity, 'value', doc_type_id ) docWidg.setFile( path, entity.name, doc_type_value, doc_type_id ) container.addWidget(docWidg)