diff options
author | Code Raguet <ignacio.code@gmail.com> | 2013-08-16 17:19:15 (GMT) |
---|---|---|
committer | Code Raguet <ignacio.code@gmail.com> | 2013-08-16 17:19:15 (GMT) |
commit | 1101373de9297176d11c7471a0cf59e6c6a7eacb (patch) | |
tree | b4ecf5eccce50497cf903ade2f38bb8472d4fe2c | |
parent | 631ccd38bb09a152eaa61892a63f78381da4f33f (diff) | |
parent | 7cb74551d3208130961219d0dfcd4e44eb0a0a26 (diff) |
Merge branch 'DEV'v4.11
27 files changed, 1255 insertions, 639 deletions
@@ -5,6 +5,8 @@ webapp/webapp/media/cache/ webapp/webapp/media/image_options/* !webapp/webapp/media/image_options/empty +webapp/webapp/media/images/* +!webapp/webapp/media/images/empty webapp/webapp/env_settings.py database.db webapp/webapp/media/results_bck/* @@ -16,4 +18,4 @@ CeibalEncuesta/gtk2/scripts/*.tar.gz CeibalEncuesta/gtk2/scripts/*.run CeibalEncuesta/gtk3/scripts/*.tar.gz CeibalEncuesta/gtk3/scripts/*.run -webapp/stress_test/poll_results
\ No newline at end of file +webapp/stress_test/poll_results diff --git a/CeibalEncuesta/gtk2/CeibalEncuesta.activity/activity/activity.info b/CeibalEncuesta/gtk2/CeibalEncuesta.activity/activity/activity.info index 5f6bdcd..43456f1 100644 --- a/CeibalEncuesta/gtk2/CeibalEncuesta.activity/activity/activity.info +++ b/CeibalEncuesta/gtk2/CeibalEncuesta.activity/activity/activity.info @@ -3,6 +3,6 @@ name = CeibalEncuesta license = GPLv2+ icon = ceibal exec = sugar-activity CeibalEncuestaActivity.CeibalEncuesta -activity_version = 4.9 +activity_version = 4.11 bundle_id = org.laptop.CeibalEncuesta diff --git a/CeibalEncuesta/gtk2/CeibalEncuesta/CeibalEncuesta.py b/CeibalEncuesta/gtk2/CeibalEncuesta/CeibalEncuesta.py index babd151..b74c1a8 100644 --- a/CeibalEncuesta/gtk2/CeibalEncuesta/CeibalEncuesta.py +++ b/CeibalEncuesta/gtk2/CeibalEncuesta/CeibalEncuesta.py @@ -25,7 +25,6 @@ import os import sys -import site import gtk from gtk import gdk @@ -41,23 +40,21 @@ from Widgets import Wizard from Globales import Encuesta, GrupoDeEncuestados 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("/tmp", "temp.encuesta") -BACKUP_PATH = os.path.join(HOME, "Documentos") +HOME = os.environ["HOME"] + +WORKPATH = os.path.join(HOME, "Documentos") if not os.path.exists(WORKPATH): - os.mkdir(WORKPATH) - os.chmod(WORKPATH, 0755) + WORKPATH = os.path.join(HOME, "CeibalEncuesta") + if not os.path.exists(WORKPATH): + os.mkdir(WORKPATH) + os.chmod(WORKPATH, 0755) -if not os.path.exists(BACKUP_PATH): - BACKUP_PATH = WORKPATH +TEMPPATH = os.path.join("/dev/shm", "temp.encuesta") -archivo = open(TEMPPATH, "w") -archivo.close() +if os.path.exists(TEMPPATH): + os.remove(TEMPPATH) gobject.threads_init() gdk.threads_init() @@ -70,14 +67,14 @@ 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")) + os.path.join(os.path.dirname(__file__), + "Iconos", "ceibal.png")) self.set_resizable(True) self.set_size_request(640, 480) @@ -120,12 +117,6 @@ class CeibalEncuesta(gtk.Window): 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.connect("destroy", gtk.main_quit) self.show_all() @@ -248,6 +239,7 @@ class CeibalEncuesta(gtk.Window): import cairo + PATH = os.path.dirname(__file__) archivo = os.path.join(PATH, "Iconos", "screen.png") img = cairo.ImageSurface.create_from_png(archivo) @@ -305,12 +297,10 @@ class CeibalEncuesta(gtk.Window): self.out_dict[encuestado][indice_grupo]["fields"][indice_pregunta] = dict_pregunta - # self.__save_json(path = self.path) - def save_text(self): - - self.__save_json(path = self.path) - + + self.__save_json() + def __change(self, widget, encuestado, indice_grupo, grupo_name, indice_pregunta, dict_pregunta): @@ -341,7 +331,7 @@ class CeibalEncuesta(gtk.Window): self.out_dict[encuestado][indice_grupo]["fields"][indice_pregunta] = dict_pregunta - self.__save_json(path = self.path) + self.__save_json() def __new_selection(self, widget, encuestado): """ @@ -449,6 +439,7 @@ class CeibalEncuesta(gtk.Window): """ ### Asegurandose de que se haya guardado toda la información. + self.__save_json() import json @@ -503,6 +494,8 @@ class CeibalEncuesta(gtk.Window): for group_order, fields in answers.iteritems(): for field_order, field_data in fields['fields'].iteritems(): + if field_data.get('img', False): + del field_data['img'] selected = field_data.get('default', []) field_data['answer'] = {} if len(selected): @@ -593,7 +586,10 @@ class CeibalEncuesta(gtk.Window): if os.path.exists(TEMPPATH): os.remove(TEMPPATH) - self.path = archivo + self.path = os.path.join(os.path.dirname(TEMPPATH), os.path.basename(archivo)) + + import shutil + shutil.copyfile(archivo, self.path) extension = os.path.splitext(os.path.split(archivo)[1])[1] @@ -618,8 +614,6 @@ class CeibalEncuesta(gtk.Window): "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"]) @@ -632,17 +626,9 @@ class CeibalEncuesta(gtk.Window): 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) + self.__save_json() encuestados = G.cargar_encuestados(os.path.join(archivo)) @@ -656,14 +642,6 @@ class CeibalEncuesta(gtk.Window): 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() @@ -694,42 +672,27 @@ class CeibalEncuesta(gtk.Window): 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 + final_path = os.path.join(WORKPATH, "%s.encuesta" % poll_id) - bck_path = self.backup_path + "/%s.encuesta" % self.poll_id - if os.path.exists(bck_path): + if os.path.exists(final_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 + """ % final_path dialog = My_Alert_Dialog3( parent_window=self.get_toplevel(), label=msg ) dialog.run() dialog.destroy() + return False + self.poll_id = poll_id + self.panel.load_encuesta(encuesta) dict = { @@ -743,7 +706,7 @@ class CeibalEncuesta(gtk.Window): while gtk.events_pending(): gtk.main_iteration() - self.__save_json(path = self.path) + self.__save_json() self.__check_sensitive() @@ -757,13 +720,11 @@ class CeibalEncuesta(gtk.Window): else: self.exportar.set_sensitive(False) - def __save_json(self, path = TEMPPATH): + def __save_json(self): """ Guarda encuesta en formato json. """ - self.path = path - encuestados = [] encuesta = {} poll_name = "" @@ -791,14 +752,20 @@ class CeibalEncuesta(gtk.Window): "pollster_id": pollster_id, "pollster_username": pollster_username} + self.poll_id = getattr(self, "poll_id", poll_id) + self.path = os.path.join(os.path.dirname(self.path), "%s.encuesta" % self.poll_id) + 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 copy_to_Documents(self): + + final_path = os.path.join(WORKPATH, os.path.basename(self.path)) + + import shutil + shutil.copyfile(self.path, final_path) def __salir(self, widget = None, senial = None): sys.exit(0) diff --git a/CeibalEncuesta/gtk2/CeibalEncuesta/Widgets.py b/CeibalEncuesta/gtk2/CeibalEncuesta/Widgets.py index cc2f2c4..9a8e884 100644 --- a/CeibalEncuesta/gtk2/CeibalEncuesta/Widgets.py +++ b/CeibalEncuesta/gtk2/CeibalEncuesta/Widgets.py @@ -36,7 +36,7 @@ WORKPATH = os.path.join(HOME, "CeibalEncuesta") BACKUP_PATH = os.path.join(HOME, "Documentos") if not os.path.exists(BACKUP_PATH): BACKUP_PATH = WORKPATH - + PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) ICON = PROJECT_ROOT + '/Iconos/ceibal.png' NEXT_PAGE_ACTIVE = PROJECT_ROOT + "/Iconos/next-page-active.png" @@ -215,6 +215,7 @@ class Panel(gtk.Paned): for question in self.visible_questions(): texto = None active_options = None + if question.pregunta.get("widget_type", None) == "TextInput": texto = question.widget_obtions.get_text().strip() else: @@ -240,7 +241,7 @@ class Panel(gtk.Paned): index_visible = grupos.index(child) break - if accion == "": + if accion == "" or accion == "Anterior": if index_visible > 0: map(self.__hide_groups, grupos) map(self.__show_groups, [grupos[index_visible - 1]]) @@ -248,13 +249,16 @@ class Panel(gtk.Paned): self._window.get_vadjustment().set_value(0) elif accion == "Siguiente" and self.toolbar_encuesta.siguiente.active: - + + ceibal_encuesta = self.get_toplevel() + if index_visible < len(grupos) - 1: map(self.__hide_groups, grupos) map(self.__show_groups, [grupos[index_visible + 1]]) self.box_encuesta.queue_draw() self._window.get_vadjustment().set_value(0) - + ceibal_encuesta.copy_to_Documents() + if index_visible == len(grupos) - 2: self.toolbar_encuesta.encuesta_actual.finalizar() @@ -264,17 +268,15 @@ class Panel(gtk.Paned): for item in encuestado: user += " %s" % item encuestado = user.strip() - ceibal_encuesta = self.get_toplevel() + if encuestado in ceibal_encuesta.out_dict.keys(): ceibal_encuesta.out_dict[encuestado].update( {"finalizada": True}) - ceibal_encuesta._CeibalEncuesta__save_json( - path=ceibal_encuesta.path) - + self.toolbar_encuesta.siguiente.hide() self.toolbar_encuesta.anterior.show() return - + self.__check_sensibility_butons() def load_encuestados(self, encuestados): @@ -904,13 +906,13 @@ class Grupo(gtk.Frame): gobject.TYPE_PYOBJECT))} def __init__(self, indice_grupo, grupo): - + gtk.Frame.__init__(self, grupo.get('name', '')) desc = pango.FontDescription(GRADO_FONT_SIZE) desc.set_weight(pango.WEIGHT_BOLD) self.modify_font(desc) - + self.indice = indice_grupo # indice del grupo en la encuesta self.grupo = grupo.copy() # Diccionario del grupo en la encuesta @@ -986,6 +988,7 @@ class Pregunta(gtk.VBox): "00002": {"text": "No"}, }, "dependence": "" + "img": "base64 string" } } """ @@ -1007,9 +1010,7 @@ class Pregunta(gtk.VBox): self.indice = indice_pregunta self.pregunta = pregunta.copy() - # CEIBAL ICON PIXBUF - # TODO: def question_icon(self, scaled_size) - self.question_icon = gdk.pixbuf_new_from_file(ICON) + self.question_icon = self.get_question_pixbuf() self.widget_obtions = None widget = self.pregunta["widget_type"] @@ -1037,19 +1038,20 @@ class Pregunta(gtk.VBox): pangoFont = pango.FontDescription(QUESTION_FONT_SIZE) label.modify_font(pangoFont) label.set_line_wrap(True) - label.set_size_request(700, -1) - + label.set_size_request(800, -1) + # --- QUESTION TITLE INDENT: horizontal alignment halign = gtk.Alignment(0, 0, 0, 0) - halign.set_padding(25, 25, 50, 0) + halign.set_padding(25, 25, 75, 0) # CEIBAL ICON | Question title question_hbox = gtk.HBox() - question_icon = gtk.Image() - scaled_qicon = self.question_icon.scale_simple( - 60, 60, gdk.INTERP_BILINEAR) - question_icon.set_from_pixbuf(scaled_qicon) - question_hbox.pack_start(question_icon, True, True, 10) + if self.question_icon is not None: + question_icon = gtk.Image() + scaled_qicon = self.question_icon.scale_simple( + 60, 60, gdk.INTERP_BILINEAR) + question_icon.set_from_pixbuf(scaled_qicon) + question_hbox.pack_start(question_icon, True, True, 10) title = label label_align = gtk.Alignment(0, 0, 0, 1) label_align.add(title) @@ -1060,7 +1062,8 @@ class Pregunta(gtk.VBox): # --- END QUESTION TITLE INDENT # --- OPTIONS INDENT: horizontal alignment for question's options - halign = gtk.Alignment(0.2, 0.1, 0, 0) + halign = gtk.Alignment(0, 0.1, 0, 0) + halign.set_padding(0, 0, 90, 0) # Vertical list of question's options vbbox = gtk.VButtonBox() @@ -1071,6 +1074,22 @@ class Pregunta(gtk.VBox): self.show_all() + def get_question_pixbuf(self): + pixbuf = None + img_string = self.get_question_img_string() + if img_string: + pixbuf = self.make_pixbuf_from_string(img_string) + return pixbuf + + def get_question_img_string(self): + return self.pregunta.get('img', None) + + def make_pixbuf_from_string(self, image_string): + loader = gdk.PixbufLoader() + loader.write(base64.b64decode(image_string)) + loader.close() + return loader.get_pixbuf() + def __emit_text_and_change(self, widget, id_opcion, text, activan): """ Cuando el usuario ingresa algún valor en una entrada de texto, @@ -1120,9 +1139,13 @@ class Widget_DropDownList(gtk.ComboBox): """ __gsignals__ = { + "key-press-event" : "override", "new":(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))} + def do_key_press_event(self, widget): + return True + def __init__(self, options): model = gtk.ListStore(gobject.TYPE_STRING) @@ -1134,19 +1157,18 @@ class Widget_DropDownList(gtk.ComboBox): self.pack_start(cell) self.add_attribute(cell, 'text', 0) - self.set_model(model) self.options = options.copy() self.updating = False order = {} - for key in keys: + for key in self.options.keys(): id = int(self.options[key]["order"]) order[id] = key - + indices = order.keys() indices.sort() - + for id in indices: key = order[id] self.append_text(self.options[key]["text"]) @@ -1280,20 +1302,20 @@ class Widget_TextInput(gtk.Entry): self.has_focus = False self.show_all() - + def is_focused(self): return self.has_focus - + def __out_focus(self, widget, event): - + if not widget.is_focus(): self.get_toplevel().save_text() - + else: widget.select_region(0, -1) - + widget.has_focus = False - + def __on_entry_clicked(self, widget, event, data=None): if not widget.is_focused() and event.type == gtk.gdk.BUTTON_RELEASE: widget.select_region(0, -1) @@ -1419,16 +1441,14 @@ class Widget_RadioButon(gtk.VButtonBox): grupo = None - keys = self.options.keys() - order = {} - for key in keys: + for key in self.options.keys(): id = int(self.options[key]["order"]) order[id] = key - + indices = order.keys() indices.sort() - + for id in indices: key = order[id] radio = MyRadioButton(key) @@ -1443,7 +1463,7 @@ class Widget_RadioButon(gtk.VButtonBox): loader.close() pixbuf = loader.get_pixbuf().scale_simple( - 80, 80, gdk.INTERP_BILINEAR) + 120, 120, gdk.INTERP_BILINEAR) radio.set_image(gtk.image_new_from_pixbuf(pixbuf)) @@ -1556,20 +1576,18 @@ class Widget_MultipleCheckBox(gtk.VButtonBox): self.options = options.copy() self.updating = False - keys = self.options.keys() - order = {} - for key in keys: + for key in self.options.keys(): id = int(self.options[key]["order"]) order[id] = key - + indices = order.keys() indices.sort() - + for id in indices: key = order[id] check = MyCheckButton(key, self.options[key].get("none", False)) - + text = self.options[key].get("text", None) imagen = self.options[key].get("img", "") @@ -1580,7 +1598,7 @@ class Widget_MultipleCheckBox(gtk.VButtonBox): loader.close() pixbuf = loader.get_pixbuf().scale_simple( - 80, 80, gdk.INTERP_BILINEAR) + 120, 120, gdk.INTERP_BILINEAR) check.set_image(gtk.image_new_from_pixbuf(pixbuf)) @@ -1616,34 +1634,34 @@ class Widget_MultipleCheckBox(gtk.VButtonBox): if self.updating: return options = self.get_children() - + activan = [] - + if widget.none and widget.get_active(): ### Desactivar todas las opciones que no sean "ninguna." for child in options: child = child.get_button() - + if child == widget: activan = [child.indice] continue - + child.set_active(False) - + elif not widget.none and widget.get_active(): ### Desactivar la opción "ninguna." for child in options: child = child.get_button() - + if child.none and child.get_active(): child.set_active(False) - + else: if child.get_active(): activan.append(child.indice) - + self.emit("new", activan) - + def update(self, dict): """ Cuando el usuario cambia de encuestado, @@ -1712,7 +1730,7 @@ class MyContainer(gtk.HBox): def get_button(self): return self.button - + def get_label(self): return self.text_label @@ -1726,11 +1744,18 @@ class MyCheckButton(gtk.CheckButton): if none == "true": none = True if none == "false": none = False self.none = none - + self.show_all() class MyRadioButton(gtk.RadioButton): + __gsignals__ = { + "key-press-event" : "override", + } + + def do_key_press_event(self, widget): + return True + def __init__(self, indice): gtk.RadioButton.__init__(self) @@ -1774,7 +1799,7 @@ class ToolbarEncuesta(gtk.HBox): self.encuesta_actual = None self.anterior = PageButton( - "", + "Anterior", img=PREV_PAGE_ACTIVE, imgs={"enter": PREV_PAGE_OVER, "down": PREV_PAGE_DOWN} ) @@ -1791,7 +1816,7 @@ class ToolbarEncuesta(gtk.HBox): # DOWN BUTTONS: < prev next > # TODO: self.__draw_pagination_buttons(self, parent_widget=vbox) - halign_prev = gtk.Alignment(0, 0, 0, 0) + halign_prev = gtk.Alignment(0.14, 0, 0, 0) halign_prev.set_padding(0, 0, 5, 0) halign_prev.add(self.anterior) halign_next = gtk.Alignment(0.85, 0, 0, 0) @@ -1805,32 +1830,31 @@ class ToolbarEncuesta(gtk.HBox): self.show_all() def __button_clicked(self, widget): - + if widget == self.siguiente: if not self.siguiente.active: dialogo = gtk.Dialog(parent = self.get_toplevel(), flags = gtk.DIALOG_MODAL, buttons = ("OK", gtk.RESPONSE_ACCEPT)) - - dialogo.set_size_request(450, 100) + + dialogo.set_size_request(450, 200) dialogo.set_border_width(15) - + label = gtk.Label("Todavía hay Preguntas sin Responder.") label.show() - + dialogo.vbox.pack_start(label, True, True, 0) - + dialogo.run() - + dialogo.destroy() - + self.emit("accion", widget.accion) panel = self.get_toplevel().panel if len(panel.visible_questions()) == 0: self.__button_clicked(widget) - class PageButton(gtk.Button): __gsignals__ = { @@ -1877,7 +1901,7 @@ class PageButton(gtk.Button): self.connect("enter", self.__button_event, "enter") self.connect("leave", self.__button_event, "leave") self.connect("update_status", self.__update_status) - + def __update_status(self, widget, panel): new_img = NEXT_PAGE_ACTIVE @@ -1903,7 +1927,7 @@ class PageButton(gtk.Button): self.emit("enter") def __button_event(self, widget, img_file=None): - + if not self.active: return if img_file is None: return @@ -2067,7 +2091,7 @@ class GeneralWidget(gtk.VBox): box = gtk.VBox() continuar = gtk.Button("Continuar") - siguiente = gtk.Button("Siguiente") + siguiente = gtk.Button("Siguiente Alumno") box.pack_start(continuar, True, True, 0) box.pack_start(siguiente, True, True, 0) @@ -2373,13 +2397,36 @@ class Wizard(gtk.Dialog): return valido def validate_json_file(self): - valido = False + + valido = True if self.json_file: extension = os.path.splitext(os.path.split(self.json_file)[1])[1] - if "json" in extension or "slv" in extension: - valido = True + if "json" in extension: + import json + import codecs + + try: + archivo = codecs.open(self.json_file, "r", "utf-8") + enc = json.JSONDecoder("utf-8").decode(archivo.read()) + + keys = enc.keys() + + for key in ["pollster_id", "poll_name", "poll_id", "groups", "pollster_username"]: + if not key in keys: + valido = False + break + except ValueError: + valido = False + finally: + archivo.close() + + else: + valido = False + + else: + valido = False return valido diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.10.xo b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.10.xo Binary files differnew file mode 100644 index 0000000..d2592b7 --- /dev/null +++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.10.xo diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xo b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xo Binary files differnew file mode 100644 index 0000000..d9ce458 --- /dev/null +++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xo diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zip b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zip Binary files differnew file mode 100644 index 0000000..e82901a --- /dev/null +++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zip diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zip b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zip Binary files differnew file mode 100644 index 0000000..19ab49f --- /dev/null +++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zip diff --git a/CeibalEncuesta/gtk2/setup.py b/CeibalEncuesta/gtk2/setup.py index 07ead24..ee74568 100644 --- a/CeibalEncuesta/gtk2/setup.py +++ b/CeibalEncuesta/gtk2/setup.py @@ -7,7 +7,7 @@ import commands from distutils.core import setup -APP_VERSION = "4.9" +APP_VERSION = "4.11" USER_BASE = site.getuserbase() diff --git a/webapp/coffee/factoryField.coffee b/webapp/coffee/factoryField.coffee new file mode 100644 index 0000000..fed8612 --- /dev/null +++ b/webapp/coffee/factoryField.coffee @@ -0,0 +1,190 @@ +factoryField = (order, value) -> + # Get field.widget_type + widget_type = value['widget_type'] + group_order = value['group_order'] + errors = [] + + # If errors, prepare it + if value.hasOwnProperty('errors') + $.each( + value['errors'], + (i, error) -> errors.push({'error': error}) + ) + + # Preparing values for render widget type select box + widget_types = [].concat(WIDGET_TYPES) + widget_types = $.each( + widget_types, + (i, v) -> + if widget_type == v['key'] + $.extend(v, {"selected": on}) + else + # TODO: ver como pasar por copia valores en javascript + delete v.selected + ) + dependence_forms = [ + {'key': "4", 'value': "Sin parentesis" }, + {'key': "1", 'value': "( ID o/y ID ) o/y ( ID o/y ID )"}, + {'key': "2", 'value': "( ID o/y ID o/y ID ) o/y ID"}, + {'key': "3", 'value': "( ID o/y ID ) o/y ID o/y ID"} + ] + dependence_forms = $.each( + dependence_forms, + (i, v) -> + if value.hasOwnProperty('dependence') + if value['dependence'].form == v['key'] + $.extend(v, {"selected": on}) + ) + with_dependence_form = group_order != 0 or (group_order == 0 and order != 0) + + img_name = value['img'] + if img_name? + img_src = IMAGE_MEDIA_URL + "/" + img_name + else + img_src = "/static/img/50x50.gif" + + field_widget = $( + Mustache.render( + TEMPLATES['field'], + { + "order": order, + "group_order": group_order, + "name": value['name'], + "dependence": value['dependence'], + "errors": errors, + "visible_errors": errors.length, + "WIDGET_TYPES": widget_types, + "dependence_forms": dependence_forms, + "img_src": img_src, + "img_name": img_name, + } + ) + ) + + # Get remove field button and bind remove field widget envent + remove_button = field_widget.find('.WField_remove') + bindFieldRemoveButton(remove_button) + + add_option_button = field_widget.find('.WFieldOptions_add_button') + bindFieldAddOptionButton(add_option_button) + + add_option_none_button = field_widget.find('.WFieldOptionsNone_add_button') + bindFieldAddOptionButton(add_option_none_button) + + # Bind change widget type event + widget_types_select_box = field_widget.find('.WFieldWidgetType') + bindFieldWidgetTypeSelectBox(widget_types_select_box) + + # Adding add_option button if is needed + buttons_container = field_widget.find('.WFieldAddOptionButton_container') + if widget_type and $.inArray(widget_type, WITH_OPTIONS) != -1 + add_option_button = $( + Mustache.render( + TEMPLATES['field_add_option_button'], + {} + ) + ) + buttons_container.append(add_option_button) + bindFieldAddOptionButton(add_option_button) + + if widget_type and $.inArray(widget_type, ["MultipleCheckBox", "ImageCheckBox"]) != -1 + add_option_none_button = $(Mustache.render(TEMPLATES['field_add_option_none_button'], {})) + buttons_container.append(add_option_none_button) + bindFieldAddOptionButton(add_option_none_button, {none: 'True'}) + + # Render options + if $.inArray(widget_type, WITH_OPTIONS) != -1 + if $.inArray(widget_type, WITH_IMAGES) == -1 + # Basic option + $.each( + value['options'] or [], + (id, opt_value) -> + options_container = field_widget.find('.WFieldOptions_container') + factoryOption(id, opt_value, options_container, group_order, order) + ) + else + # Image option + $.each( + value['options'] or [], + (id, opt_value) -> + options_container = field_widget.find('.WFieldOptions_container') + if opt_value.hasOwnProperty('img_name') + factoryImageOptionThumbnail(id, opt_value, options_container, group_order, order) + else + factoryImageOptionUpload(id, opt_value, options_container, group_order, order) + ) + else + if value['options'] and Object.keys(value['options']).length + $.each( + value['options'] or [], + (id, opt_value) -> + factoryOptionDefault(group_order, field_widget, id, opt_value['text']) + ) + else + factoryOptionDefault(group_order, field_widget, undefined, undefined) + + # Show dependences + values = [] + dependence_container = field_widget.find(".dependence") + + if value['dependence'] + values = value['dependence'].values + + values[i] = '' for i in [0..6] when i >= values.length + + factoryDependences(dependence_container, values, order, group_order) + + dependences = field_widget.find('[name*=".dependence.values"]') + $.each( + dependences, + (index, dependence) -> + if $(dependence).attr("value") != '' + $(dependence).addClass("ui-state-highlight") + ) + + parentesis_open = field_widget.find("._parentesis_open") + if parentesis_open.length == 8 + $(parentesis_open[0]).addClass('1 2 3') + $(parentesis_open[4]).addClass('1') + + parentesis_close = field_widget.find("._parentesis_close") + if parentesis_close.length == 8 + $(parentesis_close[3]).addClass('1 3') + $(parentesis_close[5]).addClass('2') + $(parentesis_close[7]).addClass('1') + + widget_selected_dependence_form = field_widget.find('.dependence_form') + $(widget_selected_dependence_form).on( + 'change', + (event) -> + change_dependence_form(this, null) + ) + + change_dependence_form(widget_selected_dependence_form[0], field_widget) + + if not with_dependence_form + $(field_widget).find(".dependence_form").remove() + $(field_widget).find(".dependence_form_label").remove() + $(field_widget).find(".dependence").remove() + $(field_widget).find(".dependence_label").remove() + + droppable = field_widget.find(".droppable") + droppable.droppable( + { + drop: (event, ui) -> + value = ui.draggable[0]['innerText'] + $(this).addClass("ui-state-highlight").attr("value", value) + } + ) + droppable.focusout( + -> + if $(this).attr('value') == "" + $(this).removeClass("ui-state-highlight") + ) + + # Show the field widget + container = value['container'] + container.append(field_widget) + + +window.factoryField = factoryField
\ No newline at end of file diff --git a/webapp/js_tests/files.json b/webapp/js_tests/files.json index f7e5203..b0e9f4f 100644 --- a/webapp/js_tests/files.json +++ b/webapp/js_tests/files.json @@ -4,6 +4,7 @@ "/static/js/jquery.min.js", "/static/js/jquery-ui.js", "/static/js/mustache-0.7.0.js", + "/static/js/factoryField.js", "/static/js/dynamic_structure.js" ], "media_files": [ diff --git a/webapp/js_tests/fixtures/container.html b/webapp/js_tests/fixtures/container.html index d1b1cc1..64e27d3 100644 --- a/webapp/js_tests/fixtures/container.html +++ b/webapp/js_tests/fixtures/container.html @@ -24,5 +24,6 @@ OFFSET_OPTION_ID = 0, WITH_IMAGES = ["ImageCheckBox", "ImageRadioButton"], IMAGE_OPTIONS_TMP_MEDIA_URL, + IMAGE_MEDIA_URL, POLL_ID; </script>
\ No newline at end of file diff --git a/webapp/js_tests/spec/DynamicStructureSpec.js b/webapp/js_tests/spec/DynamicStructureSpec.js index f35a93d..8b5c84b 100644 --- a/webapp/js_tests/spec/DynamicStructureSpec.js +++ b/webapp/js_tests/spec/DynamicStructureSpec.js @@ -159,285 +159,4 @@ describe("", function() { }); - describe("Fields", function() { - - beforeEach(function() { - - jasmine.getFixtures().fixturesPath = "fixtures/"; - loadFixtures("container.html"); - - var field_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/field.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(field_mustache_template); - - Mustache.tags = ['[[', ']]']; - - // Preparing mustache TEMPLATES - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - }); - - it("- Field template must contains right classes", function() { - var field_template = $(TEMPLATES['field']); - - var field_remove_button = field_template.find('.WField_remove'); - expect(field_remove_button).toExist(); - expect(field_remove_button).toBe(':button'); - - expect(field_template.find('.error')).toExist(); - - expect(field_template.find('.WFieldWidgetType')).toExist(); - - expect(field_template.find('.WFieldAddOptionButton_container')).toExist(); - - expect(field_template.find('.WFieldOptions_container')).toExist(); - }); - - it("Second option must has greater order than firt option", function() { - - // Preparing mustache TEMPLATES - var option_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_mustache_template); - - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - factoryField("0", {"container": container, "widget_type": "MultipleCheckBox"}); - - var field_widget = container.children(); - var field_add_option_button = $(field_widget.find('.WFieldOptions_add_button')); - field_add_option_button = field_add_option_button.find('button'); - field_add_option_button.click(); - var first_option = $(field_widget.find(".WFieldOptions_container .row-fluid")[0]); - - var field_widget = container.children(); - var field_add_option_button = $(field_widget.find('.WFieldOptions_add_button')); - field_add_option_button = field_add_option_button.find('button'); - field_add_option_button.click(); - var second_option = $(field_widget.find(".WFieldOptions_container .row-fluid")[1]); - - var first_option_order = $(first_option.find("[name*='.order']")[0]).val(); - var second_option_order = $(second_option.find("[name*='.order']")[0]).val(); - - expect( first_option_order ).toBeLessThan( second_option_order ); - - }); - - describe("Options except TextInput and image widget types", function() { - - it("option widget has an order", function() { - - jasmine.getFixtures().fixturesPath = "fixtures/"; - loadFixtures("container.html"); - - var option_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_mustache_template); - - Mustache.tags = ['[[', ']]']; - - // Preparing mustache TEMPLATES - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - factoryOption("1", "", container, "0", "0"); - - options = container.children(); - - option_1_order = options.find( - "[name='groups.0.fields.0.options.1.order']")[0]; - - expect(option_1_order).not.toBeUndefined(); - - }); - - }); - - describe("Option none", function() { - - it("should have a none attribute with value True", function() { - - jasmine.getFixtures().fixturesPath = "fixtures/"; - loadFixtures("container.html"); - - var option_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_mustache_template); - - Mustache.tags = ['[[', ']]']; - - // Preparing mustache TEMPLATES - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - factoryOption("1", {none: 'True'}, container, "0", "0"); - - options = container.children(); - option_1_none = options.find( - "[name='groups.0.fields.0.options.1.none']")[0]; - - expect(option_1_none).not.toBeUndefined(); - expect($(option_1_none).val()).toBe('True'); - - }); - - }); - - describe("TextInput widget type options", function() { - - it("option widget has an order", function() { - - jasmine.getFixtures().fixturesPath = "fixtures/"; - loadFixtures("container.html"); - - var option_default_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option_default.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_default_mustache_template); - - Mustache.tags = ['[[', ']]']; - - // Preparing mustache TEMPLATES - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - factoryField("0", {"container": container, "widget_type": "TextInput"}); - - var field_widget = container.children(); - - factoryOptionDefault("0", field_widget, "1", ""); - - options = container.children(); - - option_1_order = options.find( - "[name='groups.0.fields.0.options.1.order']")[0]; - - expect(option_1_order).not.toBeUndefined(); - - expect($(option_1_order).val()).toBe("0"); - - }); - - }); - - describe("Image thumbnail widget type options", function() { - - it("option widget has an order", function() { - - jasmine.getFixtures().fixturesPath = "fixtures/"; - loadFixtures("container.html"); - - var option_image_thumbnail_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option_image_thumbnail.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_image_thumbnail_mustache_template); - - Mustache.tags = ['[[', ']]']; - - // Preparing mustache TEMPLATES - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - factoryField("0", {"container": container, "widget_type": "ImageCheckBox"}); - - var field_widget = container.children(); - - jQuery.fn.popover = function(){}; - - factoryImageOptionThumbnail("1", {}, container, "0", "0", "0"); - - options = container.children(); - - option_1_order = options.find( - "[name='groups.0.fields.0.options.1.order']")[0]; - - expect(option_1_order).not.toBeUndefined(); - - expect($(option_1_order).val()).toBe("0"); - - }); - - }); - - describe("Image upload widget type options", function() { - - beforeEach(function() { - - jasmine.getFixtures().fixturesPath = "fixtures/"; - loadFixtures("container.html"); - - var option_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_mustache_template); - - var option_image_upload_mustache_template = $.ajax({ - type: "GET", - url: '/jasmine/mustache_templates/option_image_upload.html', - cache: false, - async: false - }).responseText; - $("#jasmine-fixtures").append(option_image_upload_mustache_template); - - Mustache.tags = ['[[', ']]']; - - // Preparing mustache TEMPLATES - $('script[type="text/x-mustache-template"]').each(function(i, obj){ - TEMPLATES[$(obj).attr('name')] = $(obj).text(); - }); - - }); - - it("option widget has an order", function() { - - factoryImageOptionUpload("1", {}, container, "0", "0", "0"); - - options = container.children(); - - option_1_order = options.find( - "[name='groups.0.fields.0.options.1.order']")[0]; - - expect(option_1_order).not.toBeUndefined(); - - expect($(option_1_order).val()).toBe("0"); - - }); - - }); - - }); - });
\ No newline at end of file diff --git a/webapp/js_tests/spec/FieldsSpec.js b/webapp/js_tests/spec/FieldsSpec.js new file mode 100644 index 0000000..c143d1a --- /dev/null +++ b/webapp/js_tests/spec/FieldsSpec.js @@ -0,0 +1,280 @@ +describe("Fields", function() { + + beforeEach(function() { + + jasmine.getFixtures().fixturesPath = "fixtures/"; + loadFixtures("container.html"); + + var field_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/field.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(field_mustache_template); + + Mustache.tags = ['[[', ']]']; + + // Preparing mustache TEMPLATES + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + }); + + it("- Field template must contains right classes", function() { + var field_template = $(TEMPLATES['field']); + + var field_remove_button = field_template.find('.WField_remove'); + expect(field_remove_button).toExist(); + expect(field_remove_button).toBe(':button'); + + expect(field_template.find('.error')).toExist(); + + expect(field_template.find('.WFieldWidgetType')).toExist(); + + expect(field_template.find('.WFieldAddOptionButton_container')).toExist(); + + expect(field_template.find('.WFieldOptions_container')).toExist(); + }); + + it("Second option must has greater order than firt option", function() { + + // Preparing mustache TEMPLATES + var option_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_mustache_template); + + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + factoryField("0", {"container": container, "widget_type": "MultipleCheckBox"}); + + var field_widget = container.children(); + var field_add_option_button = $(field_widget.find('.WFieldOptions_add_button')); + field_add_option_button = field_add_option_button.find('button'); + field_add_option_button.click(); + var first_option = $(field_widget.find(".WFieldOptions_container .row-fluid")[0]); + + var field_widget = container.children(); + var field_add_option_button = $(field_widget.find('.WFieldOptions_add_button')); + field_add_option_button = field_add_option_button.find('button'); + field_add_option_button.click(); + var second_option = $(field_widget.find(".WFieldOptions_container .row-fluid")[1]); + + var first_option_order = $(first_option.find("[name*='.order']")[0]).val(); + var second_option_order = $(second_option.find("[name*='.order']")[0]).val(); + + expect( first_option_order ).toBeLessThan( second_option_order ); + + }); + + describe("Options except TextInput and image widget types", function() { + + it("option widget has an order", function() { + + jasmine.getFixtures().fixturesPath = "fixtures/"; + loadFixtures("container.html"); + + var option_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_mustache_template); + + Mustache.tags = ['[[', ']]']; + + // Preparing mustache TEMPLATES + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + factoryOption("1", "", container, "0", "0"); + + options = container.children(); + + option_1_order = options.find( + "[name='groups.0.fields.0.options.1.order']")[0]; + + expect(option_1_order).not.toBeUndefined(); + + }); + + }); + + describe("Option none", function() { + + it("should have a none attribute with value True", function() { + + jasmine.getFixtures().fixturesPath = "fixtures/"; + loadFixtures("container.html"); + + var option_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_mustache_template); + + Mustache.tags = ['[[', ']]']; + + // Preparing mustache TEMPLATES + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + factoryOption("1", {none: 'True'}, container, "0", "0"); + + options = container.children(); + option_1_none = options.find( + "[name='groups.0.fields.0.options.1.none']")[0]; + + expect(option_1_none).not.toBeUndefined(); + expect($(option_1_none).val()).toBe('True'); + + }); + + }); + + describe("TextInput widget type options", function() { + + it("option widget has an order", function() { + + jasmine.getFixtures().fixturesPath = "fixtures/"; + loadFixtures("container.html"); + + var option_default_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option_default.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_default_mustache_template); + + Mustache.tags = ['[[', ']]']; + + // Preparing mustache TEMPLATES + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + factoryField("0", {"container": container, "widget_type": "TextInput"}); + + var field_widget = container.children(); + + factoryOptionDefault("0", field_widget, "1", ""); + + options = container.children(); + + option_1_order = options.find( + "[name='groups.0.fields.0.options.1.order']")[0]; + + expect(option_1_order).not.toBeUndefined(); + + expect($(option_1_order).val()).toBe("0"); + + }); + + }); + + describe("Image thumbnail widget type options", function() { + + it("option widget has an order", function() { + + jasmine.getFixtures().fixturesPath = "fixtures/"; + loadFixtures("container.html"); + + var option_image_thumbnail_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option_image_thumbnail.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_image_thumbnail_mustache_template); + + Mustache.tags = ['[[', ']]']; + + // Preparing mustache TEMPLATES + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + factoryField("0", {"container": container, "widget_type": "ImageCheckBox"}); + + var field_widget = container.children(); + + jQuery.fn.popover = function(){}; + + factoryImageOptionThumbnail("1", {}, container, "0", "0", "0"); + + options = container.children(); + + option_1_order = options.find( + "[name='groups.0.fields.0.options.1.order']")[0]; + + expect(option_1_order).not.toBeUndefined(); + + expect($(option_1_order).val()).toBe("0"); + + }); + + }); + + describe("Image upload widget type options", function() { + + beforeEach(function() { + + jasmine.getFixtures().fixturesPath = "fixtures/"; + loadFixtures("container.html"); + + var option_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_mustache_template); + + var option_image_upload_mustache_template = $.ajax({ + type: "GET", + url: '/jasmine/mustache_templates/option_image_upload.html', + cache: false, + async: false + }).responseText; + $("#jasmine-fixtures").append(option_image_upload_mustache_template); + + Mustache.tags = ['[[', ']]']; + + // Preparing mustache TEMPLATES + $('script[type="text/x-mustache-template"]').each(function(i, obj){ + TEMPLATES[$(obj).attr('name')] = $(obj).text(); + }); + + }); + + it("option widget has an order", function() { + + factoryImageOptionUpload("1", {}, container, "0", "0", "0"); + + options = container.children(); + + option_1_order = options.find( + "[name='groups.0.fields.0.options.1.order']")[0]; + + expect(option_1_order).not.toBeUndefined(); + + expect($(option_1_order).val()).toBe("0"); + + }); + + }); + +});
\ No newline at end of file diff --git a/webapp/polls/models.py b/webapp/polls/models.py index e6f3813..b9f9410 100644 --- a/webapp/polls/models.py +++ b/webapp/polls/models.py @@ -129,7 +129,10 @@ class Poll(Document, AbstracErrorObject): path = self.structure.get_image_options_path() clone_path = poll_clone.structure.get_image_options_path() + copy_tree(path, clone_path) + path = self.structure.get_image_path() + clone_path = poll_clone.structure.get_image_path() copy_tree(path, clone_path) return poll_clone @@ -391,7 +394,7 @@ class Poll(Document, AbstracErrorObject): return questions - def get_csv_header(self): + def get_csv_header(self, with_order=False): MANY_ANSWERS = [ "MultipleCheckBox", @@ -414,13 +417,27 @@ class Poll(Document, AbstracErrorObject): for group_id in sorted_groups: fields = groups[str(group_id)]['fields'] sorted_fields = sorted(map(lambda x: int(x), fields.keys())) + for field_id in sorted_fields: field = fields[str(field_id)] + + order = "%s_%s " % (group_id, field_id) + if field['widget_type'] in ONLY_ANSWER: - header.append(field['name']) + col_name = field['name'] + if with_order: + col_name = order + col_name + header.append(col_name) + elif field['widget_type'] in MANY_ANSWERS: - for option_id, option in field['options'].iteritems(): + cmp_ = lambda x, y: cmp( + int(x[1]['order']), int(y[1]['order'])) + sorted_options = sorted(field['options'].items(), cmp_) + + for option_id, option in sorted_options: col_name = option.get("text", option_id) + if with_order: + col_name = order + col_name header.append(col_name) return ";".join(header) @@ -665,9 +682,13 @@ class Field(AbstractObject, ComponentStructure): def __init__(self, data={}, *args, **kwargs): super(Field, self).__init__(*args, **kwargs) + self.poll_id = None + order = data.get('order', None) self.order = int(order) if order else order self.name = data.get('name', None) + self.img = data.get('img', None) + self.uploaded_file = data.get("uploaded_file", None) value = data.get('dependence', None) self.dependence = value @@ -734,6 +755,13 @@ class Field(AbstractObject, ComponentStructure): if img and opt not in self.options: self.options.append(opt) + def img_validate(self, img_file): + try: + ImageField().to_python(img_file) + except DjangoValidationError, e: + self.img = None + raise Field.ValidationError(e.messages[0]) + def validate(self, options=[], new_data=None): self.errors = [] rule, msg = Field.VALIDATION_RULES.get(self.widget_type) @@ -796,10 +824,38 @@ class Field(AbstractObject, ComponentStructure): msg = "Necesita ingresar una pregunta" self.errors.append(msg) + if self.uploaded_file: + try: + self.img_validate(self.uploaded_file) + except Field.ValidationError: + self.errors.append(u"Pregunta con imagen no válida.") + if len(self.errors): raise Field.ValidationError(str(self.errors)) - def to_python(self, with_errors=False, img_serialize=False): + def storing_image(self, path, img_file): + img_path = path + '/%s' % img_file.name + if os.path.exists(img_path): + os.remove(img_path) + + with open(img_path, 'wb+') as dst: + for chunk in img_file.chunks(): + dst.write(chunk) + dst.close() + + def store_image(self, path): + if self.uploaded_file: + self.storing_image(path, self.uploaded_file) + + def get_img_name(self): + return getattr(self.uploaded_file, "name", self.img) + + def get_img_absolute_path(self, poll_id): + path = settings.IMAGES_ROOT + '/%s' % poll_id + return path + "/%s" % self.get_img_name() + + # TODO: Need refactoring for receive poll_id in other way + def to_python(self, with_errors=False, img_serialize=False, poll_id=None): data = {} data.update({ @@ -810,6 +866,19 @@ class Field(AbstractObject, ComponentStructure): if self.dependence: data.update({'dependence': self.dependence}) + img_name = self.get_img_name() + if img_name: + if img_serialize: + img_path = self.get_img_absolute_path(poll_id) + + img_file = open(img_path, 'rb') + image_string = base64.b64encode(img_file.read()) + img_file.close() + + data.update({'img': image_string}) + else: + data.update({'img': img_name}) + if with_errors: data.update({'errors': self.errors}) @@ -855,7 +924,7 @@ class Group(AbstractObject, ComponentStructure): if len(self.errors): raise Group.ValidationError(str(self.errors)) - def to_python(self, with_errors=False, img_serialize=False): + def to_python(self, with_errors=False, img_serialize=False, poll_id=None): data = {'name': self.name, 'fields': {}} if with_errors: @@ -863,7 +932,9 @@ class Group(AbstractObject, ComponentStructure): for field_obj in self.fields: field_data = field_obj.to_python( - with_errors=with_errors, img_serialize=img_serialize + with_errors=with_errors, + img_serialize=img_serialize, + poll_id=poll_id ) data['fields'].update(field_data) @@ -968,10 +1039,22 @@ class Structure(AbstractObject, ComponentStructure): return options + def get_image_media_url(self): + return settings.IMAGES_MEDIA_URL + '/%s' % self.poll_id + def get_image_options(self): options = self.get_options() return filter(lambda opt: opt.img_name is not None, options) + def get_images_fields(self): + fields = [] + for group in self.groups: + for field in group.fields: + fields.append(field) + + fields = filter(lambda field: field.get_img_name() is not None, fields) + return [field.get_img_name() for field in fields] + def validate(self): self.errors = [] @@ -1014,7 +1097,9 @@ class Structure(AbstractObject, ComponentStructure): for group_obj in self.groups: data['groups'].update( group_obj.to_python( - with_errors=with_errors, img_serialize=img_serialize + with_errors=with_errors, + img_serialize=img_serialize, + poll_id=self.poll_id ) ) @@ -1041,7 +1126,7 @@ class Structure(AbstractObject, ComponentStructure): if self.id: _dict.update({'_id': ObjectId(self.id)}) - # Save process -> Update if it have id, else insert + # Save process -> Update if it has an id, else insert structure_id = get_db().structures.save(_dict) # Removing older img options files @@ -1055,6 +1140,16 @@ class Structure(AbstractObject, ComponentStructure): except: pass + # Removing older img files + current_img_fields = self.get_images_fields() + path = self.get_image_path() + for file in os.listdir(path): + if file not in current_img_fields: + try: + os.remove("%s/%s" % (path, file)) + except: + pass + return structure_id @staticmethod @@ -1115,6 +1210,22 @@ class Structure(AbstractObject, ComponentStructure): dst.write(chunk) dst.close() + def get_image_path(self): + path = settings.IMAGES_ROOT + '/%s' % self.poll_id + + try: + os.makedirs(path) + except OSError: + pass + + return path + + def save_image_fields(self): + + for group in self.groups: + for field in group.fields: + field.store_image(path=self.get_image_path()) + def save_image_options(self, tmp=False): options = self.get_options() @@ -1251,7 +1362,7 @@ class PollResult(AbstractObject): return poll_result_id - def get_csv_header(self): + def get_csv_header(self, with_order=False): poll = self.get_poll() @@ -1282,13 +1393,14 @@ class PollResult(AbstractObject): 'NOMBRE' ] - header = poll_record + poll.get_csv_header().split(";") - return ";".join(header) + ";" + poll_header = poll.get_csv_header(with_order=with_order) + header = poll_record + poll_header.split(";") + return ";".join(header) def to_csv(self): - poll_csv_header = self.get_csv_header() - + poll = self.get_poll() + poll_csv_header = self.get_csv_header(with_order=True) header_order = poll_csv_header.split(";") MANY_ANSWERS = [ @@ -1306,18 +1418,22 @@ class PollResult(AbstractObject): results = [] for index_record, respuesta in self._data["result"].iteritems(): - aux = dict([(x, "") for x in header_order]) + poll_col_names = poll.get_csv_header(with_order=True).split(";") + aux = dict([(x, "") for x in poll_col_names]) for key, value in respuesta['polled'].iteritems(): if key in header_order: aux[key] = value + for index_group, data_group in respuesta['answers'].iteritems(): for index_preg, data_preg in data_group['fields'].iteritems(): + order = u"%s_%s " % (index_group, index_preg) widget_type = data_preg['widget_type'] name = data_preg['name'] name = unicode(name, "utf-8") if isinstance( name, str) else name + name = order + name answer = data_preg['answer'] if len(answer) and widget_type in ONLY_ANSWER: @@ -1329,16 +1445,18 @@ class PollResult(AbstractObject): elif len(answer) and widget_type in MANY_ANSWERS: for opt_id, opt_value in answer.iteritems(): text = opt_value.get('text', None) - coln_name = text if text else opt_id - aux[coln_name] = str(opt_value.get("weight", "-")) + col_name = text if text else opt_id + col_name = order + col_name + aux[col_name] = str(opt_value.get("weight", "-")) record = [] for key in header_order: record.append(aux[key]) - record = ";".join(record) + record = ";".join(record) + ";" results.append(record) - return poll_csv_header + "\n" + ";\n".join(results) + poll_csv_header = self.get_csv_header() + ";\n" + return poll_csv_header + "\n".join(results) def sorted(self): data = self._data['result'] diff --git a/webapp/polls/templates/mustache/field.html b/webapp/polls/templates/mustache/field.html index 2a4b130..6283c43 100644 --- a/webapp/polls/templates/mustache/field.html +++ b/webapp/polls/templates/mustache/field.html @@ -49,6 +49,20 @@ value="[[ name ]]" placeholder="Pregunta..." /> + <input type="hidden" name="groups.[[ group_order ]].fields.[[ order ]].img" value="[[ img_name ]]" /> + + <span class="fileupload fileupload-new" data-provides="fileupload"> + <div class="fileupload-new thumbnail" style="width: 50px; height: 50px;"> + <img src="[[ img_src ]]" /></div> + <div class="fileupload-preview fileupload-exists thumbnail" style="width: 50px; height: 50px;"></div> + <span class="btn btn-file"> + <span class="fileupload-new">Elegir imagen</span> + <span class="fileupload-exists">Change</span> + <input type="file" name="groups.[[ group_order ]].fields.[[ order ]].uploaded_file" /> + </span> + <a href="#" class="btn fileupload-exists" data-dismiss="fileupload">Remove</a> + </span> + </div> <div class="row-fluid"> diff --git a/webapp/polls/templates/tags/structure.html b/webapp/polls/templates/tags/structure.html index 7f4c0d9..61e0c63 100644 --- a/webapp/polls/templates/tags/structure.html +++ b/webapp/polls/templates/tags/structure.html @@ -43,12 +43,13 @@ WITH_IMAGES = {{ WITH_IMAGES|json }}, OFFSET_OPTION_ID = {{ OFFSET_OPTION_ID|json }}, IMAGE_OPTIONS_TMP_MEDIA_URL = {{ IMAGE_OPTIONS_TMP_MEDIA_URL|json }}, + IMAGE_MEDIA_URL = {{ IMAGE_MEDIA_URL|json }}, POLL_ID = {{ POLL_ID|json }}; - </script> <!-- Dynamic structure methods --> +<script src="{{ STATIC_URL }}js/factoryField.js"></script> <script src="{{ STATIC_URL }}js/dynamic_structure.js"></script> diff --git a/webapp/polls/templatetags/poll_tags.py b/webapp/polls/templatetags/poll_tags.py index c28d237..5cbb284 100644 --- a/webapp/polls/templatetags/poll_tags.py +++ b/webapp/polls/templatetags/poll_tags.py @@ -11,6 +11,7 @@ register = template.Library() def render_structure(context, structure): structure_data = structure.to_python(with_errors=True) IMAGE_OPTIONS_TMP_MEDIA_URL = structure.get_image_options_tmp_media_url() + IMAGE_MEDIA_URL = structure.get_image_media_url() return render_to_string( 'tags/structure.html', @@ -22,7 +23,8 @@ def render_structure(context, structure): "OFFSET_OPTION_ID": Field.get_offset_id(), "IMAGE_OPTIONS_TMP_MEDIA_URL": IMAGE_OPTIONS_TMP_MEDIA_URL, "POLL_ID": str(context['poll'].id), - "groups": structure_data.get("groups", {}) + "groups": structure_data.get("groups", {}), + "IMAGE_MEDIA_URL": IMAGE_MEDIA_URL } ) diff --git a/webapp/polls/tests/field_tests.py b/webapp/polls/tests/field_tests.py index b7c6a18..8609357 100644 --- a/webapp/polls/tests/field_tests.py +++ b/webapp/polls/tests/field_tests.py @@ -4,7 +4,7 @@ from django.test import TestCase from polls.models import ( Poll, Dependence, Field, WIDGET_TYPES, WITH_OPTIONS, WITH_IMAGES) -from utils.test import MongoTestCase, mock_in_memory_image +from utils.test import MongoTestCase, mock_in_memory_image, mock_text_file class DependenceTests(TestCase): @@ -240,6 +240,19 @@ class FieldTests(MongoTestCase): self.assertRaises(Field.ValidationError, self.field.validate) self.assertEqual(1, len(self.field.errors)) + def test_to_python_with_img_name(self): + + data = { + 'order': 0, + 'name': "field_0", + } + field = Field(data=data) + field.img = 'img_name' + + to_python = field.to_python()['0'] + self.assertTrue('img' in to_python.keys()) + self.assertEqual('img_name', to_python['img']) + def test_to_python(self): # Widget_type = TextInput @@ -384,3 +397,13 @@ class FieldTests(MongoTestCase): 'values': ['ID', 'AND', 'ID'] } self.assertEqual(expected, field.dependence) + + def test_image_validation(self): + invalid_img_file = mock_text_file() + + data = {'id': '0', 'name': 'name', 'img': invalid_img_file} + field = Field(data) + + img_validate = lambda: field.img_validate(invalid_img_file) + self.assertRaises(Field.ValidationError, img_validate) + self.assertIsNone(field.img) diff --git a/webapp/polls/tests/result_tests.py b/webapp/polls/tests/result_tests.py index 9a646ba..7afd48f 100644 --- a/webapp/polls/tests/result_tests.py +++ b/webapp/polls/tests/result_tests.py @@ -153,15 +153,17 @@ class PollResultTests(MongoTestCase): "dependence": {}, "name": "Q: ImageCheckBox", "options": { - "1370786178812": { - "img_name": "1370786178812.jpg", - "type": "img_input", - "weight": 1 - }, "1370786178813": { "img_name": "1370786178813.jpg", "type": "img_input", - "weight": 2 + "weight": 2, + "order": "1" + }, + "1370786178812": { + "img_name": "1370786178812.jpg", + "type": "img_input", + "weight": 1, + "order": "2" } } }, @@ -172,11 +174,13 @@ class PollResultTests(MongoTestCase): "options": { "1370786178809": { "text": "option 1", - "weight": 1 + "weight": 1, + "order": "1" }, "1370786178810": { "text": "option 2", - "weight": 2 + "weight": 2, + "order": "2" } } } @@ -255,9 +259,9 @@ class PollResultTests(MongoTestCase): "pollster_id": "51b0e946421aa90df17182e9", "pollster_username": "encuestador 1", "result": { - "10": { + "100": { "answers": { - "0": { + "10": { "fields": { "0": { "answer": { @@ -347,13 +351,204 @@ class PollResultTests(MongoTestCase): poll_result = PollResult(data=result_data) expected_header = unicode( "RUEE;DEPARTAMENTO;NUM_ESC;GRADO;GRUPO;" + - "TIPO_GRUPO;option 1;option 2;1370786178812;1370786178813;" + + "TIPO_GRUPO;option 1;option 2;1370786178813;1370786178812;" + "Q: TextInput con ácento;Q: RadioButton con ácento;" + - "Q: DropDownList;Q: ImageRadioButton;", 'utf-8') + "Q: DropDownList;Q: ImageRadioButton", 'utf-8') self.assertEqual(expected_header, poll_result.get_csv_header()) csv = poll_result.to_csv() - record = "1101236;MONTEVIDEO;236;2;A;1;1;2;1;;some text;1;1;1;" + record = "1101236;MONTEVIDEO;236;2;A;1;1;2;;1;some text;1;1;1;" + self.assertTrue(expected_header in csv) + self.assertTrue(record in csv) + + +class CsvNoRepitePesoParaOpcionesConElMismoTextDescriptivo(MongoTestCase): + """ + issue 4430 + """ + + def setUp(self): + poll_data = { + "name": "test", + "status": Poll.CLOSED + } + + question_name = "repite nombre pregunta" + structure_data = { + "groups": { + "0": { + "name": "grupo 1", + "fields": { + "0": { + "widget_type": "MultipleCheckBox", + "name": "pregunta 1", + "options": { + "1": { + "text": "no repite 1", + "weight": 1, + "order": "0" + }, + "2": { + "text": "se repite", + "weight": 2, + "order": "1" + } + } + }, + "1": { + "widget_type": "MultipleCheckBox", + "name": "pregunta 2", + "options": { + "1": { + "text": "no repite 2", + "weight": 3, + "order": "0" + }, + "2": { + "text": "se repite", + "weight": 10, + "order": "1" + } + } + }, + "2": { + "widget_type": "TextInput", + "name": question_name, + "options": { + "0": { + "text": "texto 1", + "weight": 5, + "order": "0" + } + } + }, + "3": { + "widget_type": "TextInput", + "name": question_name, + "options": { + "0": { + "text": "texto 2", + "weight": 1, + "order": "0" + } + } + } + } + } + } + } + + poll = Poll(data=poll_data) + poll_id = poll.save() + self.poll = Poll.get(poll_id) + structure = Structure(data=structure_data, poll=self.poll) + structure.save() + + def test_get_csv_header_with_order(self): + poll = self.poll + + poll_header = poll.get_csv_header(with_order=True) + + expected_header = unicode( + "0_0 no repite 1;0_0 se repite;" + + "0_1 no repite 2;0_1 se repite;" + + "0_2 repite nombre pregunta;0_3 repite nombre pregunta", + 'utf-8') + + self.assertEqual(expected_header, poll_header) + + def test_to_csv(self): + + poll = self.poll + + question_name = "repite nombre pregunta" + result_data = { + "poll_id": str(poll.id), + "poll_name": "", + "poll_type": "general", + "pollster_id": "cualquiera", + "pollster_username": "cualquiera", + "result": { + "0": { + "answers": { + "0": { + "name": "grupo 1", + "fields": { + "0": { + "widget_type": "MultipleCheckBox", + "name": "pregunta 1", + "answer": { + "1": { + "text": "no repite 1", + "weight": 1, + "order": "0" + }, + "2": { + "text": "se repite", + "weight": 2, + "order": "1" + } + } + }, + "1": { + "widget_type": "MultipleCheckBox", + "name": "pregunta 2", + "answer": { + "1": { + "text": "no repite 2", + "weight": 3, + "order": "0" + } + } + }, + "2": { + "widget_type": "TextInput", + "name": question_name, + "answer": { + "0": { + "text": "texto 1", + "weight": 5, + "order": "0" + } + } + }, + "3": { + "widget_type": "TextInput", + "name": question_name, + "answer": { + "0": { + "text": "texto 2", + "weight": 1, + "order": "0" + } + } + } + } + } + }, + "polled": { + "DEPARTAMENTO": "MONTEVIDEO", + "GRADO": "2", + "GRUPO": "A", + "ID": "0", + "NUM_ESC": "236", + "RUEE": "1101236", + "TIPO_GRUPO": "1" + } + } + } + } + + poll_result = PollResult(data=result_data) + + expected_header = unicode( + "RUEE;DEPARTAMENTO;NUM_ESC;GRADO;GRUPO;TIPO_GRUPO;" + + "no repite 1;se repite;no repite 2;se repite;" + + question_name + ";" + question_name + ";", + 'utf-8') + csv = poll_result.to_csv() + + record = "1101236;MONTEVIDEO;236;2;A;1;1;2;3;;texto 1;texto 2;" self.assertTrue(expected_header in csv) self.assertTrue(record in csv) diff --git a/webapp/polls/tests/structure_tests.py b/webapp/polls/tests/structure_tests.py index a91f854..cd10d56 100644 --- a/webapp/polls/tests/structure_tests.py +++ b/webapp/polls/tests/structure_tests.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- from polls.models import Poll, Group, Field, Structure -from utils.test import MongoTestCase +from utils.test import MongoTestCase, mock_in_memory_image class StructureTests(MongoTestCase): @@ -219,3 +219,49 @@ class StructureTests(MongoTestCase): structure.add_group(group, group.order) self.assertEqual(expected, structure.to_python()) + + def test_structure_with_a_question_with_img(self): + + poll = Poll({"name": "poll name"}) + poll_id = poll.save() + poll = poll.get(poll_id) + img = "img" + data = { + 'groups': { + '0': { + 'name': "group name", + 'fields': { + '0': { + 'name': "field_0_0", + 'widget_type': Field.TextInput, + 'options': { + '131212': { + 'text': "text", + 'order': 0 + } + }, + 'img': img + } + } + } + } + } + + structure = Structure(data=data, poll=poll) + + self.assertEqual(img, structure.groups[0].fields[0].img) + + def test_save_field_image(self): + img_name = "img_test.jpg" + img = mock_in_memory_image(name=img_name) + poll = self.poll + data = self.data + data['groups']['0']['fields']['0']['uploaded_file'] = img + structure = Structure(data=data, poll=poll) + + s_id = structure.save() + + structure = Structure.get(id=s_id) + + self.assertEqual( + img_name, structure.groups[0].fields[0].get_img_name()) diff --git a/webapp/polls/views.py b/webapp/polls/views.py index 9a5a631..a2239a3 100644 --- a/webapp/polls/views.py +++ b/webapp/polls/views.py @@ -230,6 +230,7 @@ class StructureFormView(TemplateView): if structure.is_valid(new_data=data): try: structure.save_image_options(tmp=False) + structure.save_image_fields() structure.save() except Exception: msg = u'Ocurrió un error, no se guardó la estructura.' @@ -254,6 +255,7 @@ class StructureFormView(TemplateView): ) else: structure.save_image_options(tmp=True) + structure.save_image_fields() context.update({'errors': structure.errors}) messages.add_message( self.request, diff --git a/webapp/webapp/media/images/empty b/webapp/webapp/media/images/empty new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/webapp/webapp/media/images/empty diff --git a/webapp/webapp/settings.py b/webapp/webapp/settings.py index 218ed8a..afdde63 100644 --- a/webapp/webapp/settings.py +++ b/webapp/webapp/settings.py @@ -208,10 +208,14 @@ JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/../js_tests/' IMAGE_OPTIONS_ROOT = MEDIA_ROOT + 'image_options' +IMAGES_ROOT = MEDIA_ROOT + 'images' + RESULT_BCK_ROOT = MEDIA_ROOT + 'results_bck' IMAGE_OPTIONS_MEDIA_URL = MEDIA_URL + 'image_options' +IMAGES_MEDIA_URL = MEDIA_URL + 'images' + THUMBNAIL_DEBUG = True try: diff --git a/webapp/webapp/static/img/50x50.gif b/webapp/webapp/static/img/50x50.gif Binary files differnew file mode 100644 index 0000000..651f890 --- /dev/null +++ b/webapp/webapp/static/img/50x50.gif diff --git a/webapp/webapp/static/js/dynamic_structure.js b/webapp/webapp/static/js/dynamic_structure.js index 1773100..9a6aef9 100644 --- a/webapp/webapp/static/js/dynamic_structure.js +++ b/webapp/webapp/static/js/dynamic_structure.js @@ -433,177 +433,6 @@ var change_dependence_form = function(widget, field_widget) { _parentesis.show(); } -var factoryField = function(order, value) { - // Get field.widget_type - var widget_type = value['widget_type'], - group_order = value['group_order'], - errors = []; - - // If errors, prepare it - if (value.hasOwnProperty('errors')){ - $.each(value['errors'], function(i, error){ - errors.push({'error': error}); - }); - } - - // Preparing values for render widget type select box - var widget_types = [].concat(WIDGET_TYPES); - widget_types = $.each(widget_types, function(i, v){ - if (widget_type == v['key']) { - $.extend(v, {"selected": true}); - } else { - // TODO: ver como pasar por copia valores en javascript - delete v.selected; - } - }), - dependence_forms = [ - {'key': "4", 'value': "Sin parentesis" }, - {'key': "1", 'value': "( ID o/y ID ) o/y ( ID o/y ID )"}, - {'key': "2", 'value': "( ID o/y ID o/y ID ) o/y ID"}, - {'key': "3", 'value': "( ID o/y ID ) o/y ID o/y ID"} - ], - dependence_forms = $.each(dependence_forms, function(i, v){ - if (value.hasOwnProperty('dependence')){ - if (value['dependence'].form == v['key']) { - $.extend(v, {"selected": true}); - } - } - }), - with_dependence_form = group_order != 0 || ( group_order == 0 && order != 0 ); - - var field_widget = $( - Mustache.render(TEMPLATES['field'], { - "order": order, - "group_order": group_order, - "name": value['name'], - "dependence": value['dependence'], - "errors": errors, - "visible_errors": errors.length, - "WIDGET_TYPES": widget_types, - "dependence_forms": dependence_forms, - }) - ) - - // Get remove field button and bind remove field widget envent - var remove_button = field_widget.find('.WField_remove'); - bindFieldRemoveButton(remove_button); - - var add_option_button = field_widget.find('.WFieldOptions_add_button'); - bindFieldAddOptionButton(add_option_button); - - var add_option_none_button = field_widget.find('.WFieldOptionsNone_add_button'); - bindFieldAddOptionButton(add_option_none_button); - - // Bind change widget type event - var widget_types_select_box = field_widget.find('.WFieldWidgetType'); - bindFieldWidgetTypeSelectBox(widget_types_select_box); - - // Adding add_option button if is needed - buttons_container = field_widget.find('.WFieldAddOptionButton_container'); - if (widget_type && $.inArray(widget_type, WITH_OPTIONS) != -1) { - var add_option_button = $(Mustache.render(TEMPLATES['field_add_option_button'], {})); - buttons_container.append(add_option_button); - bindFieldAddOptionButton(add_option_button); - } - if (widget_type && $.inArray(widget_type, ["MultipleCheckBox", "ImageCheckBox"]) != -1) { - var add_option_none_button = $(Mustache.render(TEMPLATES['field_add_option_none_button'], {})); - buttons_container.append(add_option_none_button); - bindFieldAddOptionButton(add_option_none_button, {none: 'True'}); - } - - // Render options - if ($.inArray(widget_type, WITH_OPTIONS) != -1){ - if ($.inArray(widget_type, WITH_IMAGES) == -1){ - // Basic option - $.each(value['options'] || [], function(id, opt_value){ - options_container = field_widget.find('.WFieldOptions_container'); - factoryOption(id, opt_value, options_container, group_order, order); - }); - } else { - // Image option - $.each(value['options'] || [], function(id, opt_value){ - options_container = field_widget.find('.WFieldOptions_container'); - if (opt_value.hasOwnProperty('img_name')){ - factoryImageOptionThumbnail(id, opt_value, options_container, group_order, order); - } else { - factoryImageOptionUpload(id, opt_value, options_container, group_order, order); - } - }); - } - } else { - if (value['options'] && Object.keys(value['options']).length){ - $.each(value['options'] || [], function(id, opt_value){ - factoryOptionDefault(group_order, field_widget, id, opt_value['text']); - }); - } else { - factoryOptionDefault(group_order, field_widget, undefined, undefined); - } - } - - // Show dependences - var values = [], - dependence_container = field_widget.find(".dependence"), - widget; - - if (value['dependence']) - values = value['dependence'].values; - - for (var i=values.length; i < 7; i++) - values[i] = ''; - factoryDependences(dependence_container, values, order, group_order); - - var dependences = field_widget.find('[name*=".dependence.values"]'); - $.each(dependences, function(index, dependence){ - if ($(dependence).attr("value") != '') - $(dependence).addClass("ui-state-highlight"); - }); - - var parentesis_open = field_widget.find("._parentesis_open"); - if (parentesis_open.length == 8) { - $(parentesis_open[0]).addClass('1 2 3'); - $(parentesis_open[4]).addClass('1'); - } - - var parentesis_close = field_widget.find("._parentesis_close"); - if (parentesis_close.length == 8) { - $(parentesis_close[3]).addClass('1 3'); - $(parentesis_close[5]).addClass('2'); - $(parentesis_close[7]).addClass('1'); - } - - var widget_selected_dependence_form = field_widget.find('.dependence_form'); - $(widget_selected_dependence_form).on('change', function(event){ - change_dependence_form(this, null); - }); - - change_dependence_form(widget_selected_dependence_form[0], field_widget); - - if (!with_dependence_form) { - $(field_widget).find(".dependence_form").remove(); - $(field_widget).find(".dependence_form_label").remove(); - $(field_widget).find(".dependence").remove(); - $(field_widget).find(".dependence_label").remove(); - } - - var droppable = field_widget.find(".droppable"); - droppable.droppable({ - drop: function( event, ui ) { - var value = ui.draggable[0]['innerText']; - $(this) - .addClass("ui-state-highlight") - .attr("value", value); - } - }); - droppable.focusout(function(){ - if ($(this).attr('value') == ""){ - $(this).removeClass("ui-state-highlight"); - } - }); - - // Show the field widget - var container = value['container']; - container.append(field_widget); -}; /// METHODS FOR GROUPS /// diff --git a/webapp/webapp/static/js/factoryField.js b/webapp/webapp/static/js/factoryField.js new file mode 100644 index 0000000..37b3ee9 --- /dev/null +++ b/webapp/webapp/static/js/factoryField.js @@ -0,0 +1,175 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var factoryField; + + factoryField = function(order, value) { + var add_option_button, add_option_none_button, buttons_container, container, dependence_container, dependence_forms, dependences, droppable, errors, field_widget, group_order, i, img_name, img_src, parentesis_close, parentesis_open, remove_button, values, widget_selected_dependence_form, widget_type, widget_types, widget_types_select_box, with_dependence_form, _i; + widget_type = value['widget_type']; + group_order = value['group_order']; + errors = []; + if (value.hasOwnProperty('errors')) { + $.each(value['errors'], function(i, error) { + return errors.push({ + 'error': error + }); + }); + } + widget_types = [].concat(WIDGET_TYPES); + widget_types = $.each(widget_types, function(i, v) { + if (widget_type === v['key']) { + return $.extend(v, { + "selected": true + }); + } else { + return delete v.selected; + } + }); + dependence_forms = [ + { + 'key': "4", + 'value': "Sin parentesis" + }, { + 'key': "1", + 'value': "( ID o/y ID ) o/y ( ID o/y ID )" + }, { + 'key': "2", + 'value': "( ID o/y ID o/y ID ) o/y ID" + }, { + 'key': "3", + 'value': "( ID o/y ID ) o/y ID o/y ID" + } + ]; + dependence_forms = $.each(dependence_forms, function(i, v) { + if (value.hasOwnProperty('dependence')) { + if (value['dependence'].form === v['key']) { + return $.extend(v, { + "selected": true + }); + } + } + }); + with_dependence_form = group_order !== 0 || (group_order === 0 && order !== 0); + img_name = value['img']; + if (img_name != null) { + img_src = IMAGE_MEDIA_URL + "/" + img_name; + } else { + img_src = "/static/img/50x50.gif"; + } + field_widget = $(Mustache.render(TEMPLATES['field'], { + "order": order, + "group_order": group_order, + "name": value['name'], + "dependence": value['dependence'], + "errors": errors, + "visible_errors": errors.length, + "WIDGET_TYPES": widget_types, + "dependence_forms": dependence_forms, + "img_src": img_src, + "img_name": img_name + })); + remove_button = field_widget.find('.WField_remove'); + bindFieldRemoveButton(remove_button); + add_option_button = field_widget.find('.WFieldOptions_add_button'); + bindFieldAddOptionButton(add_option_button); + add_option_none_button = field_widget.find('.WFieldOptionsNone_add_button'); + bindFieldAddOptionButton(add_option_none_button); + widget_types_select_box = field_widget.find('.WFieldWidgetType'); + bindFieldWidgetTypeSelectBox(widget_types_select_box); + buttons_container = field_widget.find('.WFieldAddOptionButton_container'); + if (widget_type && $.inArray(widget_type, WITH_OPTIONS) !== -1) { + add_option_button = $(Mustache.render(TEMPLATES['field_add_option_button'], {})); + buttons_container.append(add_option_button); + bindFieldAddOptionButton(add_option_button); + } + if (widget_type && $.inArray(widget_type, ["MultipleCheckBox", "ImageCheckBox"]) !== -1) { + add_option_none_button = $(Mustache.render(TEMPLATES['field_add_option_none_button'], {})); + buttons_container.append(add_option_none_button); + bindFieldAddOptionButton(add_option_none_button, { + none: 'True' + }); + } + if ($.inArray(widget_type, WITH_OPTIONS) !== -1) { + if ($.inArray(widget_type, WITH_IMAGES) === -1) { + $.each(value['options'] || [], function(id, opt_value) { + var options_container; + options_container = field_widget.find('.WFieldOptions_container'); + return factoryOption(id, opt_value, options_container, group_order, order); + }); + } else { + $.each(value['options'] || [], function(id, opt_value) { + var options_container; + options_container = field_widget.find('.WFieldOptions_container'); + if (opt_value.hasOwnProperty('img_name')) { + return factoryImageOptionThumbnail(id, opt_value, options_container, group_order, order); + } else { + return factoryImageOptionUpload(id, opt_value, options_container, group_order, order); + } + }); + } + } else { + if (value['options'] && Object.keys(value['options']).length) { + $.each(value['options'] || [], function(id, opt_value) { + return factoryOptionDefault(group_order, field_widget, id, opt_value['text']); + }); + } else { + factoryOptionDefault(group_order, field_widget, void 0, void 0); + } + } + values = []; + dependence_container = field_widget.find(".dependence"); + if (value['dependence']) { + values = value['dependence'].values; + } + for (i = _i = 0; _i <= 6; i = ++_i) { + if (i >= values.length) { + values[i] = ''; + } + } + factoryDependences(dependence_container, values, order, group_order); + dependences = field_widget.find('[name*=".dependence.values"]'); + $.each(dependences, function(index, dependence) { + if ($(dependence).attr("value") !== '') { + return $(dependence).addClass("ui-state-highlight"); + } + }); + parentesis_open = field_widget.find("._parentesis_open"); + if (parentesis_open.length === 8) { + $(parentesis_open[0]).addClass('1 2 3'); + $(parentesis_open[4]).addClass('1'); + } + parentesis_close = field_widget.find("._parentesis_close"); + if (parentesis_close.length === 8) { + $(parentesis_close[3]).addClass('1 3'); + $(parentesis_close[5]).addClass('2'); + $(parentesis_close[7]).addClass('1'); + } + widget_selected_dependence_form = field_widget.find('.dependence_form'); + $(widget_selected_dependence_form).on('change', function(event) { + return change_dependence_form(this, null); + }); + change_dependence_form(widget_selected_dependence_form[0], field_widget); + if (!with_dependence_form) { + $(field_widget).find(".dependence_form").remove(); + $(field_widget).find(".dependence_form_label").remove(); + $(field_widget).find(".dependence").remove(); + $(field_widget).find(".dependence_label").remove(); + } + droppable = field_widget.find(".droppable"); + droppable.droppable({ + drop: function(event, ui) { + value = ui.draggable[0]['innerText']; + return $(this).addClass("ui-state-highlight").attr("value", value); + } + }); + droppable.focusout(function() { + if ($(this).attr('value') === "") { + return $(this).removeClass("ui-state-highlight"); + } + }); + container = value['container']; + return container.append(field_widget); + }; + + window.factoryField = factoryField; + +}).call(this); |