def getDateRangeDlg(parent=None, is_concrete_date=False): """ Select date range in dialog. :param parent: Parent window. If None then get wx.GetApp().GetTopWindow() :param is_concrete_date: Select concrete date? :return: Date range tuple (as datetime) or None if press <Cancel>. """ selected_range = None if parent is None: parent = wx.GetApp().GetTopWindow() dlg = daterange_dlg.iqDateRangeDialog(parent) dlg.setConcreteDateCheck(is_concrete_date) dlg.Centre() if dlg.ShowModal() == wx.ID_OK: selected_range = dlg.getSelectedDateRangeAsDatetime() dlg.Destroy() if selected_range: try: log_func.debug(u'Selected date range: <%s> - <%s>' % selected_range) except: pass return selected_range
def editIndicatorConstructorDlg(parent=None, indicator=None): """ Start editing the filter indicator. :param parent: Parent window. :param indicator: Indicator state. :return: Edited indicator list or None if CANCEL is pressed. """ try: if parent is None: parent = iq.getMainWin() dlg = iqIndicatorConstructorDlg(parent) dlg.init() dlg.setIndicator(indicator=indicator, refresh_ctrl=True) result = dlg.ShowModal() if result == wx.ID_OK: indicator = dlg.getIndicator() dlg.Destroy() log_func.debug(u'List of edited filter indicator %s' % str(indicator)) return indicator except: log_func.fatal(u'Filter indicator editing error') return None
def edit(self, rep_filename=None): """ Edit report. :param rep_filename: Report template filename. """ rprt_file_name = os.path.abspath(rep_filename) rep = res_func.loadResource(rprt_file_name) report_dir = os.path.abspath(self.getReportDir()) rep_file = os.path.join(report_dir, rep['generator']) reportman_designer_key = utilfunc.getRegValue( 'Software\\Classes\\Report Manager Designer\\shell\\open\\command', None) if reportman_designer_key: reportman_designer_run = reportman_designer_key.replace( '\'%1\'', '\'%s\'') % rep_file cmd = 'start %s' % reportman_designer_run log_func.debug(u'Execute command <%s>' % cmd) # Run Report Manager Designer os.system(cmd) else: msg = u'Not define Report Manager Designer <%s>' % reportman_designer_key log_func.warning(msg) dlg_func.openWarningBox(u'WARNING', msg) xml_file = os.path.normpath( os.path.abspath(os.path.splitext(rep_filename)[0] + '.xml')) cmd = 'start excel.exe \'%s\'' % xml_file log_func.debug(u'Execute command <%s>' % cmd) os.system(cmd)
def _execCodeBlock(self, cur_func, locals, globals): """ Execute code block. In the code block, new_cell and record objects are available. If you need to display information, then it must be displayed in the variable value. For example: [=value = '-' if record['name']=='My name' else ''=] :param cur_func: Code block text. :param locals: Local name space. :param globals: Global name space. :return: The calculated value as a string or an empty string in case of an error. """ value = u'' exec_func = cur_func[2:-2].strip() try: exec(exec_func, globals, locals) # When the code block is executed, the value of the variable is located in the locals namespace. # Therefore, after executing the code block, it is necessary # to return the variable back to the current function value = locals.get('value', u'') log_func.debug(u'Execute code block <%s>. Value [%s]' % (exec_func, value)) except: log_func.fatal(u'Error code block execute <%s>' % str_func.toUnicode(exec_func)) return str(value)
def createNewByOffice(self, dst_path=None): """ Create a new report using LibreOffice Calc. :param dst_path: Destination report folder path. """ try: src_filename = DEFAULT_REP_TMPL_FILE new_filename = dlg_func.getTextEntryDlg(self._parent_window, u'Create new', u'Enter a file name for the report template') if os.path.splitext(new_filename)[1] != '.ods': new_filename += '.ods' if dst_path is None: # It is necessary to determine the resulting path dst_path = dlg_func.getDirDlg(self._parent_window, u'Report folder') if not dst_path: dst_path = os.getcwd() dst_filename = os.path.join(dst_path, new_filename) if os.path.exists(dst_filename): if dlg_func.openAskBox(u'Rewrite existing file?'): shutil.copyfile(src_filename, dst_filename) else: shutil.copyfile(src_filename, dst_filename) cmd = '%s %s' % (UNIX_OFFICE_OPEN, dst_filename) log_func.debug(u'Command <%s>' % str_func.toUnicode(cmd)) os.system(cmd) return True except: log_func.fatal(u'Error create new report template by LibreOffice Calc')
def setExtOptions(self, **options): """ Set advanced options. :param options: Dictionary of options. """ log_func.debug(u'Scan options: %s' % options) if options: if 'scanner' in options: self.scanner = options.get('scanner', None) if 'source' in options: self.scan_source = options.get('source', None) if 'mode' in options: self.scan_mode = options.get('mode', None) if 'is_multi_scan' in options: self.is_multi_scan = options.get('is_multi_scan', None) if 'is_preview' in options: self.is_preview = options.get('is_preview', None) if 'page_size' in options: self.page_size = options.get('page_size', None) if 'area' in options: self.scan_area = options.get('area', None) if 'scan_dir' in options: self.scan_dir = options.get('scan_dir', None) if 'file_name' in options: self.scan_filename = options.get('file_name', None) if 'file_type' in options: self.scan_filetype = options.get('file_type', None) if 'depth' in options: self.depth = options.get('depth', None) if 'ext_scan_cmd' in options: self.ext_scan_cmd = options.get('ext_scan_cmd', None) else: log_func.warning(u'Undefined scan options for set')
def showOptions(self): """ Set scan parameters in window controls. """ if self.scanner: self.scanner_comboBox.SetStringSelection(self.scanner) if self.scan_source: i = 0 try: log_func.debug(u'Setting the scan source <%s>' % self.scan_source) i = scan_manager.SCAN_SOURCES.index(self.scan_source) except ValueError: log_func.warning(u'Scan source not found <%s>' % self.scan_source) self.source_comboBox.Select(i) if self.scan_mode: i = 0 try: i = scan_manager.SCAN_MODES.index(self.scan_mode) except ValueError: log_func.warning(u'Scan mode not found <%s>' % self.scan_mode) self.mode_comboBox.Select(i) self.multiscan_checkBox.SetValue(bool(self.is_multi_scan)) self.preview_checkBox.SetValue(bool(self.is_preview)) if self.page_size: i = 0 try: i = scan_manager.SCAN_PAGE_SIZES.index(self.page_size) except ValueError: log_func.warning(u'Scan page size not found <%s>' % self.page_size) self.pagesize_comboBox.Select(i) if self.scan_area: self.left_spinCtrl.SetValue(self.scan_area[0]) self.top_spinCtrl.SetValue(self.scan_area[1]) self.right_spinCtrl.SetValue(self.scan_area[2]) self.bottom_spinCtrl.SetValue(self.scan_area[3]) if self.scan_dir: self.scan_dirPicker.SetPath(self.scan_dir) if self.scan_filename: self.filename_textCtrl.SetValue(self.scan_filename) if self.scan_filetype: i = 0 try: i = scan_manager.SCAN_FILE_TYPES.index(self.scan_filetype) except ValueError: log_func.warning(u'Scan file type not found <%s>' % self.scan_filetype) self.fileext_comboBox.Select(i) if self.depth: self.depth_spinCtrl.SetValue(self.depth) if self.ext_scan_cmd: self.extern_cmd_textCtrl.SetValue(self.ext_scan_cmd)
def appendDBFNewField(dbf_filename, new_fieldname, field_type, field_length, default=None): """ Append new field inDBF table. :param dbf_filename: DBF filename. :param new_fieldname: New field name. :param field_type: Field type (C-string and etc). :param field_length: Field length. :param default: Default value. :return: DBF table object. """ dbf_filename = os.path.abspath(dbf_filename) if not os.path.exists(dbf_filename): log_func.warning(u'DBF file <%s> not found' % dbf_filename) return None dbf_connection = None try: dbf_url = DBF_DB_URL_FMT % (os.path.dirname(dbf_filename), DEFAULT_DBF_ENCODING) dbf_connection = jaydebeapi.connect('com.hxtt.sql.dbf.DBFDriver', [dbf_url], JDBC_DBF_DRIVER) if field_type == 'C': db_cursor = dbf_connection.cursor() sql = 'ALTER TABLE %s ADD %s VARCHAR(%d)' % (os.path.splitext( os.path.basename(dbf_filename))[0], new_fieldname, field_length) if default is not None: sql += ' DEFAULT \'%s\'' % str(default) log_func.debug(u'Execute SQL <%s>' % sql) db_cursor.execute(sql) elif field_type == 'L': db_cursor = dbf_connection.cursor() sql = 'ALTER TABLE %s ADD %s BOOLEAN' % (os.path.splitext( os.path.basename(dbf_filename))[0], new_fieldname) if default is not None: sql += ' DEFAULT %s' % str(default) db_cursor.execute(sql) elif field_type == 'D': db_cursor = dbf_connection.cursor() sql = 'ALTER TABLE %s ADD %s DATE' % (os.path.splitext( os.path.basename(dbf_filename))[0], new_fieldname) if default is not None: sql += ' DEFAULT \'%s\'' % str(default) db_cursor.execute(sql) else: log_func.warning(u'Unsupported field type <%s>' % field_type) dbf_connection.close() except: if dbf_connection: dbf_connection.close() log_func.fatal(u'Error append new field DBF file <%s>' % dbf_filename) return None
def scanPack(self, scan_filenames=()): """ Scan documents in batch mode and save them to files. :param scan_filenames: Scan file names with the number of sheets and a sign of 2-sided scanning. For example: ('D:/tmp/scan001', 3, True), ('D:/tmp/scan002', 1, False), ('D:/tmp/scn003', 2, True), ... :return: List of scan file names. None - in case of an error. """ result = list() # Scanned Sheet Counter tray_sheet_count = 0 # Tray volume in sheets max_sheets = self.getMaxSheets() # In batch mode, do not use the dialog box # But in the case of gluing the document in parts, the dialog boxes are used # Dialog Box Application Object wx_app = None for scan_filename, n_pages, is_duplex in scan_filenames: tray_sheet_count += n_pages if not is_duplex else n_pages / 2 if tray_sheet_count <= max_sheets: # Until the scan counter has exceeded the tray size limit, # then continue the usual scan scan_result = self.scanPackPart(scan_filename, n_pages, is_duplex) result.append(scan_filename if scan_result and os.path.exists(scan_filename) else None) else: log_func.debug( u'Enabling document scan mode <%s> piecemeal. Number of pages [%d] Current counter %d. ' % (scan_filename, n_pages, tray_sheet_count)) if wx_app is None: wx_app = wx.PySimpleApp() locale = wx.Locale() locale.Init(wx.LANGUAGE_RUSSIAN) # If the tray runs out of paper, you need to start the process of gluing the last document glue_result = self.scanGlue(scan_filename, n_pages, is_duplex) result.append(scan_filename if glue_result and os.path.exists(scan_filename) else None) # ATTENTION! After a successfully scanned large document, # reset the counter of scanned sheets tray_sheet_count = 0 # ATTENTION! Because Since the interaction is built on modal dialogs, # MainLoop does not need to be done otherwise # the main application freezes # if wx_app: # wx_app.MainLoop() return result
def onIndicatorListItemSelected(self, event): """ Handler for selecting an indicator state from the list. """ idx = event.GetIndex() state_indicator = self._indicator[idx] log_func.debug(u'Edit indicator: %s' % str(state_indicator)) self.setStateCtrlValue(state_indicator=state_indicator) self.ctrl_toolBar.EnableTool(self.save_tool.GetId(), True) event.Skip()
def _execQueryFunc(self, query, vars=None): """ Get a request from a function. :param query: Query text. :param vars: External variables. :return: Query in internal format. """ # Clear signature func = query.replace(PY_SIGNATURE, '').strip() var_names = vars.keys() if vars else None log_func.debug(u'Execute function: <%s>. External variables %s' % (func, var_names)) return exec_func.execTxtFunction(func, context=vars, show_debug=True)
def _getVariable(self, cur_func, locals, globals): """ Get variable from report name space. :param cur_func: Get variable text. :param locals: Local name space. :param globals: Global name space. :return: The variable value as a string or an empty string in case of an error. """ var_name = cur_func[2:-2] if var_name in self._NameSpace: log_func.debug(u'Get variable <%s>' % var_name) else: log_func.warning(u'Variable <%s> not found in report name space' % var_name) value = str(self._NameSpace.setdefault(var_name, u'')) return value
def setCircleMarker(self, geo_latitude, geo_longitude, radius=100, color='blue', is_fill=True, fill_color='blue', popup_text=u'', tooltip_text=u''): """ Adding a circle marker to the map. :param geo_latitude: Geographic latitude. :param geo_longitude: Geographic longitude. :param radius: Circle radius. :param color: Circle color. :param is_fill: Fill the inner area of the circle? :param fill_color: The fill color of the circle. :param popup_text: Marker pop-up text. A tooltip appears by clicking on the marker. :param tooltip_text: Marker tooltip text. A tooltip appears when you hover the mouse over the marker. :return: True/False. """ if self._geo_map is not None: try: color = wxcolour_func.wxColour2StrRGB(color) if color else None fill_color = wxcolour_func.wxColour2StrRGB( color) if fill_color else None marker = self._rendering.CircleMarker( location=[geo_latitude, geo_longitude], radius=radius, popup=popup_text if popup_text else None, color=color if color else None, fill=is_fill, fill_color=fill_color if fill_color else None, tooltip=tooltip_text if tooltip_text else None) marker.add_to(self._geo_map) log_func.debug( u'Circle marker created. Geolocation [%s x %s]' % (geo_latitude, geo_longitude)) except: log_func.fatal(u'Error adding a circle marker to the map') else: log_func.warning(u'Map object not defined to add a circle marker') return False
def onPrintRepButton(self, event): """ Print button click handler. """ item = self.rep_tree.GetSelection() item_data = self.rep_tree.GetItemData(item) log_func.debug(u'Print <%s>' % item_data[REP_FILE_IDX] if item_data else u'-') if item_data is not None and item_data[REP_ITEMS_IDX] is None: report_gen_func.getReportGeneratorSystem(item_data[REP_FILE_IDX], parent=self, refresh=True).print() else: dlg_func.openWarningBox(title=_(u'WARNING'), message=_(u'You must select a report'), parent=self) event.Skip()
def onLastDateChanged(self, event): """ Change last date handler. """ first_date = self.firstDatePicker.GetValue() last_date = event.GetDate() try: log_func.debug(u'Correct last date <%d.%d.%d>' % (last_date.GetDay(), last_date.GetMonth(), last_date.GetYear())) except: pass if first_date > last_date and len(str(last_date.GetYear())) == 4: self.lastDatePicker.SetValue(first_date) event.Skip()
def onUpdateRepButton(self, event): """ Update button click handler. """ item = self.rep_tree.GetSelection() item_data = self.rep_tree.GetItemData(item) if item_data is not None and item_data[REP_ITEMS_IDX] is None: log_func.debug(u'Update report <%s>' % item_data[0]) report_gen_func.getReportGeneratorSystem(item_data[REP_FILE_IDX], parent=self).update( item_data[0]) else: report_gen_func.getCurReportGeneratorSystem(self).update() self.buildReportTree(self._report_dirname) event.Skip()
def getSputnikGeolocations(address_query): """ Get all geolocation data for the requested address by service http://api.sputnik.ru/maps/geocoder/. :param address_query: Address. For example: Moscow, Gagarin street, 10. :return: [(latitude, longitude),...] Geolocation data list or empty list in case of error. """ try: address = urllib.parse.quote(address_query) url = SPUTNIK_GEO_LOCATOR_URL_FMT % address log_func.debug(u'Geodata retrieval URL <%s>' % url) response = urllib.request.urlopen(url) data = response.read() # ERROR: JSON object must be str not bytes # V geo_location_data = json.loads(data.decode('utf-8')) find_address = geo_location_data.get('result', dict()).get('address', list()) find_features = list( itertools.chain( *[item.get('features', list()) for item in find_address])) find_geometries = list( itertools.chain(*[ item.get('geometry', dict()).get('geometries', list()) for item in find_features ])) find_coordinates = [ item.get('coordinates', list()) for item in find_geometries ] find_coordinates = [ list(reversed(item)) for item, _ in itertools.groupby(find_coordinates) ] # log_func.debug(u'Sputnik. Get geo locations %s' % str(find_coordinates)) return find_coordinates except Exception as e: log_func.fatal( u'Sputnik. Error retrieving geolocation data by address <%s>' % address_query) return list()
def getYandexMapsGeolocations(address_query, geo_key=None): """ Get all geolocation data for the requested address by yandex maps. :param address_query: Address. For example: Moscow, Gagarin street, 10. :param geo_key: Geolocator API key. :return: [(latitude, longitude),...] Geolocation data list or empty list in case of error. """ if geo_key is None: geo_key = txtfile_func.loadTextFile(API_KEY_YANDEX_FILENAME).strip() try: address = urllib.parse.quote(address_query) url = YANDEX_GEO_LACATOR_URL_FMT % (address, geo_key) log_func.debug(u'Geodata retrieval URL <%s>' % url) response = urllib.request.urlopen(url) data = response.read() # ERROR: JSON object must be str not bytes # V geo_location_data = json.loads(data.decode('utf-8')) find_geo = geo_location_data.get('response', dict()).get( 'GeoObjectCollection', dict()).get('featureMember', list()) str_geo_locations = [ item.get('GeoObject', dict()).get('Point', dict()).get('pos', None) for item in find_geo ] # [NOTE] In API Yandex Maps longitude first, then latitude, # V geo_locations = [ tuple([float(pos) for pos in reversed(location.split(' '))]) if location is not None else (None, None) for location in str_geo_locations ] # log_func.debug(u'Yandexmaps. Get geo locations %s' % str(geo_locations)) return geo_locations except Exception as e: log_func.fatal( u'Yandex default. Error retrieving geolocation data by address <%s>' % address_query) return list()
def _execExpression(self, cur_func, locals, globals): """ Execute expression. For example: [#record["dt"].strftime("%B")#] :param cur_func: Expression text. :param locals: Local name space. :param globals: Global name space. :return: The calculated value as a string or an empty string in case of an error. """ value = u'' exp_body = cur_func[2:-2] try: value = eval(exp_body, globals, locals) except: log_func.fatal(u'Error expression execute <%s>' % exp_body) log_func.debug(u'Execute expression <%s>. Value <%s>' % (exp_body, str(value))) return value
def scanSingle(self, scan_filename=None): """ Single Page Scan If incl. DUPLEX and off-page scanning, then you need to scan 1 sheet from 2 sides. :param scan_filename: The name of the scan file. If the file name is not specified, then scanning and returns the PIL.Image object. :return: True/False """ if self.isDuplexOption(): # If on DUPLEX and multi-page scanning is turned off, # then you need to scan 1 sheet from 2 sides log_func.debug(u'Duplex scan') return self.scanMulti(scan_filename, 2) log_func.debug(u'Single-sided scanning') self.startScan() scan_obj = self.scan(scan_filename) return scan_obj is not None
def getReportResourceFilename(report_filename='', report_dir=''): """ Get the full file name of the report template. :param report_filename:The name of the report file in short form. :param report_dir: Report folder. :return: The full name of the report file. """ # Check extension if not report_filename.endswith(DEFAULT_REPORT_FILE_EXT): report_filename = os.path.splitext( report_filename)[0] + XML_REPORT_FILE_EXT rprt_filename = report_filename if not rprt_filename.endswith(DEFAULT_REPORT_FILE_EXT): rprt_filename = os.path.splitext( rprt_filename)[0] + DEFAULT_REPORT_FILE_EXT # Check the relevance of the template full_src_filename = getPathFilename(report_filename, report_dir) full_rprt_filename = getPathFilename(rprt_filename, report_dir) if isNewReportTemplateFile(full_src_filename, full_rprt_filename): # If the original template is changed later than the working # template file <rprt> then you need to make changes updateReportTemplateFile(full_src_filename, full_rprt_filename) if os.path.exists(rprt_filename): # Check can be given an absolute file name filename = rprt_filename else: # The relative file name relative to the report # folder is most likely set filename = full_rprt_filename if not os.path.exists(filename): log_func.warning(u'Report template file <%s> not found' % str(filename)) filename = createReportResourceFile(filename) log_func.debug(u'Report template filename <%s>' % str(filename)) return filename
def main(argv): """ Main function. :param argv: A list of command line options. """ # Parse command line options try: options, args = getopt.getopt(argv, 'h?vdVEDpPES', [ 'help', 'version', 'debug', 'log', 'viewer', 'editor', 'postprint', 'postpreview', 'postexport', 'print=', 'preview=', 'export=', 'select=', 'gen=', 'db=', 'sql=', 'stylelib=', 'var=', 'path=', 'no_gui' ]) except getopt.error as err: log_func.warning(err.msg, is_force_print=True) log_func.info(__doc__, is_force_print=True) sys.exit(2) report_filename = None db = None sql = None do_cmd = None stylelib = None variables = dict() path = None mode = 'default' mode_arg = None for option, arg in options: if option in ('-h', '--help', '-?'): log_func.info(__doc__, is_force_print=True) sys.exit(0) elif option in ('-v', '--version'): version_txt = 'iqReport version: %s' % '.'.join( [str(ver) for ver in __version__]) log_func.info(version_txt, is_force_print=True) sys.exit(0) elif option in ('-d', '--debug'): global_func.setDebugMode() elif option in ('-l', '--log'): global_func.setLogMode() elif option in ('-V', '--viewer'): mode = 'view' elif option in ('-E', '--editor'): mode = 'edit' elif option in ('-p', '--print'): mode = 'print' mode_arg = arg elif option in ('-P', '--preview'): mode = 'preview' mode_arg = arg elif option in ('-E', '--export'): mode = 'export' mode_arg = arg elif option in ('-S', '--select'): mode = 'select' mode_arg = arg elif option in ('--gen', ): report_filename = arg elif option in ('--db', ): db = arg elif option in ('--sql', ): sql = arg elif option in ('--postprint', ): do_cmd = do_report.DO_COMMAND_PRINT elif option in ('--postpreview', ): do_cmd = do_report.DO_COMMAND_PREVIEW elif option in ('--postexport', ): do_cmd = do_report.DO_COMMAND_EXPORT elif option in ('--stylelib', ): stylelib = arg elif option in ('--var', ): var_name = arg.split('=')[0].strip() var_value = arg.split('=')[-1].strip() variables[var_name] = var_value log_func.debug(u'External variable <%s>. Value [%s]' % (str(var_name), str(var_value))) elif option in ('--path', ): path = arg elif option in ('--no_gui', ): global_func.setEngineType(global_data.CUI_ENGINE_TYPE) log_func.init(LOG_FILENAME) # You must add the path to the report folder so that import of report modules if path is None: path = DEFAULT_REPORTS_PATH if os.path.exists(path) and os.path.isdir(path) and path not in sys.path: sys.path.append(path) app = wx.App() # locale = wx.Locale() # locale.Init(wx.LANGUAGE_RUSSIAN) if mode == 'default': if report_filename: # Run report generation from the command line do_report.doReport(report_filename=report_filename, report_dir=path, db_url=db, sql=sql, command=do_cmd, stylelib_filename=stylelib, variables=variables) elif mode == 'view': do_report.openReportViewer(report_dir=path) elif mode == 'edit': do_report.openReportEditor(report_dir=path) elif mode == 'print': do_report.printReport(report_filename=mode_arg, report_dir=path, db_url=db, sql=sql, command=do_cmd, stylelib_filename=stylelib, variables=variables) elif mode == 'preview': do_report.previewReport(report_filename=mode_arg, report_dir=path, db_url=db, sql=sql, command=do_cmd, stylelib_filename=stylelib, variables=variables) elif mode == 'export': do_report.exportReport(report_filename=mode_arg, report_dir=path, db_url=db, sql=sql, command=do_cmd, stylelib_filename=stylelib, variables=variables) elif mode == 'select': do_report.selectReport(report_filename=mode_arg, report_dir=path, db_url=db, sql=sql, command=do_cmd, stylelib_filename=stylelib, variables=variables) app.MainLoop()
def runScanPack(self, *scan_filenames): """ Start the scanning process in batch mode, according to the parameters set. :param scan_filenames: Scan file names with the number of sheets and a sign of 2-sided scanning. For example: (scan001, 3, True), (scan002, 1, False), (scn003, 2, True), ... :return: True/False """ if self.scan_manager is None: log_func.warning(u'Scan Manager not defined') return False if self.scanner is None: log_func.warning(u'Undefined scan device') return False self.scan_manager.init() self.scan_manager.open(self.scanner) options = dict() if self.scan_source: options['source'] = self.scan_source if self.scan_mode: options['mode'] = self.scan_mode if self.depth: options['depth'] = self.depth if self.page_size: options['page_width'] = self.page_size[0] options['page_height'] = self.page_size[1] else: options['page_width'] = 210.0 options['page_height'] = 297.0 if self.scan_area: options['tl_x'] = self.scan_area[0] options['tl_y'] = self.scan_area[1] if self.scan_area and self.page_size: options['br_x'] = self.page_size[0] - self.scan_area[2] options['br_y'] = self.page_size[1] - self.scan_area[3] else: options['br_x'] = 210.0 options['br_y'] = 297.0 self.scan_manager.setScanOptions(**options) scans = [(os.path.join(file_func.getHomePath(), global_func.getProjectName(), scan_filename + '.' + self.scan_filetype) if scan_filename else config.DEFAULT_SCAN_FILENAME, int(n_pages), bool(is_duplex)) for scan_filename, n_pages, is_duplex in scan_filenames] for scan_filename, n_pages, is_duplex in scans: full_scan_filename = os.path.join(os.environ.get('HOME', '/home/user'), global_func.getProjectName(), scan_filename) if os.path.exists(full_scan_filename): try: os.remove(full_scan_filename) log_func.info(u'Previously scanned file deleted <%s>' % full_scan_filename) except OSError: log_func.fatal(u'Error delete file <%s>' % full_scan_filename) try: scan_filenames = self.scan_manager.scanPack(scan_filenames=scans) # Transfer scanned files to the resulting folder if self.scan_dir and os.path.exists(self.scan_dir): for scan_filename in scan_filenames: if scan_filename and os.path.exists(scan_filename): log_func.debug(u'File transfer <%s> to the resulting folder <%s>' % (scan_filename, self.scan_dir)) self.copyToScanDir(scan_filename, self.scan_dir) else: log_func.warning(u'Result scan file not defined') else: log_func.warning(u'Result scan folder not defined') return True except: log_func.fatal(u'Scan Error in Batch Processing') return False
def _runScan(self): """ Start the scanning process according to the set parameters. :return: True/False """ if self.scan_manager is None: log_func.warning(u'Scan manager not defined') return False if self.scanner is None: log_func.warning(u'Undefined scan device') return False self.scan_manager.init() self.scan_manager.open(self.scanner) # Set options options = dict() if self.scan_source: options['source'] = self.scan_source if self.scan_mode: options['mode'] = self.scan_mode if self.depth: options['depth'] = self.depth if self.page_size: options['page_width'] = self.page_size[0] options['page_height'] = self.page_size[1] else: # If you do not determine the page size, # the edge of the scan will be cropped. # Default A4 portrait orientation options['page_width'] = 210.0 options['page_height'] = 297.0 if self.scan_area: options['tl_x'] = self.scan_area[0] options['tl_y'] = self.scan_area[1] if self.scan_area and self.page_size: options['br_x'] = self.page_size[0] - self.scan_area[2] options['br_y'] = self.page_size[1] - self.scan_area[3] else: # If you do not determine the page size, # the edge of the scan will be cropped. # Default A4 portrait orientation options['br_x'] = 210.0 options['br_y'] = 297.0 self.scan_manager.setScanOptions(**options) # Defining a scan file name scan_filename = os.path.join(file_func.getHomePath(), global_func.getProjectName(), self.scan_filename + '.' + self.scan_filetype) if self.scan_filename else config.DEFAULT_SCAN_FILENAME if os.path.exists(scan_filename): # Delete old scan file try: os.remove(scan_filename) log_func.info(u'Delete file <%s>' % scan_filename) except OSError: log_func.fatal(u'Error delete file <%s>' % scan_filename) log_func.debug(u'Scan to file <%s>' % scan_filename) try: if not self.is_multi_scan: result = self.scan_manager.scanSingle(scan_filename) else: result = self.scan_manager.scanMulti(scan_filename) if not result: dlg_func.openErrBox(u'ERROR', u'Scan error. Check the sheets in the scanner tray') return False if self.scan_dir: self.copyToScanDir(scan_filename, self.scan_dir) if self.is_preview: self.previewScanFile(scan_filename) return True except: log_func.fatal(u'Scan error') return False
def getDaDataGeolocation(address_query, api_key=None, secret_key=None, cache=True): """ Get geolocation data for the requested address. Library used https://github.com/hflabs/dadata-py. Ubuntu installation: pip3 install dadata :param address_query: Address. For example: Moscow, Gagarin street, 10. :param api_key: Geolocator API key. :param secret_key: Secret key. :param cache: Use internal cache? :return: (latitude, longitude) of geolocation data or (None, None) in case of error. """ if api_key is None: api_key = txtfile_func.loadTextFile(API_KEY_DADATA_FILENAME).strip() if secret_key is None: secret_key = txtfile_func.loadTextFile( SECRET_KEY_DADATA_FILENAME).strip() global GEO_LOCATOR_CACHE if cache: if GEO_LOCATOR_CACHE is None: GEO_LOCATOR_CACHE = dict() if address_query in GEO_LOCATOR_CACHE: # If such an address is in the cache, then we take it from the cache return GEO_LOCATOR_CACHE[address_query] dadata_client = None try: if not dadata: log_func.warning( u'DaData library not installed. Ubuntu installation: pip3 install dadata' ) return None, None dadata_client = dadata.Dadata(token=api_key, secret=secret_key) geo_data = dadata_client.clean(name='address', source=address_query) dadata_client.close() dadata_client = None geo_latitude = geo_data.get('geo_lat', None) geo_latitude = float(geo_latitude.replace(',', '.')) if isinstance( geo_latitude, str) else geo_latitude geo_longitude = geo_data.get('geo_lon', None) geo_longitude = float(geo_longitude.replace(',', '.')) if isinstance( geo_longitude, str) else geo_longitude log_func.debug(u'Address <%s>. Geo position [%f x %f]' % (address_query, geo_latitude, geo_longitude)) if cache: # Save in the cache GEO_LOCATOR_CACHE[address_query] = (geo_latitude, geo_longitude) # log_func.debug(u'DaData. Get geo locations (%s x %s)' % (str(geo_latitude), str(geo_longitude))) return geo_latitude, geo_longitude except Exception as e: if dadata_client: dadata_client.close() log_func.fatal( u'DaData. Error retrieving geolocation data by address <%s>' % address_query) return None, None
def scan_glue_mode(scan_manager, scan_filename, n_sheets, is_duplex=False, max_tray_sheets=60): """ Starting the document gluing mode in parts. :param scan_manager: Scan manager. :param scan_filename: The name of the resulting scan file. :param n_sheets: Number of sheets. :param is_duplex: Duplex scanning? :param max_tray_sheets: The maximum number of sheets in the scanner tray. :return: True/False. """ # The main cycle of scanning a document in parts and subsequent gluing n_part = 1 scan_file_path, scan_file_ext = os.path.splitext(scan_filename) part_suffix = '_part%03d' % n_part new_scan_filename = scan_file_path + part_suffix + scan_file_ext sheets = scan_glue_load_sheets(None, min(max_tray_sheets, n_sheets)) scan_sheet_count = sheets is_cancel = scan_sheet_count <= 0 while (0 < scan_sheet_count <= n_sheets) or is_cancel: log_func.debug(u'Scan File <%s> Scan Sheets [%d]' % (new_scan_filename, sheets)) # If duplex is used, then it is necessary to increase the number of pages scan_n_pages = sheets * 2 if is_duplex else sheets # Starting the scanning process scan_result = scan_manager.scanMulti(new_scan_filename, scan_n_pages) if scan_result and os.path.exists(new_scan_filename): verify_result = scan_glue_verify(None, new_scan_filename) if verify_result: n_part += 1 part_suffix = '_part%03d' % n_part new_scan_filename = scan_file_path + part_suffix + scan_file_ext do_scan_sheet_count = min(max_tray_sheets, n_sheets-scan_sheet_count) if do_scan_sheet_count <= 0: # All sheets are scanned. break sheets = scan_glue_load_sheets(None, do_scan_sheet_count) if sheets <= 0: # Clicked <Cancel> is_cancel = True break scan_sheet_count += sheets elif verify_result is None: scan_sheet_count -= sheets sheets = scan_glue_load_sheets(None, min(max_tray_sheets, n_sheets)) if sheets <= 0: # Clicked <Cancel> is_cancel = True break scan_sheet_count += sheets else: is_cancel = True log_func.warning(u'Scan to parts of file <%s> canceled' % new_scan_filename) break else: is_cancel = True log_func.warning(u'Error scanning file <%s>' % new_scan_filename) # Glue the scanned parts of the document if not is_cancel: part_pdf_filenames = [scan_file_path + ('_part%03d' % i_part) + scan_file_ext for i_part in range(1, n_part)] log_func.debug(u'Merge %d parts of scan %s to PDF file% s' % (n_part-1, part_pdf_filenames, scan_filename)) glue_result = pdf_func.glue_pdf_files(scan_filename, *part_pdf_filenames) dlg_func.openMsgBox(u'SCAN', u'Load documents in the scanner tray for later scanning') return glue_result else: log_func.warning(u'The mode of combining a scanned document in parts is canceled') return False
def getQueryTbl(self, report, db_url=None, sql=None, *args, **kwargs): """ Get query table. :param report: Report template data. :param db_url: Connection string as url. For example: postgresql+psycopg2://postgres:[email protected]:5432/realization. :param sql: SQL query. At the beginning of the SQL query should be a signature: <SQL:> - SQL query text <PY:> - SQL query text returned Python function. <PRG:> - SQL query text returned Python function. :return: Query table dictionary: {'__fields__': field_name_list, '__data__': table_data} """ query = None try: if sql: query = sql else: # Case when a query function returns a table if isinstance(report['query'], dict): return report['query'] elif self._isQueryFunc(report['query']): variables = kwargs.get('variables', None) query = self._execQueryFunc(report['query'], vars=variables) log_func.debug(u'Execute query func <%s>' % str(query)) if isinstance(query, dict): if '__sql__' in query: dataset_dict = self._getSQLQueryTable(report=report, db_url=db_url, sql=query['__sql__']) if isinstance(dataset_dict, dict): query.update(dataset_dict) return query else: log_func.warning(u'Error query result type <%s>' % query.__class__.__name__) else: query = report['query'] # Query not defined if query is None: log_func.warning(u'Query not defined') return None if query.startswith(SQL_SIGNATURE): # SQL expression query = query.replace(SQL_SIGNATURE, u'').strip() elif query.startswith(CODE_SIGNATURE): # Python function query = exec_func.execTxtFunction(query.replace(CODE_SIGNATURE, u'').strip()) elif query.startswith(PY_SIGNATURE): # Python query = exec_func.execTxtFunction(query.replace(PY_SIGNATURE, u'').strip()) else: log_func.warning(u'Not defined query signature <%s>' % query) return None # The request can be parameterized by variables passed explicitly. # Therefore, it is necessary to generate if kwargs: try: # For replacements, use another generator query_txt = query.replace(u'[&', u'{{ ').replace(u'&]', u' }}') query = txtgen_func.generate(query_txt, kwargs) except: log_func.fatal(u'Error transform query\n<%s>\nfor report query table' % str(query)) query_tbl = None if not self._query_table: # SQL expression query_tbl = self._getSQLQueryTable(report, db_url=db_url, sql=query) else: if isinstance(self._query_table, str): if self._query_table[:4].upper() == SQL_SIGNATURE: query_tbl = self._getSQLQueryTable(report, sql=self._query_table[4:].strip()) else: log_func.warning(u'Unsupported query type <%s>' % self._query_table) elif isinstance(self._query_table, dict): query_tbl = self._query_table else: log_func.warning(u'Unsupported query type <%s>' % type(self._query_table)) return query_tbl except: log_func.fatal(u'Error query table <%s>.' % query) return None
def generate(self, rep_template, query_table, name_space=None, coord_fill=None): """ Generate report. :param rep_template: Report template data. :param query_table: Query table: { '__name__': query table name, '__fields__': [field names], '__data__': [query table data], '__sub__': {sub report data}, }. :param name_space: Report name space. { 'variable name': variable value, }. This dictionary can be transmitted in the query table key __variables__. :param coord_fill: Coordinate filling in cell values. Format: { (row, col): 'value', }. This dictionary can be transmitted in the query table key __coord_fill__. :return: Generated report data. """ try: # Coordinate filling in cell values self._CoordFill = coord_fill if query_table and '__coord_fill__' in query_table: if self._CoordFill is None: self._CoordFill = dict() self._CoordFill.update(query_table['__coord_fill__']) # Group list self._RepGrp = list() # I. Define all bands in the template and amount cells if isinstance(rep_template, dict): self._Template = rep_template else: log_func.warning(u'Error report template type <%s>.' % type(rep_template)) return None # Init report name if 'name' in query_table and query_table['name']: # If the query table is named, then this is the name of the finished report self._RepName = str(query_table['name']) elif 'name' in self._Template: self._RepName = self._Template['name'] # Init name space self._NameSpace = name_space if self._NameSpace is None: self._NameSpace = dict() self._NameSpace.update(self._Template['variables']) if query_table and '__variables__' in query_table: self._NameSpace.update(query_table['__variables__']) if self._NameSpace: log_func.debug(u'Report variables: %s' % str(list(self._NameSpace.keys()))) # Style library self._StyleLib = None if 'style_lib' in self._Template: self._StyleLib = self._Template['style_lib'] self._TemplateSheet = self._Template['sheet'] self._TemplateSheet = self._initSumCells(self._TemplateSheet) # II. Init query table self._QueryTbl = query_table # Determine the number of records in the query table self._QueryTblRecCount = 0 if self._QueryTbl and '__data__' in self._QueryTbl: self._QueryTblRecCount = len(self._QueryTbl['__data__']) # Init group band for grp in self._Template['groups']: grp['old_rec'] = None time_start = time.time() log_func.info(u'Report <%s>. Generate start' % str_func.toUnicode(self._RepName)) # III. Fill report # Create report self._Rep = copy.deepcopy(REPORT_TEMPLATE) self._Rep['name'] = self._RepName # Init variables field_idx = dict() # Field indexes i = 0 i_rec = 0 # Iterate through the fields of a query table if self._QueryTbl and '__fields__' in self._QueryTbl: for cur_field in self._QueryTbl['__fields__']: field_idx[cur_field] = i i += 1 if self._QueryTblRecCount: # Init current record rec = self._QueryTbl['__data__'][i_rec] for field_name in field_idx.keys(): val = rec[field_idx[field_name]] self._CurRec[field_name] = val # Current record index self._CurRec['sys_num_rec_idx'] = i_rec # Upper if self._Template['upper']: self._genUpper(self._Template['upper']) # Header self._genHeader(self._Template['header']) # Main loop while i_rec < self._QueryTblRecCount: # Group # Check group change and find the index of the most common change group i_grp_out = -1 # Start generate flag start_gen = False for i_grp in range(len(self._Template['groups'])): grp = self._Template['groups'][i_grp] if grp['old_rec']: # Check group note output condition if self._CurRec[grp['field']] != grp['old_rec'][grp['field']]: i_grp_out = i_grp break else: i_grp_out = 0 start_gen = True break if i_grp_out != -1: # Display notes if start_gen is False: for i_grp in range(len(self._Template['groups'])-1, i_grp_out-1, -1): grp = self._Template['groups'][i_grp] self._genGrpFooter(grp) # Show headers for i_grp in range(i_grp_out, len(self._Template['groups'])): grp = self._Template['groups'][i_grp] grp['old_rec'] = copy.deepcopy(self._CurRec) self._genGrpHeader(grp) # Data area self._genDetail(self._Template['detail']) # Increase the sum of summing cells self._sumIterate(self._TemplateSheet, self._CurRec) # Next record i_rec += 1 # Set current record if i_rec < self._QueryTblRecCount: rec = self._QueryTbl['__data__'][i_rec] for field_name in field_idx.keys(): val = rec[field_idx[field_name]] self._CurRec[field_name] = val # Set current record index self._CurRec['sys_num_rec_idx'] = i_rec # Footer for i_grp in range(len(self._Template['groups'])-1, -1, -1): grp = self._Template['groups'][i_grp] if grp['old_rec']: self._genGrpFooter(grp) else: break self._genFooter(self._Template['footer']) # Under if self._Template['under']: self._genUnder(self._Template['under']) # Page setup self._Rep['page_setup'] = self._Template['page_setup'] log_func.info(u'Report <%s>. Generate end. Time: %d sec.' % (str_func.toUnicode(self._RepName), time.time()-time_start)) return self._Rep except: log_func.fatal(u'Error report generate') return None
def getReportList(report_dir, is_sort=True): """ Get report template list. :param report_dir: Report directory. :type is_sort: bool. :param is_sort: Sort list by name? :return: Format list: [ [ full file name / report directory, report name / directory, report description / directory, None / Nested Objects, image index ], . . . ] The description of the directory is taken from the descript.ion file, which should be in the same directory. If such a file is not found, then the directory description is empty. Nested objects is a list whose elements have the same structure. """ try: report_dir = os.path.abspath(os.path.normpath(report_dir)) log_func.debug(u'Report folder scan <%s>' % report_dir) dir_list = list() rep_list = list() sub_dirs = file_func.getSubDirs(report_dir) img_idx = 0 for sub_dir in sub_dirs: # Exclude not processed folders if os.path.basename(sub_dir) in NOT_WORK_DIRNAMES: continue description_file = None try: description_file = open(os.path.join(sub_dir, 'descript.ion'), 'rt') dir_description = description_file.read() description_file.close() except: if description_file: description_file.close() dir_description = sub_dir data = [ sub_dir, os.path.basename(sub_dir), dir_description, getReportList(sub_dir, is_sort), img_idx ] dir_list.append(data) if is_sort: dir_list.sort(key=lambda i: i[2]) filename_mask = os.path.join(report_dir, '*%s' % REPORT_FILENAME_EXT) file_rep_list = [ filename for filename in file_func.getFilesByMask(filename_mask) ] for rep_file_name in file_rep_list: rep_struct = res_func.loadResourcePickle(rep_file_name) img_idx = 2 try: if rep_struct['generator'][-3:].lower() == 'xml': img_idx = 1 except: log_func.warning(u'Report type definition error') try: data = [ rep_file_name, rep_struct['name'], rep_struct['description'], None, img_idx ] rep_list.append(data) except: log_func.fatal(u'Error reading report template <%s>' % rep_file_name) if is_sort: rep_list.sort(key=lambda i: i[2]) return dir_list + rep_list except: log_func.fatal( u'Error filling out information about report files <%s>.' % report_dir) return list()