#!/usr/bin/env python # -*- coding: utf-8 -*- # CeibalEncuesta.py por: # Flavio Danesse # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Notas: # Las listas de Encuestados se abren desde un archivo csv. # Las encuestas sin Responder se abren desde un archivo slv (shelve) o json. # Las Encuestas respondidas o parcialmente respondidas se guardan en archivos .encuesta (json) import os import sys import site import gi from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GObject from Widgets import Panel from Widgets import InfoWidget from Widgets import My_FileChooser from Widgets import My_Alert_Dialog, My_Alert_Dialog3 from Widgets import Wizard import Globales as G PATH = os.path.dirname(__file__) home = site.getuserbase() HOME = home.replace(".local", "") WORKPATH = os.path.join(HOME, "CeibalEncuesta") TEMPPATH = os.path.join(WORKPATH, "temp.encuesta") BACKUP_PATH = os.path.join(HOME, "Documentos") if not os.path.exists(WORKPATH): os.mkdir(WORKPATH) os.chmod(WORKPATH, 0755) if not os.path.exists(BACKUP_PATH): BACKUP_PATH = WORKPATH archivo = open(TEMPPATH, "w") archivo.close() screen = Gdk.Screen.get_default() css_provider = Gtk.CssProvider() style_path = os.path.join(PATH, "CssStyle.css") css_provider.load_from_path(style_path) context = Gtk.StyleContext() context.add_provider_for_screen( screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) GObject.threads_init() Gdk.threads_init() class CeibalEncuesta(Gtk.Window): def __init__(self): self.backup_path = BACKUP_PATH Gtk.Window.__init__(self) self.set_title("Ceibal Encuesta") self.set_icon_from_file( os.path.join(PATH, "Iconos", "ceibal.png")) self.set_resizable(True) self.set_size_request(640, 480) self.set_position(Gtk.WindowPosition.CENTER) self.path = TEMPPATH self.out_dict = {} # keys = Encuestados, Values = Encuesta respondida box = Gtk.VBox() self.infowidget = InfoWidget() self.panel = Panel() # menuitems self.exportar = None box.pack_start(self.__get_menu(), False, False, 5) box.pack_start(self.infowidget, False, False, 5) box.pack_end(self.panel, True, True, 0) self.add(box) self.show_all() self.realize() self.infowidget.hide() self.panel.hide() self.panel.connect("new", self.__change) self.panel.connect("text_and_change", self.__set_text_and_change) self.panel.connect("new-selection", self.__new_selection) self.connect("key-press-event", self.__key_press_event) self.connect("destroy", self.__salir) self.__init() if getattr(self, "poll_id", None): if str(self.poll_id) not in self.backup_path: self.backup_path = self.backup_path + "/%s.encuesta" % self.poll_id if not os.path.exists(self.backup_path): self.__save_json(path=self.backup_path) self.show_all() def __init(self, widget = None): """ Abre encuesta y lista a través del asistente. """ reset = False dialog = Wizard(parent_window = self) response = dialog.run() if Gtk.ResponseType(response) == Gtk.ResponseType.CANCEL: self.emit("destroy") else: encuesta_nueva = dialog.label_1.get_text() lista = dialog.label_2.get_text() encuesta_anterior = dialog.label_3.get_text() if encuesta_nueva and lista and not encuesta_anterior: continue_ = self.__load_encuesta(None, encuesta_nueva) if not continue_: reset = True else: while Gtk.events_pending(): Gtk.main_iteration() self.__load_encuestados(None, lista) elif encuesta_anterior and not encuesta_nueva and not lista: self.__load_encuesta_respondida(None, encuesta_anterior) else: reset = True dialog.destroy() if reset: self.__init() self.fullscreen() self.panel.box_encuesta.queue_draw() def __key_press_event(self, widget, event): """ Eventos de Teclas. """ ### Cuando se presiona escape if event.keyval == 65307: from Widgets import Password_Dialog dialog = Password_Dialog( parent_window = self, title = "Administrar", label = "Password:") response = dialog.run() password = 0 if Gtk.ResponseType(response) == Gtk.ResponseType.ACCEPT: password = dialog.get_password() dialog.destroy() if password == "123": self.panel.show_lista() self.menu_bar.show_all() self.infowidget.show_all() def do_draw(self, context): """ Pinta una imagen si no se ha cargado una lista a encuestar o una encuesta a aplicar. """ if self.panel.get_visible(): return False from gi.repository import GdkPixbuf from gi.repository import Gdk import cairo archivo = os.path.join(PATH, "Iconos", "screen.svg") pixbuf = GdkPixbuf.Pixbuf.new_from_file(archivo) rect = self.get_allocation() x, y, w, h = (rect.x, rect.y, rect.width, rect.height) ww, hh = pixbuf.get_width(), pixbuf.get_height() scaledPixbuf = pixbuf.scale_simple( w, h, GdkPixbuf.InterpType.BILINEAR) surface = cairo.ImageSurface( cairo.FORMAT_ARGB32, scaledPixbuf.get_width(), scaledPixbuf.get_height()) tmpcontext = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(tmpcontext, scaledPixbuf, 0, 0) tmpcontext.paint() context.set_source_surface(surface) context.paint() def __set_text_and_change(self, widget, encuestado, grupo, pregunta, dict_pregunta): """ Cuando se ingresa texto en una pregunta cuyo widgets es un Gtk.Entry, se almacenan los datos. """ indice_grupo = grupo.indice grupo_name = grupo.get_label() indice_pregunta = pregunta.indice user = "" for item in encuestado: user += " %s" % item encuestado = user.strip() # Entrada para encuestado if not encuestado in self.out_dict.keys(): self.out_dict[encuestado] = {} # Entrada para grupo if not self.out_dict[encuestado].get(indice_grupo, False): self.out_dict[encuestado][indice_grupo] = {} # Entrada para pregunta if not self.out_dict[encuestado][indice_grupo].get("fields", False): self.out_dict[encuestado][indice_grupo]["fields"] = {} self.out_dict[encuestado][indice_grupo]["name"] = grupo_name self.out_dict[encuestado][indice_grupo]["fields"][indice_pregunta] = dict_pregunta self.__save_json(path = self.path) def __change(self, widget, encuestado, indice_grupo, grupo_name, indice_pregunta, dict_pregunta): """ Recibe los cambios en la encuesta y almacena los datos. """ user = "" for item in encuestado: user += " %s" % item encuestado = user.strip() # Entrada para encuestado if not encuestado in self.out_dict.keys(): self.out_dict[encuestado] = {} # Entrada para grupo if not self.out_dict[encuestado].get(indice_grupo, False): self.out_dict[encuestado][indice_grupo] = {} # Entrada para pregunta if not self.out_dict[encuestado][indice_grupo].get("fields", False): self.out_dict[encuestado][indice_grupo]["fields"] = {} self.out_dict[encuestado][indice_grupo]["name"] = grupo_name self.out_dict[encuestado][indice_grupo]["fields"][indice_pregunta] = dict_pregunta self.__save_json(path = self.path) def __new_selection(self, widget, encuestado): """ Cuando el usuario cambia de Encuestado. """ import json import codecs archivo = codecs.open(self.path, "r", "utf-8") enc = json.JSONDecoder("utf-8").decode(archivo.read()) self.out_dict = {} self.out_dict = enc["groups"] if self.out_dict.get(encuestado, False): self.panel.update(self.out_dict[encuestado]) else: self.out_dict[encuestado] = {} self.panel.update({}) self.menu_bar.hide() self.infowidget.hide() self.infowidget.set_encuestado(encuestado) combo_box = getattr(self.panel.lista, "combo", None) if combo_box: current_group_text = "Grupo: %s" % combo_box.get_current_group() self.panel.current_group_label.set_text(current_group_text) def __get_menu(self): """ Crea y devuelve el menú de la aplicación. """ self.menu_bar = Gtk.MenuBar() self.menu_bar.show() file_menu = Gtk.Menu() abrir = Gtk.MenuItem("Abrir Encuesta . . .") abrir.set_tooltip_text("Abrir una Encuesta.") file_menu.append(abrir) abrir.connect_object("activate", self.__init, "") self.exportar = Gtk.MenuItem("Exportar Encuesta . . .") salir = Gtk.MenuItem("Salir") self.exportar.set_tooltip_text( "Exportar Encuesta a Formato csv") salir.set_tooltip_text( "Salir de la Aplicación.") file_menu.append(self.exportar) file_menu.append(salir) self.exportar.connect_object("activate", self.__exportar_encuesta, "") salir.connect_object("activate", self.__salir, "") salir.show() file_item = Gtk.MenuItem("Archivo") file_item.show() file_item.set_submenu(file_menu) self.menu_bar.append(file_item) self.exportar.set_sensitive(False) return self.menu_bar def __exportar_encuesta(self, widget = None, senial = None): """ Abre Filechooser para elegir directorio donde se guardará el archivo con los resultados """ filechooser = My_FileChooser( parent_window = self, action_type = Gtk.FileChooserAction.SELECT_FOLDER, title = "Exportar resultados de encuesta") filechooser.connect("load", self.__save_results) def __save_results(self, widget = None, chosen_path = WORKPATH): """ Exporta los datos a un archivo con el formato necesario para ser enviado a web app. """ ### Asegurandose de que se haya guardado toda la información. self.__save_json() import json import codecs archivo = codecs.open(self.path, "r", "utf-8") enc = json.JSONDecoder("utf-8").decode(archivo.read()) ### Cabecera para lista de encuestados. cabecera = enc["encuestados"][0] polltype = "monitoreo" if "ID" in cabecera: polltype = "general" ### Encuestados. encuestados = enc["encuestados"][1:] ### Respuestas respuestas = enc["groups"] ### Formando salida para encuestados. polled_dict = {} for item in cabecera: polled_dict[item] = "" ### llenando la salida para este encuestado. result = {} contador = 0 for encuestado in encuestados: ### polled polled = polled_dict.copy() for item in encuestado: polled[cabecera[encuestado.index(item)]] = item ### answers user = "" for item in encuestado: user += " %s" % item encue = user.strip() answers = respuestas.get(encue, {}) for group_order, fields in answers.iteritems(): for field_order, field_data in fields['fields'].iteritems(): selected = field_data.get('default', []) field_data['answer'] = {} if len(selected): for option_id, value in field_data.get("options", {}).iteritems(): if option_id in selected: selected_opt = field_data['options'][option_id] if "Image" in field_data['widget_type']: del selected_opt['img'] elif "TextInput" in field_data['widget_type']: new_text = value.get("newtext", "") value['text'] = new_text del value['newtext'] field_data['answer'].update({"%s" % option_id: selected_opt}) for key_to_del in ['dependence', 'options', 'default']: if field_data.get(key_to_del, None): del field_data[key_to_del] result[contador] = {"polled": polled, "answers": answers} contador += 1 ### Salida Final. out_dict = { "result": result, "poll_name": enc.get("poll_name", ""), "poll_id": enc.get("poll_id", ""), "pollster_id": enc.get("pollster_id", ""), "pollster_username": enc.get("pollster_username", ""), "poll_type": polltype} import time poll_id = enc.get("poll_id", "") hash = int(time.time() * 1000) path = os.path.join(chosen_path, "%s_%s.poll_result" % (poll_id, hash)) import json try: archivo = open(path, "w") except IOError, ioe: dialog = My_Alert_Dialog( parent_window = self, label = u"Ocurrió un error inesperado al exportar los resultados." ) if "Permission denied" in str(ioe): dialog = My_Alert_Dialog( parent_window = self, label = u"No tiene permisos para escribir en el directorio elegido." ) response = dialog.run() dialog.destroy() if widget: widget.destroy() self.exportar.emit("activate") else: archivo.write( json.dumps( out_dict, indent=4, separators=(", ", ":"), sort_keys=True ) ) archivo.close() dialog = self._alert_dialog( parent_window=self, label="Encuesta Exportada Correctamente.") dialog.run() dialog.destroy() def _alert_dialog(self, parent_window, label): return My_Alert_Dialog(parent_window=parent_window, label=label) def __load_encuesta_respondida(self, widget, archivo): """ Carga una encuesta parcial o totalmente respondida, desde un archivo json. """ if os.path.exists(TEMPPATH): os.remove(TEMPPATH) self.path = archivo extension = os.path.splitext(os.path.split(archivo)[1])[1] if "encuesta" in extension: import json import codecs archivo = codecs.open(archivo, "r", "utf-8") enc = json.JSONDecoder("utf-8").decode(archivo.read()) self.out_dict = enc["groups"] self.panel.load_encuesta(enc["encuesta"]) poll_name = enc["poll_name"] poll_id = enc["poll_id"] pollster_id = enc["pollster_id"] pollster_username = enc["pollster_username"] dict = { "poll_name": poll_name, "poll_id": poll_id, "pollster_id": pollster_id, "pollster_username": pollster_username} self.backup_path = self.backup_path + "/%s.encuesta" % poll_id self.infowidget.set_info(dict) self.panel.load_encuestados(enc["encuestados"]) self.__check_sensitive() def __load_encuestados(self, widget, archivo): """ Recibe archivo csv con la lista a encuestar y la manda cargar en la aplicación. """ if os.path.exists(TEMPPATH): os.remove(TEMPPATH) self.path = TEMPPATH arch = open(self.path, "w") arch.close() self.out_dict = {} self.__save_json(path = self.path) encuestados = G.cargar_encuestados(os.path.join(archivo)) self.panel.load_encuestados(encuestados) self.__check_sensitive() def __load_encuesta(self, widget, archivo): """ Carga una encuesta almacenada en un archivo json o shelve. """ if os.path.exists(TEMPPATH): os.remove(TEMPPATH) self.path = TEMPPATH arch = open(self.path, "w") arch.close() # Mantiene la lista de encuestados cargada actualmente. encuestados = self.out_dict.keys() self.out_dict = {} for encuestado in encuestados: self.out_dict[encuestado] = {} encuesta = {} poll_name = "" poll_id = "" pollster_id = "" pollster_username = "" extension = os.path.splitext(os.path.split(archivo)[1])[1] if "json" in extension: import json import codecs archivo = codecs.open(archivo, "r", "utf-8") enc = json.JSONDecoder("utf-8").decode(archivo.read()) encuesta = enc["groups"] poll_name = enc["poll_name"] poll_id = enc["poll_id"] pollster_id = enc["pollster_id"] pollster_username = enc["pollster_username"] elif "slv" in extension: import shelve archivo = shelve.open(archivo) enc = archivo["groups"] poll_name = archivo["poll_name"] poll_id = archivo["poll_id"] pollster_id = archivo["pollster_id"] pollster_username = archivo["pollster_username"] for key in enc.keys(): encuesta[key] = enc[key] archivo.close() self.poll_id = poll_id bck_path = self.backup_path + "/%s.encuesta" % self.poll_id if os.path.exists(bck_path): msg = u""" Existe un archivo temporal para esta encuesta en: - "%s" Si desea recuperarlo, debe utilizar la opción del asistente: - "seleccionar encuesta desde archivo temporal" """ % bck_path dialog = My_Alert_Dialog3( parent_window=self.get_toplevel(), label=msg ) dialog.run() dialog.destroy() return False self.panel.load_encuesta(encuesta) dict = { "poll_name": poll_name, "poll_id": poll_id, "pollster_id": pollster_id, "pollster_username": pollster_username} self.infowidget.set_info(dict) while Gtk.events_pending(): Gtk.main_iteration() self.__save_json(path = self.path) self.__check_sensitive() return True def __check_sensitive(self): if self.panel.encuesta and self.panel.lista: self.exportar.set_sensitive(True) else: self.exportar.set_sensitive(False) def __save_json(self, path = TEMPPATH): """ Guarda encuesta en formato json. """ self.path = path encuestados = [] encuesta = {} poll_name = "" poll_id = "" pollster_id = "" pollster_username = "" if self.panel: if self.panel.encuestados: encuestados = self.panel.encuestados if self.panel.encuesta: encuesta = self.panel.encuesta if self.infowidget: dict = self.infowidget.get_info() pull_name = dict["poll_name"] poll_id = dict["poll_id"] pollster_id = dict["pollster_id"] pollster_username = dict["pollster_username"] salida = { "encuestados": encuestados, "groups": self.out_dict, "encuesta": encuesta, "poll_name": poll_name, "poll_id": poll_id, "pollster_id": pollster_id, "pollster_username": pollster_username} import json archivo = open(self.path, "w") archivo.write(json.dumps(salida)) archivo.close() if os.path.exists(TEMPPATH): if TEMPPATH != path: os.remove(TEMPPATH) def __salir(self, widget = None, senial = None): sys.exit(0) if __name__ == "__main__": ceibalencuesta = CeibalEncuesta() Gtk.main()