Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
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)
Пример #4
0
    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
Пример #5
0
    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)
Пример #6
0
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})
Пример #7
0
    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()
Пример #8
0
    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
Пример #9
0
    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"
Пример #10
0
    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()
Пример #11
0
    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)