Example #1
0
    def set_data_source(self, data_source: ComposerDataSource):
        """
        Sets the data_source to show in the widget
        """
        if not data_source.name():
            return

        self.setCategory(data_source.category())
        self.setSelectedSource(data_source.name())
        self.set_referenced_table(data_source.referenced_table_name)
Example #2
0
 def build_from_path(name: str, path: str):
     """
     Creates an instance of the _DocumentTemplate class from the path of
     a document template.
     :param name: Template name.
     :type name: str
     :param path: Absolute path to the document template.
     :type path: str
     :return: Returns an instance of the _DocumentTemplate class from the
     absolute path of the document template.
     :rtype: _DocumentTemplate
     """
     data_source = ComposerDataSource.from_template_file(path)
     return DocumentTemplate(name=name, path=path, data_source=data_source)
Example #3
0
    def composer_data_source(self, template_document):
        """
        :param template_document: Document containing document composition
        information.
        :type template_document: QDomDocument
        :return: Returns the data source defined in the template document.
        :rtype: ComposerDataSource
        """
        composer_ds = ComposerDataSource.create(template_document)

        if not self.data_source_exists(composer_ds):
            msg = QApplication.translate("DocumentGenerator",
                                             "'{0}' data source does not exist in the database."
                                             "\nPlease contact your database "
                                             "administrator.".format(composer_ds.name()))

            return None, msg

        return composer_ds, ""
Example #4
0
    def _writeXML(self, xml_doc, doc_name):
        """
        Write the template configuration into the XML document.
        """
        #Write default composer configuration
        composer_element = xml_doc.createElement("Composer")
        composer_element.setAttribute("title", doc_name)
        composer_element.setAttribute("visible", 1)

        xml_doc.appendChild(composer_element)

        self.composition().writeXML(composer_element, xml_doc)

        #Write STDM data field configurations
        dataSourceElement = ComposerDataSource.domElement(self, xml_doc)
        composer_element.appendChild(dataSourceElement)

        #Write spatial field configurations
        spatialColumnsElement = SpatialFieldsConfiguration.domElement(
            self, xml_doc)
        dataSourceElement.appendChild(spatialColumnsElement)

        #Write photo configuration
        photos_element = PhotoConfigurationCollection.dom_element(
            self, xml_doc)
        dataSourceElement.appendChild(photos_element)

        #Write table configuration
        tables_element = TableConfigurationCollection.dom_element(
            self, xml_doc)
        dataSourceElement.appendChild(tables_element)

        #Write chart configuration
        charts_element = ChartConfigurationCollection.dom_element(
            self, xml_doc)
        dataSourceElement.appendChild(charts_element)

        # Write QRCode configuration
        qr_codes_element = QRCodeConfigurationCollection.dom_element(
            self, xml_doc)
        dataSourceElement.appendChild(qr_codes_element)
Example #5
0
    def __init__(self, layout_interface, iface):
        QObject.__init__(self, layout_interface)

        self._layout_interface = layout_interface
        # self._compView = composerView
        self._stdmTB = layout_interface.window().addToolBar(
            "STDM Document Designer")
        self._stdmTB.setObjectName('stdmDocumentDesigner')
        self._iface = iface
        self._config_items = []

        self.variable_template_path = None

        # Container for custom editor widgets
        self._widgetMappings = {}

        # Hide default dock widgets
        if self.itemDock() is not None:
            self.itemDock().hide()

        if self.atlasDock() is not None:
            self.atlasDock().hide()

        if self.generalDock() is not None:
            self.generalDock().hide()

        # Remove default toolbars
        self._remove_composer_toolbar('mAtlasToolbar')

        self._remove_composer_toolbar('mLayoutToolbar')

        # Create dock widget for configuring STDM data source
        stdmDataSourceDock = QDockWidget(
            QApplication.translate("ComposerWrapper", "STDM Data Source"),
            self.mainWindow())
        stdmDataSourceDock.setObjectName("STDMDataSourceDock")
        stdmDataSourceDock.setMinimumWidth(300)
        stdmDataSourceDock.setFeatures(QDockWidget.DockWidgetMovable
                                       | QDockWidget.DockWidgetClosable)
        self.mainWindow().addDockWidget(Qt.RightDockWidgetArea,
                                        stdmDataSourceDock)

        self._dataSourceWidget = ComposerDataSourceSelector(self.composition())
        stdmDataSourceDock.setWidget(self._dataSourceWidget)
        stdmDataSourceDock.show()

        # Re-insert dock widgets
        if self.generalDock() is not None:
            self.generalDock().show()

        if self.itemDock() is not None:
            self.itemDock().show()

        # Re-arrange dock widgets and push up STDM data source dock widget
        if self.generalDock() is not None:
            self.mainWindow().splitDockWidget(stdmDataSourceDock,
                                              self.generalDock(), Qt.Vertical)

        if self.itemDock() is not None:
            self.mainWindow().splitDockWidget(stdmDataSourceDock,
                                              self.itemDock(), Qt.Vertical)
            if self.generalDock() is not None:
                self.mainWindow().tabifyDockWidget(self.generalDock(),
                                                   self.itemDock())

        # Set focus on composition properties window
        if self.generalDock() is not None:
            self.generalDock().activateWindow()
            self.generalDock().raise_()

        self._selected_item_uuid = str()

        composer_data_source = ComposerDataSource.from_layout(
            self.composition())
        self._configure_data_controls(composer_data_source)
Example #6
0
    def xxxloadTemplate(self, filePath):
        """
        Loads a document template into the view and updates the necessary STDM-related composer items.
        """

        try:
            LayoutUtils.load_template_into_layout(self.composition(), filePath)
        except IOError as e:
            QMessageBox.critical(
                self.mainWindow(),
                QApplication.translate("ComposerWrapper",
                                       "Open Operation Error"),
                "{0}\n{1}".format(
                    QApplication.translate("ComposerWrapper",
                                           "Cannot read template file."),
                    str(e)))
            return

        if templateDoc.setContent(templateFile):
            table_config_collection = TableConfigurationCollection.create(
                templateDoc)
            '''
            First load vector layers for the table definitions in the config
            collection before loading the composition from file.
            '''
            load_table_layers(table_config_collection)

            self.clearWidgetMappings()

            # Load items into the composition and configure STDM data controls
            context = QgsReadWriteContext()
            self.composition().loadFromTemplate(templateDoc, context)

            # Load data controls
            composerDS = ComposerDataSource.create(templateDoc)

            # Set title by appending template name
            title = QApplication.translate("STDMPlugin",
                                           "STDM Document Designer")

            composer_el = templateDoc.documentElement()
            if composer_el is not None:
                template_name = ""
                if composer_el.hasAttribute("title"):
                    template_name = composer_el.attribute("title", "")
                elif composer_el.hasAttribute("_title"):
                    template_name = composer_el.attribute("_title", "")

                if template_name:
                    win_title = "{0} - {1}".format(title, template_name)
                    self.mainWindow().setWindowTitle(template_name)

            self._configure_data_controls(composerDS)

            # Load symbol editors
            spatialFieldsConfig = SpatialFieldsConfiguration.create(
                templateDoc)
            self._configureSpatialSymbolEditor(spatialFieldsConfig)

            # Load table editors
            self._configure_table_editors(table_config_collection)

            # Load chart property editors
            chart_config_collection = ChartConfigurationCollection.create(
                templateDoc)
            self._configure_chart_editors(chart_config_collection)

            # Load QR code property editors
            qrc_config_collection = QRCodeConfigurationCollection.create(
                templateDoc)
            self._configure_qr_code_editors(qrc_config_collection)
Example #7
0
    def loadTemplate(self, filePath):
        """
        Loads a document template into the view and updates the necessary STDM-related composer items.
        """
        if not QFile.exists(filePath):
            QMessageBox.critical(
                self.mainWindow(),
                QApplication.translate("OpenTemplateConfig",
                                       "Open Template Error"),
                QApplication.translate(
                    "OpenTemplateConfig",
                    "The specified template does not exist."))
            return

        copy_file = filePath.replace('sdt', 'cpy')

        # remove existing copy file
        if QFile.exists(copy_file):
            copy_template = QFile(copy_file)
            copy_template.remove()

        orig_template_file = QFile(filePath)

        self.setDocumentFile(orig_template_file)

        # make a copy of the original
        result = orig_template_file.copy(copy_file)

        #templateFile = QFile(filePath)

        # work with copy
        templateFile = QFile(copy_file)

        self.copy_template_file = templateFile

        if not templateFile.open(QIODevice.ReadOnly):
            QMessageBox.critical(
                self.mainWindow(),
                QApplication.translate("ComposerWrapper",
                                       "Open Operation Error"),
                "{0}\n{1}".format(
                    QApplication.translate("ComposerWrapper",
                                           "Cannot read template file."),
                    templateFile.errorString()))
            return

        templateDoc = QDomDocument()

        if templateDoc.setContent(templateFile):
            table_config_collection = TableConfigurationCollection.create(
                templateDoc)
            '''
            First load vector layers for the table definitions in the config
            collection before loading the composition from file.
            '''
            load_table_layers(table_config_collection)

            self.clearWidgetMappings()

            #Load items into the composition and configure STDM data controls
            self.composition().loadFromTemplate(templateDoc)

            #Load data controls
            composerDS = ComposerDataSource.create(templateDoc)

            #Set title by appending template name
            title = QApplication.translate("STDMPlugin",
                                           "STDM Document Designer")

            composer_el = templateDoc.documentElement()
            if not composer_el is None:
                template_name = ""
                if composer_el.hasAttribute("title"):
                    template_name = composer_el.attribute("title", "")
                elif composer_el.hasAttribute("_title"):
                    template_name = composer_el.attribute("_title", "")

                if template_name:
                    win_title = "{0} - {1}".format(title, template_name)
                    self.mainWindow().setWindowTitle(template_name)

            self._configure_data_controls(composerDS)

            #Load symbol editors
            spatialFieldsConfig = SpatialFieldsConfiguration.create(
                templateDoc)
            self._configureSpatialSymbolEditor(spatialFieldsConfig)

            #Load photo editors
            photo_config_collection = PhotoConfigurationCollection.create(
                templateDoc)
            self._configure_photo_editors(photo_config_collection)

            # Load table editors
            self._configure_table_editors(table_config_collection)

            #Load chart property editors
            chart_config_collection = ChartConfigurationCollection.create(
                templateDoc)
            self._configure_chart_editors(chart_config_collection)

            # Load QR code property editors
            qrc_config_collection = QRCodeConfigurationCollection.create(
                templateDoc)
            self._configure_qr_code_editors(qrc_config_collection)

            self._sync_ids_with_uuids()
Example #8
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"