def slot_reEnable(self, arg): """Handle updating and creation of database from a configuration file. Here the configuration editor has finished. """ self.gui.setEnabled(True) if not self.configEd.getSourcePath(): message(_("No data source")) return errors = self.configEd.getErrorCount() if (errors > 0): message(_("%d files containing errors found") % errors) return source = CfgZip(self.configEd.getSourcePath()) if not source.isOpen(): warning( _("The supplied configuration file (%s) could not" " be opened. Actually this shouldn't be possible!") % self.configEd.getSourcePath()) return if self.newdb: self.createNewDb(source) else: self.updateDbConfig(source) source.close()
def slot_openConfig(self, force): """ """ zpath = self.settings.getSetting("configFile") if force or (not os.path.isfile(zpath)): dir = os.path.dirname(str(zpath)) if not os.path.isdir(dir): dir = None zpath = getFile(_("Choose Configuration File"), startDir=dir, defaultSuffix="zip", filter=(_("zip Files"), ("*.zip",)), create=True) if not zpath: return False if isinstance(zpath, unicode): zpath = zpath.encode('utf8') self.save() # If a folder is open, save it and close it self.source = CfgZip(zpath) if not self.source.isOpen(): warning(_("The supplied configuration file (%s) could" " not be opened. A completely empty configuration" " will be created.") % zpath) self.baseFolder = os.path.dirname(zpath) self.gui.setTitle(_("Config Editor: %s") % zpath) self.gui.setLabel(_("For database: %s") % self.source.cfgName) self.settings.setSetting("configFile", zpath) self.buildTree() return True
def saveData(self, filePath, imageX=None): """Save all the data to a zip file. If imageX is a directory path, get the images from there instead of from the source file """ dest = CfgZip(filePath, write=True) for f, spec in self.fileDict.items(): if f.endswith('/'): continue if (spec[1] == 'i'): if not imageX: # Image file, copy it from the source dest.addFile(f, self.source.getFile(f)) else: # Information file, write it in 'sini'-format dest.addFile(f, self.writeSection(f, spec[4], spec[3])) if imageX: # Copy in all files ending .svg and .jpg from the given folder try: tlist = ["teachers/" + i for i in os.listdir(imageX + "/teachers")] except: tlist = [] try: ilist = os.listdir(imageX) except: ilist = [] imlist = ilist + tlist for i in imlist: if i.endswith(".svg") or i.endswith(".jpg"): fpath = [imageX] + i.split('/') dest.addFromFile(os.path.join(*fpath), "imagefiles/" + i) dest.close()
def slot_reEnable(self, arg): """Handle updating and creation of database from a configuration file. Here the configuration editor has finished. """ self.gui.setEnabled(True) if not self.configEd.getSourcePath(): message(_("No data source")) return errors = self.configEd.getErrorCount() if ( errors > 0): message(_("%d files containing errors found") % errors) return source = CfgZip(self.configEd.getSourcePath()) if not source.isOpen(): warning(_("The supplied configuration file (%s) could not" " be opened. Actually this shouldn't be possible!") % self.configEd.getSourcePath()) return if self.newdb: self.createNewDb(source) else: self.updateDbConfig(source) source.close()
def restoreConfigFile(self, db): """Get a parent directory for the creation of a data file. The file to be created may not exist already. Returns the file-name if successful, otherwise 'None'. """ # put up directory dialog, starting one up from dir0 dir = getDirectory(_("Parent Folder")) if not dir: return None datapath = os.path.join(dir, self.dbname + '.zip') if os.path.exists(datapath): message(_("'%1' already exists"), (datapath, )) return None dest = CfgZip(datapath, True) # open for writing if not dest.isOpen(): message(_("Couldn't open '%s' for writing") % datapath) return None for path, data in db.getAllData(): dest.addFile(path, data) dest.close() message(_("Configuration saved to '%s'") % datapath) return datapath
def restoreConfigFile(self, db): """Get a parent directory for the creation of a data file. The file to be created may not exist already. Returns the file-name if successful, otherwise 'None'. """ # put up directory dialog, starting one up from dir0 dir = getDirectory(_("Parent Folder")) if not dir: return None datapath = os.path.join(dir, self.dbname + '.zip') if os.path.exists(datapath): message(_("'%1' already exists"), (datapath,)) return None dest = CfgZip(datapath, True) # open for writing if not dest.isOpen(): message(_("Couldn't open '%s' for writing") % datapath) return None for path, data in db.getAllData(): dest.addFile(path, data) dest.close() message(_("Configuration saved to '%s'") % datapath) return datapath
class ConfigEd: def __init__(self, settings): self.settings = settings slot("ced_open", self.slot_openConfig) slot("ced_edit", self.slot_valueChange) slot("ced_newItem", self.slot_switchNode) slot("ced_clone", self.slot_clone) slot("ced_delete", self.slot_delete) slot("ced_comment", self.slot_commentChanged) slot("ced_pupils", self.slot_getPupils) slot("ced_pix", self.slot_replacePix) slot("ced_tempsave", self.slot_tempsave) def init(self, gui): self.sourcePath = None # For the control panel only self.gui = gui self.baseFolder = None # For the folder containing the active file self.validationObject = Validate() if not self.slot_openConfig(False): self.gui.close() def slot_openConfig(self, force): """ """ zpath = self.settings.getSetting("configFile") if force or (not os.path.isfile(zpath)): dir = os.path.dirname(str(zpath)) if not os.path.isdir(dir): dir = None zpath = getFile(_("Choose Configuration File"), startDir=dir, defaultSuffix="zip", filter=(_("zip Files"), ("*.zip",)), create=True) if not zpath: return False if isinstance(zpath, unicode): zpath = zpath.encode('utf8') self.save() # If a folder is open, save it and close it self.source = CfgZip(zpath) if not self.source.isOpen(): warning(_("The supplied configuration file (%s) could" " not be opened. A completely empty configuration" " will be created.") % zpath) self.baseFolder = os.path.dirname(zpath) self.gui.setTitle(_("Config Editor: %s") % zpath) self.gui.setLabel(_("For database: %s") % self.source.cfgName) self.settings.setSetting("configFile", zpath) self.buildTree() return True def buildTree(self): self.currentSpec = None self.fileDict = {} self.nodeDict = {} self.validationObject.init(self.fileDict) self.gui.clearTable() self.gui.clearTree() self.readLevel(dataDefinition(), "") self.checkAll() def pathExists(self, path): return (self.fileDict.get(path) != None) def checkAll(self): errorTags = self.validationObject.validateAll() self.gui.setTreeNodeColours(errorTags) def readLevel(self, itemlist, path): for item in itemlist: iname = item[0] itype = item[1] # If it's a directory, make directory sub-nodes if (itype == "/"): if iname: self.addDir(iname, item, path) else: # If the name is empty, all existing directories count dirs = self.source.listDirs(path) if not dirs: # Create a 'default' directory ... self.addDir("default", item, path) else: dirs.sort() for iname in dirs: self.addDir(iname, item, path) # Files elif (itype == "i"): # Image files - just make nodes for them. # Only 'empty' file-names (in the spec) are covered # at present! files = self.source.listFiles(path) files.sort() for f in files: self.addnode(f, joinPath(path, f), item) elif not iname: # The file-name can be 'empty', in which case all # existing files count, except any starting with '_' files = [f for f in self.source.listFiles(path) if not f.startswith('_')] if not files: # Create a 'default' file ... self.addFile(path, FDEFAULT, item) else: files.sort() for f in files: self.addFile(path, f, item) continue else: self.addFile(path, iname, item) def addDir(self, iname, item, path): """Add a directory node to the tree structure. """ newpath = joinPath(path, iname) self.addnode(iname, newpath + '/', item) self.readLevel(item[3:], newpath) def addFile(self, path, iname, item, text=None): """Add a file to the tree structure. If no text is supplied, get it from the file. """ newpath = joinPath(path.rstrip('/'), iname) if (text == None): text = self.source.getFile(newpath) dic = getsini(text) sec = self.buildSection(item[3:], dic) comment = dic["comment"] self.addnode(iname, newpath, list(item[:3]) + [comment, sec]) def addnode(self, iname, path, ispec): """Add a node to the 'directory' tree. """ # There is the specification record for that path self.fileDict[path] = ispec # And there is the graphical node, which just records the path. # Get the parent (gui) node ps = path.rstrip('/').rsplit('/', 1) if (len(ps) == 1): parent = None else: parent = self.nodeDict[ps[0] + '/'] node = self.gui.addnode(parent, iname, path, ispec[1], ispec[2]) self.nodeDict[path] = node def buildSection(self, spec, vdict): """Build a list representing a section containing field definitions. It will take on the values from the given dictionary, leaving out those not in the specification list and adding default values for those not in the dictionary. """ slist = [] for field, ftype, tip in spec: ft = ftype.split("|") val = vdict.get(field) if (val == None): # Get the default value if (len(ft) > 1): val = ft[1] else: val = "" if (ft[0]== "bool"): if (val == ""): val = "0" elif (val != "0"): val = "1" slist.append([field, val, ft, tip]) return slist def writeSection(self, path, flist, comment): """Build a sini-file from the given data. Return the text of the file. """ text = '' clines = comment.splitlines() if clines: for cl in clines: text += '#%s\n' % cl for fspec in flist: text += '%s = %s\n' % (fspec[0], fspec[1]) return text def saveData(self, filePath, imageX=None): """Save all the data to a zip file. If imageX is a directory path, get the images from there instead of from the source file """ dest = CfgZip(filePath, write=True) for f, spec in self.fileDict.items(): if f.endswith('/'): continue if (spec[1] == 'i'): if not imageX: # Image file, copy it from the source dest.addFile(f, self.source.getFile(f)) else: # Information file, write it in 'sini'-format dest.addFile(f, self.writeSection(f, spec[4], spec[3])) if imageX: # Copy in all files ending .svg and .jpg from the given folder try: tlist = ["teachers/" + i for i in os.listdir(imageX + "/teachers")] except: tlist = [] try: ilist = os.listdir(imageX) except: ilist = [] imlist = ilist + tlist for i in imlist: if i.endswith(".svg") or i.endswith(".jpg"): fpath = [imageX] + i.split('/') dest.addFromFile(os.path.join(*fpath), "imagefiles/" + i) dest.close() def slot_tempsave(self, arg): self.save(True) def save(self, onlytemp=False, force=False, imageX=None): """Save the data to a temporary file, close it and then ask if the original file should be replaced by it. If onlytemp is True, stop after saving the data to the temporary file, leaving the source file open, and fail if that didn't work first time. Otherwise the possibility of entering an alternative save path will be given. If force is True, don't ask, just do everything. If imageX is a directory path, get the images from there instead of from the source file """ if not self.baseFolder: return cfgFile = os.path.join(self.baseFolder, self.source.cfgName + '.zip') tmpsave = cfgFile + '_' backup = cfgFile + '~' while True: try: if os.path.exists(tmpsave): os.remove(tmpsave) self.saveData(tmpsave, imageX) if onlytemp: messageDialog(_("Information"), _("Saved as '%s'") % tmpsave) return self.source.close() break except: traceback.print_exc() warning(_("Could not save data to '%s'") % tmpsave) if onlytemp: return tmpsave = getFile(_("Save file to"), startDir=os.path.dirname(tmpsave), defaultSuffix="zip", filter=(_("zip Files"), ("*.zip",)), create=True) if isinstance(tmpsave, unicode): tmpsave = tmpsave.encode('utf8') if not tmpsave: return if (not force) and (not confirmationDialog(_("Replace existing file?"), _("The changes have been saved to '%s'.\n" "Should this now replace the previous data?") % tmpsave)): return try: if os.path.exists(backup): os.remove(backup) if os.path.exists(cfgFile): os.rename(cfgFile, backup) os.rename(tmpsave, cfgFile) if imageX: self.baseFolder = None self.slot_openConfig(False) else: # For the control panel, to indicate that the file has # been saved: self.sourcePath = cfgFile self.validationObject.validateAll() self.errorCount = self.validationObject.errorCount except: traceback.print_exc() warning(_("Couldn't update configuration file '%s'") % cfgFile) # self.currentSection is a list of field-item lists, in display order. # These consist of [field-name, value, field-type-list, tip]. # The field-type list is the field specifier split at '|'. def slot_valueChange(self, rv): if self.disableChange: return row, value = rv entry = self.currentSection[row] entry[1] = value # This is to allow checking of changes to the error state of # the file newERC = self.errorRowCount if self.rowErrors[row]: newERC -= 1 # Set the error state for the particular field vError = self.validate(value, entry[2]) self.gui.setValid(row, vError) # Continue with checking the file error state self.rowErrors[row] = vError if vError: newERC += 1 if ((newERC != self.errorRowCount) and (newERC == 0) or (self.errorRowCount == 0)): self.checkAll() def slot_switchNode(self, path): self.currentPath = path self.currentSpec = self.fileDict[path] # Block attempts to update the data self.disableChange = True self.gui.clearTable() self.rowErrors = [] self.errorRowCount = 0 if (self.currentSpec[1] == "="): self.currentSection = self.currentSpec[4] row = 0 for sec in self.currentSection: ftype = sec[2][0] field = sec[0] value = sec[1] if (ftype == "bool"): self.gui.addBoolField(sec[0], sec[1], sec[3]) vError = '' else: self.gui.addField(sec[0], sec[1], sec[3]) vError = self.validate(sec[1], sec[2]) self.gui.setValid(row, vError) self.rowErrors.append(vError) if vError: self.errorRowCount += 1 row += 1 else: self.currentSection = None if (self.currentSpec[1] == "="): self.gui.setComment(self.currentSpec[3]) else: self.gui.clearComment() # Unblock attempts to update the data self.disableChange = False def slot_commentChanged(self, text): if self.disableChange: return self.currentSpec[3] = text def validate(self, value, spec): """Check the value against the field specification. If valid, return '', otherwise return an error message. """ return self.validationObject.checkValue(value, spec, self.currentPath) def slot_clone(self, arg): """Make an exact copy of this node. Only 'non-fixed' nodes may be cloned. """ spec = self.currentSpec if not spec: return if (spec[0] != '') or (spec[1] not in ('/', '=')): warning(_("Can't clone this node")) return fpath = self.currentPath.rstrip('/') spl = fpath.rsplit("/", 1) if (len(spl) == 1): basePath = '' oldName = fpath else: basePath, oldName = spl while True: filename = None if basePath.endswith('/pupils'): ipath = basePath[:-6] + 'info' try: id = int(self.getField(ipath, 'lastId')) if (id > 0): id += 1 self.changeField(ipath, 'lastId', str(id)) filename = "%03d" % id except: pass if not filename: filename = getFileName(oldName) if not filename: return path = joinPath(basePath, filename) if (spec[1] == '/'): path += '/' if self.fileDict.has_key(path): warning(_("Node '%s' already exists") % filename) else: break # Handle '='-files and folders separately if (spec[1] == '/'): # Folder self.cloneDir(self.currentPath, path, filename) elif (spec[1] == '='): # Information file self.cloneFile(path, filename, spec) else: return self.gui.sort(self.nodeDict[basePath + '/']) self.gui.setCurrentItem(self.nodeDict[path]) self.checkAll() def cloneFile(self, newpath, name, spec): """Copy a file node to the new name, using the details in spec. path is the path of the new node. """ # The specification list needs to be duplicated spec2 = list(spec) # But that's not enough, also the field lists spec2[4] = [list(l) for l in spec[4]] self.addnode(name, newpath, spec2) def cloneDir(self, oldpath, newpath, name): """Copy a folder node recursively. Note that oldpath and newpath are the paths including the folder to be copied. """ spec = self.fileDict[oldpath] self.addnode(name, newpath, spec) dirs, files = dirList(self.fileDict.keys(), oldpath) files.sort() for f in files: spec = self.fileDict[oldpath + f] self.cloneFile(newpath + f, f, spec) dirs.sort() for d in dirs: self.cloneDir(oldpath + d + '/', newpath + d + '/', d) def slot_delete(self, arg): """The graphical items must be deleted but also the entries in self.fileDict. Only 'non-fixed' nodes may be deleted and then only when they are not the only one at that level. """ spec = self.currentSpec if (spec[0] != ''): warning(_("Can't delete this node")) return spl = self.currentPath.rstrip('/').rsplit("/", 1) if (len(spl) == 1): basePath = '' else: basePath = spl[0] + '/' # Check for siblings baselen = len(basePath) for p in self.fileDict.keys(): if p.startswith(basePath): if (p == self.currentPath): continue rest = p[baselen:].rstrip('/') if (len(rest.split('/')) != 1): continue specf = self.fileDict[p] if (specf[0] != '') or (specf[1] != spec[1]): continue self.delete(self.currentPath) self.checkAll() return warning(_("This is the last node of its type, not deleting")) def delete(self, path): """This deletes a node and all its children. """ # Get the graphical item item = self.nodeDict[path] # Delete the memory structures if path.endswith('/'): # Folder for p in self.fileDict.keys(): if p.startswith(path): del self.fileDict[p] del self.nodeDict[p] else: del self.fileDict[path] del self.nodeDict[path] # Delete the gui items self.gui.deleteItem(item) def slot_getPupils(self, arg): """Import pupil data from csv files, one file per class. """ columns = [f[0] for f in getSpec("classes/*/pupils/*")[3:]] csv = CsvData(self.settings.getSetting("csvData")) if not getCsvConfig(["id"] + columns, csv): return self.settings.setSetting("csvData", csv.setting) csvHandler = Csv2Sini(self, csv.separator, csv.columns) lastDir = self.settings.getSetting("csvDir") csvdir = csvHandler.init(lastDir) if not csvdir: return self.settings.setSetting("csvDir", csvdir) guiReport(_("Importing pupil data"), csvHandler, '') self.checkAll() self.gui.setCurrentItem(self.nodeDict["classes/"]) def slot_replacePix(self, arg): """Replace the all images by the contents of a supplied folder. """ sdir = getDirectory(_("Image Folder")) if not sdir: return # Perform a save operation, but instead of getting the images # from the (old) source file, get them from sdir. if isinstance(sdir, unicode): sdir = sdir.encode('utf8') self.save(imageX=sdir) def emptyDir(self, path): """Empty the directory node at the given path. """ dirs, files = dirList(self.fileDict.keys(), path.rstrip('/') + '/') for f in (dirs + files): self.delete(joinPath(path, f)) def addPupil(self, pupilPath, id, sinitext): """Used by the csv import utility to add a pupil. """ for s in self.fileDict[pupilPath.rstrip('/') + '/'][3:]: if (s[0] == '') and (s[1] == '='): spec = s break self.addFile(pupilPath, id, spec, sinitext) def endPupils(self, classPath, idauto): """After all pupils have been entered for a class (using the csv import utility), make sure there is at least one pupil (!) and set the 'lastId' field in 'info'. """ if (idauto == 0): self.addPupil(classPath + "/pupils", FDEFAULT, '') idauto = 1 elif (idauto < 0): idauto = 0 self.changeField(classPath + "/info", "lastId", str(idauto)) def changeField(self, path, field, value): """Change one field of a file. """ # First in the memory structure spec = self.fileDict[path] flist = spec[4] for sec in flist: if (sec[0] == field): sec[1] = value def getField(self, path, field): """Get the value of the given field in the given file. """ spec = self.fileDict[path] flist = spec[4] for sec in flist: if (sec[0] == field): return sec[1] return None