Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCode 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)
commit1101373de9297176d11c7471a0cf59e6c6a7eacb (patch)
treeb4ecf5eccce50497cf903ade2f38bb8472d4fe2c
parent631ccd38bb09a152eaa61892a63f78381da4f33f (diff)
parent7cb74551d3208130961219d0dfcd4e44eb0a0a26 (diff)
Merge branch 'DEV'v4.11
-rw-r--r--.gitignore4
-rw-r--r--CeibalEncuesta/gtk2/CeibalEncuesta.activity/activity/activity.info2
-rw-r--r--CeibalEncuesta/gtk2/CeibalEncuesta/CeibalEncuesta.py117
-rw-r--r--CeibalEncuesta/gtk2/CeibalEncuesta/Widgets.py195
-rw-r--r--CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.10.xobin0 -> 285751 bytes
-rw-r--r--CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xobin0 -> 285707 bytes
-rw-r--r--CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zipbin0 -> 218217 bytes
-rw-r--r--CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zipbin0 -> 218045 bytes
-rw-r--r--CeibalEncuesta/gtk2/setup.py2
-rw-r--r--webapp/coffee/factoryField.coffee190
-rw-r--r--webapp/js_tests/files.json1
-rw-r--r--webapp/js_tests/fixtures/container.html1
-rw-r--r--webapp/js_tests/spec/DynamicStructureSpec.js281
-rw-r--r--webapp/js_tests/spec/FieldsSpec.js280
-rw-r--r--webapp/polls/models.py154
-rw-r--r--webapp/polls/templates/mustache/field.html14
-rw-r--r--webapp/polls/templates/tags/structure.html3
-rw-r--r--webapp/polls/templatetags/poll_tags.py4
-rw-r--r--webapp/polls/tests/field_tests.py25
-rw-r--r--webapp/polls/tests/result_tests.py221
-rw-r--r--webapp/polls/tests/structure_tests.py48
-rw-r--r--webapp/polls/views.py2
-rw-r--r--webapp/webapp/media/images/empty0
-rw-r--r--webapp/webapp/settings.py4
-rw-r--r--webapp/webapp/static/img/50x50.gifbin0 -> 184 bytes
-rw-r--r--webapp/webapp/static/js/dynamic_structure.js171
-rw-r--r--webapp/webapp/static/js/factoryField.js175
27 files changed, 1255 insertions, 639 deletions
diff --git a/.gitignore b/.gitignore
index 43943a0..1353354 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
new file mode 100644
index 0000000..d2592b7
--- /dev/null
+++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.10.xo
Binary files differ
diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xo b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xo
new file mode 100644
index 0000000..d9ce458
--- /dev/null
+++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta-4.11.xo
Binary files differ
diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zip b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zip
new file mode 100644
index 0000000..e82901a
--- /dev/null
+++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.10_Installer.zip
Binary files differ
diff --git a/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zip b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zip
new file mode 100644
index 0000000..19ab49f
--- /dev/null
+++ b/CeibalEncuesta/gtk2/installers/CeibalEncuesta_Gtk2_4.11_Installer.zip
Binary files differ
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
new file mode 100644
index 0000000..651f890
--- /dev/null
+++ b/webapp/webapp/static/img/50x50.gif
Binary files differ
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);