import sys import yaml import os from PyQt4 import QtGui, QtCore class DictQuery(dict): # https://www.haykranen.nl/2016/02/13/handling-complex # -nested-dicts-in-python/ def get(self, path, default=None): keys = path.split(".") val = None for key in keys: if val: if isinstance(val, list): val = [v.get(key, default) if v else None for v in val] else: val = val.get(key, default) else: val = dict.get(self, key, default) if not val: break return val def create_path(path_str, delimiter="/"): """ helper function to crate a path from a string Keword Arguments: path_str -- a "/" separated path delimiter -- you can use a different delimiter """ path = '' for d in path_str.split(delimiter): path = os.path.join(path, d) return path def nested_path(dic, path, value, delimiter='.'): """ sets the value in a dictionary object given a string key separated by '.' Keyword Arguments: path -- the string key whose path is separated by '.' value -- the value we wish to set delimiter -- the delimeter value used to separate the path """ keys = path.split('.') return nested_set(dic, keys, value) def nested_set(dic, keys, value): """ sets the value in a dictionary object given a list object of keys Keyword Arguments: dic -- the dictionary object keys -- the list object of keys value -- the value we wish to set """ for key in keys[:-1]: dic = dic.setdefault(key, {}) dic[keys[-1]] = value class DeployMeta(QtGui.QMainWindow): PATH_META = "../../../meta/configuration" PATH_TEMPLATES = "../share/templates/meta" path_meta = None path_templates = None default_store_file = "test.yml" currentbranch = None config_data = None CONFIG_TYPES = { 'development': 'development.yml', 'staging': 'staging.yml', 'production': 'staging.yml', } widgets = None widgets_baseinfo = { 'PROJECT_NAME': { 'title': 'Project Name', 'location': 'project.name', }, 'PROJECT_IP': { 'title': 'Project Host', 'location': 'project.host', }, 'BRANCH_NAME': { 'title': 'Branch Name', 'location': 'project.branch', 'default': { 'development': "development", 'staging': "staging", 'production': "production" } }, 'BRANCH_EXT': { 'title': 'Branch Extension', 'location': 'project.extension', 'default': { "development": "dev", "staging": "stg", "production": "prd" } }, 'BRANCH_USER': { 'title': 'Branch User', 'location': 'project.user', 'default': { "development": "ronny", "staging": "website", "production": "website" } }, 'BRANCH_GROUP': { 'title': 'Branch Group', 'location': 'project.group', 'default': { "development": "wheel", "staging": "www-data", "production": "www-data" } }, 'DATABASE_IP': { 'title': 'Database IP', 'location': 'database.host'}, 'DATABASE_PORT': { 'title': 'Database Port', 'location': 'database.port', 'default': { 'development': 'DOCKER_PORT', "staging": "5432", "production": "5432" }, }, 'DATABASE_NAME': { 'title': 'Database Name', 'location': 'database.name', }, 'DATABASE_ADMIN_NAME': { 'title': 'Database Admin Name', 'location': 'database.users.admin.name', 'default': { "development": "admin", "staging": "admin", "production": "admin" } }, 'DATABASE_ADMIN_PASS': { 'title': 'Database Admin Pass', 'location': 'database.users.admin.pass', 'default': { 'development': "admin", 'staging': "admin", 'production': "admin" } }, 'DATABASE_USER_NAME': { 'title': 'Database User Name', 'location': 'database.users.default.name', 'default': { 'development': "ronny", 'staging': "website", 'production': "website" } }, 'DATABASE_USER_PASS': { 'title': 'Database User Pass', 'location': 'database.users.default.pass', 'default': { 'development': "admin", 'staging': "admin", 'production': "admin", } }, 'DJANGO_IP': { 'title': 'Django IP Address', 'location': 'django.host', 'default': { 'development': "127.0.0.1", 'staging': None, 'production': None } }, 'DJANGO_PORT': { 'title': 'Django Port', 'location': 'django.port', 'default': { 'development': None, 'staging': "8000", 'production': "8010", }, }, 'NGINX_PORT': { 'title': 'Nginx Port', 'location': 'nginx.port', 'default': { 'development': "8050", 'staging': "80", 'production': "80", }, } } def __init__(self): super(DeployMeta, self).__init__() # create the base path variables self.path_meta = create_path(self.PATH_META) self.path_templates = create_path(self.PATH_TEMPLATES) self.widgets = dict() for key in self.widgets_baseinfo: self.widgets[key] = dict() self.widgets[key]['title'] = self.widgets_baseinfo[key]['title'] self.widgets[key]['location'] = \ self.widgets_baseinfo[key]['location'] if 'default' in self.widgets_baseinfo[key]: self.widgets[key]['default'] = \ self.widgets_baseinfo[key]['default'] else: self.widgets[key]['default'] = None self.widgets[key]['field'] = None self.initUI() def load_config(self, configname): if configname not in self.CONFIG_TYPES.keys(): print "\nerror, load_config was called with parameter: {confname}," \ "which is not a legitimate value in CONFIG TYPES." \ "\nLegitimate values are {configvalues}".format( confname=configname, configvalues=self.CONFIG_TYPES.keys()) sys.exit() path_config_full = os.path.join( self.path_templates, self.CONFIG_TYPES[configname]) configuration_file = yaml.load(file(path_config_full, 'r')) return configuration_file def store_config(self): for key in self.widgets: configvalue = str(self.widgets[key]['field'].text().__str__()) if configvalue.isdigit(): configvalue = int(configvalue) nested_path( self.config_data, self.widgets[key]['location'], configvalue) dquery = DictQuery(self.config_data) database_name = "{projectname}_{branchext}".format( projectname=dquery.get('project.name'), branchext=dquery.get('project.extension')) nested_path( self.config_data, 'database.name', database_name) nested_path( self.config_data, 'virtualenv.name', dquery.get('project.name')) if self.currentbranch == 'development': projectpath = "{projectname}.prj".format( projectname=dquery.get('project.name')) nested_path( self.config_data, 'project.paths.home', projectpath) def add_widgetrow(self, key, grid): row = grid.rowCount() title = self.widgets[key]['title'] label = QtGui.QLabel(title) field = QtGui.QLineEdit() grid.addWidget(label, row, 0) grid.addWidget(field, row, 1) self.widgets[key]['field'] = field def create_action(self, key): title = key.capitalize() menuname = "&%s" % title shortcut = "Ctrl+%s" % title[0] desc = "Load Configuration %s" % title loadAction = QtGui.QAction(menuname, self) loadAction.setShortcut(shortcut) loadAction.setStatusTip(desc) loadAction.triggered.connect(self.action_loadconfig) variant = QtCore.QVariant(key) loadAction.setData(variant) return loadAction def action_loadconfig(self): sender = self.sender() self.currentbranch = sender.data().toString().__str__() self.config_data = self.load_config(self.currentbranch) dquery = DictQuery(self.config_data) for key in self.widgets: configvalue = dquery.get(self.widgets[key]['location']) if self.widgets[key]['default']: defaultvalue = self.widgets[key]['default'][self.currentbranch] if configvalue == key and defaultvalue is not None: self.widgets[key]['field'].setText(defaultvalue) else: self.widgets[key]['field'].setText(str(configvalue)) else: self.widgets[key]['field'].setText(configvalue) def action_save_config(self): # first make sure we've loaded up a configuration # file to save. currentbranch will be set if we # loaded it up if self.currentbranch: self.store_config() # path_newconfig = os.path.join( # self.path_meta, self.default_store_file) dialog_title = "Save {currentbranch} configuration".format( currentbranch=self.currentbranch) # # generate the default path to save to path_default_save = os.path.join( self.path_meta, self.default_store_file) # # get the path value from the dialog box path_newconfig = QtGui.QFileDialog.getSaveFileName( self, dialog_title, path_default_save, selectedFilter='*.yml') # # if the user hit 'cancel' path_newconfig will be empty if path_newconfig: stream = file(path_newconfig, 'w') yaml.dump(self.config_data, stream) else: # # display message warning no configuration has been loaded from PyQt4.QtGui import QMessageBox msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText('Save Error') msg.setInformativeText( "Cannot save modified configuration until you load one of the" " default config types") retval = msg.exec_() print "value of qmessagebox in action_save: %s" % retval def setupMenu(self): menubar = self.menuBar() menubar.setNativeMenuBar(False) exitAction = QtGui.QAction('&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip("Exit application") exitAction.triggered.connect(QtGui.qApp.quit) saveAction = QtGui.QAction('&Gore', self) saveAction.setShortcut('Ctrl+T') saveAction.setStatusTip("Save Config File") saveAction.triggered.connect(self.action_save_config) fileMenu = menubar.addMenu('&File') fileMenu.addAction(exitAction) fileMenu.addAction(saveAction) loadMenu = menubar.addMenu("&Load Configuration") for key in self.CONFIG_TYPES: loadMenu.addAction(self.create_action(key)) def initUI(self): self.setupMenu() grid = QtGui.QGridLayout() grid.setSpacing(10) self.add_widgetrow('PROJECT_NAME', grid) self.add_widgetrow('PROJECT_IP', grid) self.add_widgetrow('BRANCH_NAME', grid) self.add_widgetrow('BRANCH_EXT', grid) self.add_widgetrow('BRANCH_USER', grid) self.add_widgetrow('BRANCH_GROUP', grid) self.add_widgetrow('DATABASE_IP', grid) self.add_widgetrow('DATABASE_PORT', grid) self.add_widgetrow('DATABASE_NAME', grid) self.add_widgetrow('DATABASE_ADMIN_NAME', grid) self.add_widgetrow('DATABASE_ADMIN_PASS', grid) self.add_widgetrow('DATABASE_USER_NAME', grid) self.add_widgetrow('DATABASE_USER_PASS', grid) self.add_widgetrow('DJANGO_IP', grid) self.add_widgetrow('DJANGO_PORT', grid) self.add_widgetrow('NGINX_PORT', grid) central = QtGui.QWidget() central.setLayout(grid) self.setCentralWidget(central) self.setGeometry(300, 300, 450, 300) self.setWindowTitle("Deploy Meta Files") self.show() self.raise_() def main(): app = QtGui.QApplication(sys.argv) app.setStyle("cleanlooks") ex = DeployMeta() sys.exit(app.exec_()) print ex if __name__ == '__main__': main()