def __init__(self, parent): super(google_drive, self).__init__(parent, self.parameters) self.importDataFromService = QgisODKimportDataFromService(self.module) self.authorization = None self.verification = None self.client_id = "88596974458-r5dckj032ton00idb87c4oivqq2k1pks.apps.googleusercontent.com" self.client_secret = "c6qKnhBdVxkPMH88lHf285hQ" self.getCollectors()
def __init__(self, parent, parameters): super(external_service, self).__init__(parent) self.parent = parent self.module = parent.parent().parent().parent().module self.importDataFromService = QgisODKimportDataFromService(self) self.iface = parent.parent().parent().parent().module.iface self.resize(QSize(310,260)) self.setColumnCount(2) self.setColumnWidth(0, 152) self.setColumnWidth(1, 152) self.setRowCount(len(parameters)-1) self.verticalHeader().hide() self.horizontalHeader().hide() S = QSettings() for row,parameter in enumerate(parameters): if row == 0: self.service_id = parameter[1] continue row = row -1 pKey = QTableWidgetItem (parameter[0]) pKey.setFlags(pKey.flags() ^ Qt.ItemIsEditable) pValue = QTableWidgetItem (parameter[1]) self.setItem(row,0,pKey) valueFromSettings = S.value("qgisodk/%s/%s/" % (self.service_id,self.item(row,0).text()), defaultValue = "undef") if not valueFromSettings or valueFromSettings == "undef": self.setItem(row,1,pValue) S.setValue("qgisodk/%s/%s/" % (self.service_id,self.item(row,0).text()),parameter[1]) else: self.setItem(row,1,QTableWidgetItem (valueFromSettings))
class google_drive(external_service): parameters = [["id", "google_drive"], ["google drive login", ""], ["data collectors emails", ""], ["folder", ""], ["data collection table ID", ""], ["notifications?(YES/NO)", "YES"]] def __init__(self, parent): super(google_drive, self).__init__(parent, self.parameters) self.importDataFromService = QgisODKimportDataFromService(self.module) self.authorization = None self.verification = None self.client_id = "88596974458-r5dckj032ton00idb87c4oivqq2k1pks.apps.googleusercontent.com" self.client_secret = "c6qKnhBdVxkPMH88lHf285hQ" self.getCollectors() def collectData(self): ''' interactive table selection ''' #remoteTableName, response = self.getAvailableDataCollections() remoteTableName = QgisODKImportCollectedData.getXFormID(self) if remoteTableName: remoteTableID = self.getIdFromName(remoteTableName) else: self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("no data collect table selected"), level=QgsMessageBar.CRITICAL, duration=6) return if remoteTableID != '': remoteTable = self.getTable(remoteTableID) remoteTableMetadata = self.getMetadataFromID(remoteTableID) self.importDataFromService.view(remoteTableMetadata, remoteTable) else: self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("undefined data collect table ID"), level=QgsMessageBar.CRITICAL, duration=6) def getCollectors(self): collectorsFromParams = self.getValue("data collectors emails").split( ' ') self.collectors = [] email_regex = re.compile( r"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$") for collector in collectorsFromParams: if email_regex.match(collector): self.collectors.append(collector) def send_message(self, FROM, TO, SUBJECT, MSG): if not self.authorization: self.get_authorization() # create a message to send message = MIMEText(MSG.encode('utf-8'), 'plain', 'utf-8') message['to'] = TO message['from'] = FROM message['subject'] = SUBJECT.encode('utf-8') body = {'raw': base64.b64encode(message.as_string())} url = 'https://www.googleapis.com/gmail/v1/users/me/messages/send' headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']), 'Content-Type': 'application/json' } response = requests.post(url, headers=headers, data=json.dumps(body), proxies=self.getProxiesConf()) def notify(self, XFormName, XFormFolder, collectTableId, collectTableName): if self.getValue('notifications?(YES/NO)').upper() == 'YES': message = self.tr(''' You are receiving this automatically generated message because you are taking part to a Open Data Kit survey Your ODK Collect app has to be configured with the following parameters: a new form called %s has been uploaded in the folder %s shared with you The Data Collection table is named %s and has the following uri: https://docs.google.com/spreadsheets/d/%s/edit ''') % (XFormName, XFormFolder, collectTableName, collectTableId) for email in self.collectors: self.send_message(self.getValue('google drive login'), email, 'ODK survey notification', message) def shareFileWithCollectors(self, id, role='reader', type='user'): url = 'https://www.googleapis.com/drive/v3/files/%s/permissions' % id headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']), 'Content-Type': 'application/json' } for email in self.collectors: metadata = {"role": role, "type": type, "emailAddress": email} response = requests.post(url, headers=headers, data=json.dumps(metadata), proxies=self.getProxiesConf()) def getExportMethod(self): return 'exportXForm' def getExportExtension(self): return 'xml' def getIdFromName(self, fileName, mimeType=None): if not self.authorization: self.get_authorization() url = 'https://www.googleapis.com/drive/v3/files' headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']) } params = {"q": "name = '%s'" % fileName, "spaces": "drive"} response = requests.get(url, headers=headers, params=params, proxies=self.getProxiesConf()) if response.status_code == requests.codes.ok: found = response.json() files = found['files'] if len(files) > 0: if mimeType: if files[0]['mimeType'] == mimeType: return files[0]['id'] else: return None else: return files[0]['id'] else: return None def getMetadataFromID(self, fileID): if not self.authorization: self.get_authorization() url = 'https://www.googleapis.com/drive/v3/files/' + fileID headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']) } response = requests.get(url, headers=headers, proxies=self.getProxiesConf()) if response.status_code == requests.codes.ok: return response.json() else: return None def getAvailableDataCollections(self): if not self.authorization: self.get_authorization() url = 'https://www.googleapis.com/drive/v3/files' headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']) } folderID = self.getIdFromName( self.getValue('folder'), mimeType='application/vnd.google-apps.folder') params = { "q": "mimeType = 'application/vnd.google-apps.spreadsheet' and '%s' in parents" % folderID, "spaces": "drive" } response = requests.get(url, headers=headers, params=params, proxies=self.getProxiesConf()) if response.status_code == requests.codes.ok: files = response.json()["files"] filesList = [] for file in files: filesList.append(file["name"]) if filesList != []: return filesList, response else: return None, response else: return None, response def createNew(self, name, mimeType, parentsId=None): if not self.authorization: self.get_authorization() if mimeType == 'application/vnd.google-apps.folder' and name == '': return 'root' foundId = self.getIdFromName(name, mimeType=mimeType) if foundId: return foundId else: url = 'https://www.googleapis.com/drive/v3/files' headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']), 'Content-Type': 'application/json' } metadata = {"name": name, "mimeType": mimeType} if parentsId: metadata['parents'] = [parentsId] response = requests.post(url, headers=headers, data=json.dumps(metadata), proxies=self.getProxiesConf()) if response.status_code != 200 or 'error' in response.json(): return None return response.json()['id'] def get_verification(self): #phase 1 oauth2 verification_params = { 'response_type': 'code', 'client_id': self.client_id, 'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob:auto', # 'scope': 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/gmail.send', #'https://www.googleapis.com/auth/drive.file', 'login_hint': self.getValue('google drive login') } response = requests.post( 'https://accounts.google.com/o/oauth2/v2/auth', params=verification_params, proxies=self.getProxiesConf()) if response.status_code == requests.codes.ok: self.verification = internalBrowser.getCode( response.text, self.getValue('google drive login')) def get_authorization(self): #phase 2 oauth2 if not self.verification: self.get_verification() authorization_params = { 'client_id': self.client_id, 'client_secret': self.client_secret, 'code': self.verification, 'grant_type': 'authorization_code', 'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob:auto' } response = requests.post('https://www.googleapis.com/oauth2/v4/token', params=authorization_params, proxies=self.getProxiesConf()) if response.status_code == requests.codes.ok: authorization = response.json() if 'error' in authorization: QMessageBox().warning( None, "Google authorization error", "Google authorization error: %s" % authorization['error']) self.verification = None self.authorization = None else: self.authorization = authorization elif response.status_code == 401: # token expired refresh_params = { 'client_id': self.client_id, 'client_secret': self.client_secret, 'refresh_token': self.authorization['refresh_token'], 'grant_type': 'refresh_token' } else: pass #print response.reason def _getLayer(self, remoteTableName): if self.getValue("data collection table ID") == '': self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("undefined data collect table ID"), level=QgsMessageBar.CRITICAL, duration=6) return if not self.authorization: self.get_authorization() metadata = self.getMetadataFromID( self.getValue("data collection table ID")) if metadata: layerName = metadata['name'] else: layerName = self.tr('collected-data') remoteData = self.getTable() geojson = self.getLayerFromTable(remoteData) if geojson: workDir = QgsProject.instance().readPath("./") geoJsonFileName = layerName + '_odk-' + time.strftime( "%d-%m-%Y") + '.geojson' with open(os.path.join(workDir, geoJsonFileName), "w") as geojson_file: geojson_file.write(json.dumps(geojson)) layer = self.iface.addVectorLayer(os.path.join(workDir, layerName), layerName[:-8], "ogr") QgsMapLayerRegistry.instance().addMapLayer(layer) else: self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("error loading csv table")) def getLayerFromTable( self, remoteData, ): geojson = { "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, "features": [] } for record in remoteData: if 'GEOMETRY' in record: geomType, geomCoordinates = self.guessGeomType( record['GEOMETRY']) feature = { "type": "Feature", "properties": {}, "geometry": { "type": geomType } } # build geojson geometry jsonCoordinates = [] for geomCoordinate in geomCoordinates: jsonCoordinates.append( [float(geomCoordinate[1]), float(geomCoordinate[0])]) if geomType == 'Point': feature["geometry"]["coordinates"] = [ jsonCoordinates[0][0], jsonCoordinates[0][1] ] if geomType == 'LineString': feature["geometry"]["coordinates"] = jsonCoordinates if geomType == 'Polygon': feature["geometry"]["coordinates"] = [jsonCoordinates] else: feature = { "type": "Feature", "properties": {}, "geometry": None } # build geojson properties for fieldKey, fieldValue in record.iteritems(): if not fieldKey in ('GEOMETRY', ): # field exclusion feature["properties"][fieldKey] = fieldValue geojson["features"].append(feature) #metadata = self.getMetadataFromID(self.getValue("data collection table ID")) #if metadata: # layerName = metadata['name'] #else: # layerName = self.tr('collected-data') return geojson def getTable(self, tableID): if not self.authorization: self.get_authorization() #step1 - verify if form exists: url = 'https://docs.google.com/spreadsheets/d/%s/export?format=csv&id=%s&gid=0' % ( tableID, self.getValue("data collection table ID")) headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']), 'Content-Type': 'application/json' } response = requests.get(url, headers=headers) if response.status_code == 200: csvIO = StringIO.StringIO(response.text) csvIn = csv.DictReader(csvIO, delimiter=',', quotechar='"') csvList = [] for row in csvIn: csvList.append(row) geometryField = '' for field in csvList[0].keys(): if 'GEOMETRY' in field.upper(): geometryField = field prefix = field[:-8] len_prefix = len(field) - 8 newCsvList = [] for row in csvList: newRow = {} for field in row.keys(): newRow[field[len_prefix:]] = row[field] # remap field newCsvList.append(newRow) return newCsvList else: print "getTable", response, response.text def setDataSubmissionTable(self, xForm_id): if not self.authorization: self.get_authorization() headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']) } folderId = self.createNew(self.getValue('folder'), 'application/vnd.google-apps.folder') self.shareFileWithCollectors(folderId, role='reader') content_table_id = self.createNew( xForm_id[:-4] + "-collect-table", 'application/vnd.google-apps.spreadsheet', parentsId=folderId) self.shareFileWithCollectors(content_table_id, role='writer') if content_table_id: url = 'https://www.googleapis.com/drive/v3/files/' + content_table_id response = requests.get(url, headers=headers, proxies=self.getProxiesConf()) self.notify(xForm_id, self.getValue('folder'), response.json()['id'], response.json()['name']) return 'https://docs.google.com/spreadsheets/d/%s/edit' % response.json( )['id'] else: return None def sendForm(self, xForm_id, xForm): if not self.authorization: self.get_authorization() xForm_id += '.xml' fileId = self.getIdFromName(xForm_id) folderId = self.createNew(self.getValue('folder'), 'application/vnd.google-apps.folder') self.shareFileWithCollectors(folderId, role='reader') metadata = { "name": xForm_id, "description": 'uploaded by QGISODK plugin' } if fileId: method = "PATCH" url = 'https://www.googleapis.com/upload/drive/v3/files/%s?uploadType=multipart' % fileId else: method = "POST" url = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart' metadata['parents'] = [folderId] headers = { 'Authorization': 'Bearer {}'.format(self.authorization['access_token']) } data = ('metadata', json.dumps(metadata), 'application/json; charset=UTF-8') file = (xForm, open(xForm, 'r'), 'text/xml') files = {'data': data, 'file': file} response = requests.request(method, url, headers=headers, files=files, proxies=self.getProxiesConf()) self.shareFileWithCollectors(response.json()['id'], role='reader') return response
def __init__(self, parent): super(aggregate, self).__init__(parent, self.parameters) self.importDataFromService = QgisODKimportDataFromService(self.module)
class aggregate(external_service): parameters = [["id", "aggregate"], ["url", ''], ["lastID", ''], ["user", ''], ["password", '']] def __init__(self, parent): super(aggregate, self).__init__(parent, self.parameters) self.importDataFromService = QgisODKimportDataFromService(self.module) def getExportMethod(self): return 'exportXForm' def getExportExtension(self): return 'xml' def getFormList(self, xForm_id): method = 'GET' url = self.getValue('url') + '//formList' response = requests.request(method, url) root = ET.fromstring(response.content) keylist = [ form.attrib['url'].split('=')[1] for form in root.findall('form') ] return xForm_id in keylist, response def sendForm(self, xForm_id, xForm): # step1 - verify if form exists: form_key, response = self.getFormList(xForm_id) if response.status_code != requests.codes.ok: return response if form_key: method = 'POST' url = self.getValue('url') + '//formUpload' else: method = 'POST' url = self.getValue('url') + '//formUpload' # method = 'POST' # url = self.getValue('url')+'//formUpload' #step1 - upload form: POST if new PATCH if exixtent files = open(xForm, 'r') files = {'form_def_file': files} response = requests.request(method, url, files=files, proxies=self.getProxiesConf()) return response def collectData(self, layer): if not layer: return XFormKey = layer.name() response, remoteTable = self.getTable(XFormKey) if response.status_code == 200: print 'before Update Layer' if remoteTable: print 'table have some data' # self.importDataFromService.updateLayer(layer,remoteTable) else: self.iface.messageBar().pushMessage(self.tr("QGISODK plugin"), self.tr("Form is invalid"), level=QgsMessageBar.CRITICAL, duration=6) def getTable(self, XFormKey): url = self.getValue('url') + '/view/submissionList?formId=' + XFormKey method = 'GET' try: response = requests.request(method, url, proxies=self.getProxiesConf()) # Read instance id except: print 'not able to downlaod' sys.exit() table = [] if not response.status_code == 200: return response, table root = ET.fromstring(response.content) ns = '{http://opendatakit.org/submissions}' instance_ids = [child.text for child in root[0].findall(ns + 'id')] print 'instance ids before filter', instance_ids lastID = self.getValue('lastID') print 'lastID is', lastID lastindex = 0 try: lastindex = instance_ids.index(lastID) except: print 'first download' ns1 = '{http://www.opendatakit.org/cursor}' lastReturnedURI = ET.fromstring( root[1].text).findall(ns1 + 'uriLastReturnedValue')[0].text print 'server lastID is', lastReturnedURI if lastID == lastReturnedURI: print 'No Download returning' return response, table instance_ids = instance_ids[lastindex:] print 'downloading', instance_ids for id in instance_ids: if id: url = self.getValue( 'url' ) + '/view/downloadSubmission?formId={}[@version=null and @uiVersion=null]/{}[@key={}]'.format( XFormKey, XFormKey, id) try: response = requests.request(method, url) except: print 'not able to fetch' if not response.status_code == 200: return response root1 = ET.fromstring(response.content) data = root1[0].findall(ns + XFormKey) dict = { child.tag.replace(ns, ''): child.text for child in data[0] } mediaFile = root1.findall(ns + 'mediaFile') if len(mediaFile) > 0: mediaDict = { child.tag.replace(ns, ''): child.text for child in mediaFile[0] } for key, value in dict.iteritems(): if value == mediaDict['filename']: dict[key] = self.importDataFromService.cleanURIm( mediaDict['downloadUrl'], XFormKey, value) table.append(dict) self.getValue('lastID', lastReturnedURI) return response, table
class ona(external_service): parameters = [ ["id", "ona.io"], ["name", ""], ["project_id", ""], ["user", ""], ["password", ""], ] def __init__(self, parent): super(ona, self).__init__(parent, self.parameters) self.importDataFromService = QgisODKimportDataFromService(self.module) def getExportMethod(self): return 'exportXlsForm' def getExportExtension(self): return 'xls' def getAvailableDataCollections(self): url = 'https://api.ona.io/api/v1/projects/%s/forms' % self.getValue( "project_id") response = requests.get(url, auth=requests.auth.HTTPBasicAuth( self.getValue("user"), self.getValue("password")), proxies=self.getProxiesConf()) if response.status_code != requests.codes.ok: return None, response forms = response.json() availableDataCollections = [] for form in forms: availableDataCollections.append(form["id_string"]) return availableDataCollections, response def formIDToPk(self, xForm_id): #verify if form exists: url = 'https://api.ona.io/api/v1/projects/%s/forms' % self.getValue( "project_id") response = requests.get(url, auth=requests.auth.HTTPBasicAuth( self.getValue("user"), self.getValue("password")), proxies=self.getProxiesConf()) if response.status_code != requests.codes.ok: self.iface.messageBar().pushMessage(self.tr("QGISODK plugin"), self.tr("Response is not Ok"), level=QgsMessageBar.CRITICAL, duration=6) return None, response forms = response.json() form_key = None for form in forms: if form['sms_id_string'] == xForm_id: form_key = form['formid'] break return form_key, response def sendForm(self, xForm_id, xForm): #step1 - verify if form exists: form_key, response = self.formIDToPk(xForm_id) if response.status_code != requests.codes.ok: return response if form_key: method = 'PATCH' url = 'https://api.ona.io/api/v1/forms/%s' % form_key else: method = 'POST' url = 'https://api.ona.io/api/v1/projects/%s/forms' % self.getValue( "project_id") #step1 - upload form: POST if new PATCH if exixtent files = { 'xls_file': (xForm, open(xForm, 'rb'), 'application/vnd.ms-excel', { 'Expires': '0' }) } response = requests.request( method, url, files=files, auth=requests.auth.HTTPBasicAuth(self.getValue("user"), self.getValue("password")), proxies=self.getProxiesConf() ) #, proxies = proxyDict,headers={'Content-Type': 'application/octet-stream'}) return response def getUUIDfield(self): return '_uuid' def getJSON(self, xForm_id): #step1 - verify if form exists: form_key, response = self.formIDToPk(xForm_id) if response.status_code != requests.codes.ok: return response if form_key: url = 'https://api.ona.io/api/v1/data/%s' % form_key response = requests.get(url, auth=requests.auth.HTTPBasicAuth( self.getValue("user"), self.getValue("password")), proxies=self.getProxiesConf()) return response def setDataSubmissionTable(self, xForm_id): return None #defined by ona.io def collectData(self, layer=None): if (layer == None): ''' interactive table selection ''' XFormID = QgisODKImportCollectedData.getXFormID(self) if XFormID: XFormKey, response = self.formIDToPk(XFormID) response, remoteTable = self.getTable(XFormKey) self.importDataFromService.view(XFormID, remoteTable) else: self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("no data collect table selected"), level=QgsMessageBar.CRITICAL, duration=6) else: XFormID = layer.name().lower() if XFormID: XFormKey, response = self.formIDToPk(XFormID) response, remoteTable = self.getTable(XFormKey) self.importDataFromService.getData(XFormID, remoteTable, layer) self.importDataFromService.writeLayer(layer) else: self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("Form is invalid"), level=QgsMessageBar.CRITICAL, duration=6) def getTable(self, form_key): #step1 - verify if form exists: #form_key, response = self.formIDToPk(xForm_id) #if response.status_code != requests.codes.ok: # self.iface.messageBar().pushMessage(self.tr("QgisODK plugin"), self.tr("error loading csv table %s, %s.") % ( # response.status_code, response.reason), level=QgsMessageBar.CRITICAL, duration=6) # return response, None if form_key: url = 'https://api.ona.io/api/v1/data/%s.csv' % form_key response = requests.get(url, auth=requests.auth.HTTPBasicAuth( self.getValue("user"), self.getValue("password")), proxies=self.getProxiesConf()) if response.status_code == 200: csvIO = StringIO.StringIO(response.text) csvIn = csv.DictReader(csvIO, delimiter=',', quotechar='"') csvList = [] for row in csvIn: remappedRow = {} for key, value in row.iteritems(): if '/' in key: cleanedKey = key.split('/')[-1] remappedRow[cleanedKey] = value else: remappedRow[key] = value csvList.append(remappedRow) return response, csvList else: self.iface.messageBar().pushMessage( self.tr("QGISODK plugin"), self.tr("error loading csv table %s, %s.") % (response.status_code, response.reason), level=QgsMessageBar.CRITICAL, duration=6) return response, None def getLayerFromTable(self, remoteData, downloadAttachements=None): #remoteResponse = self.getJSON(xForm_id) #response, remoteData = self.getTable(xForm_id) geojson = { "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, "features": [] } for record in remoteData: if 'GEOMETRY' in record: geomType, geomCoordinates = self.guessGeomType( record['GEOMETRY']) feature = { "type": "Feature", "properties": {}, "geometry": { "type": geomType } } #build geojson geometry jsonCoordinates = [] for geomCoordinate in geomCoordinates: jsonCoordinates.append( [float(geomCoordinate[1]), float(geomCoordinate[0])]) if geomType == 'Point': feature["geometry"]["coordinates"] = [ jsonCoordinates[0][0], jsonCoordinates[0][1] ] if geomType == 'LineString': feature["geometry"]["coordinates"] = jsonCoordinates if geomType == 'Polygon': feature["geometry"]["coordinates"] = [jsonCoordinates] else: feature = { "type": "Feature", "properties": {}, "geometry": None } #recode attachments: attachements = {} if '_attachments' in record: for attachment in record['_attachments']: fileKey = attachment["download_url"].split('/')[-1] #todo local download attachment files if option is checked attachements[fileKey] = 'https://api.ona.io' + attachment[ "download_url"] if downloadAttachements and QgsProject.instance().readPath( "./") != "./": downloadDir = os.path.join( QgsProject.instance().readPath("./"), 'attachments_%s_%s' % (self.getValue("name"), self.getValue("project_id"))) if not os.path.exists(downloadDir): os.makedirs(downloadDir) response = requests.get(attachements[fileKey], stream=True, proxies=self.getProxiesConf()) localAttachmentPath = os.path.abspath( os.path.join(downloadDir, fileKey)) if response.status_code == 200: with open(localAttachmentPath, 'wb') as f: for chunk in response: f.write(chunk) attachements[fileKey] = localAttachmentPath else: attachements[fileKey] = '' #build geojson properties for fieldKey, fieldValue in record.iteritems(): if not fieldKey in ( 'GEOMETRY', '_attachments', '_tags', '_notes', '_bamboo_dataset_id', '_geolocation'): # field exclusion to verify if fieldValue in attachements.keys(): fieldValue = attachements[fieldValue] if "/" in fieldKey: #check if grouped Field cleanedKey = fieldKey.split("/")[-1] else: cleanedKey = fieldKey fieldRemap = self.module.dlg.treeView.mapNameTofield( cleanedKey ) #try to remap field name to existing field using map to property feature["properties"][fieldRemap] = fieldValue geojson["features"].append(feature) return geojson