Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRogelio Mita <rogeliomita@activitycentral.com>2013-03-21 00:23:01 (GMT)
committer Rogelio Mita <rogeliomita@activitycentral.com>2013-03-25 21:51:48 (GMT)
commit09ae6b31b5bc865046911b5c8db1ae64ad96958e (patch)
tree8644ae08595f3de98d45bb7060606eb09cc60715
parentd92a0318216818fd65286ccaefdf581327221c83 (diff)
Keeping img options temporarily while the structure form is filling up
-rw-r--r--.gitignore3
-rw-r--r--webapp/README3
-rw-r--r--webapp/polls/models.py84
-rw-r--r--webapp/polls/templates/mustache/option_image_thumbnail.html7
-rw-r--r--webapp/polls/templates/mustache/option_image_upload.html (renamed from webapp/polls/templates/mustache/option_image.html)4
-rw-r--r--webapp/polls/templates/poll-structure-form.html2
-rw-r--r--webapp/polls/templates/tags/structure.html7
-rw-r--r--webapp/polls/templatetags/poll_tags.py2
-rw-r--r--webapp/polls/tests.py37
-rw-r--r--webapp/polls/views.py12
-rw-r--r--webapp/requirements3
-rw-r--r--webapp/utils/data_structure.py7
-rw-r--r--webapp/utils/test.py32
-rw-r--r--webapp/webapp/media/image_options/empty0
-rw-r--r--webapp/webapp/settings.py2
-rw-r--r--webapp/webapp/static/js/dynamic_structure.js48
16 files changed, 231 insertions, 22 deletions
diff --git a/.gitignore b/.gitignore
index 8b5d621..98dc8b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.pyc
*.pyo
*.bak
-webapp/webapp/media/output/*.json
+webapp/webapp/media/image_options/*
+!webapp/webapp/media/image_options/empty
webapp/webapp/env_settings.py
database.db \ No newline at end of file
diff --git a/webapp/README b/webapp/README
index 2a75dd5..4121de7 100644
--- a/webapp/README
+++ b/webapp/README
@@ -1,5 +1,8 @@
=== dev env ===
+system requirements:
+- libjpeg
+
python version: 2.7.3
1 - create virtualenv (example: mkvirtualenv --no-site-packages polls)
diff --git a/webapp/polls/models.py b/webapp/polls/models.py
index 609b38e..8b090c3 100644
--- a/webapp/polls/models.py
+++ b/webapp/polls/models.py
@@ -1,10 +1,16 @@
# -*- encoding: utf-8 -*-
import time
import json
+import os
from exceptions import *
from bson import ObjectId, DBRef
+from django.conf import settings
+from django.forms.fields import ImageField
+from django.core.exceptions import ValidationError as DjangoValidationError
+from django.core.files.uploadedfile import InMemoryUploadedFile
+
from utils.mongo_connection import get_db
@@ -127,21 +133,38 @@ class AbastractObject(AbstracErrorObject):
class Option(AbastractObject):
- def __init__(self, id, text):
+ def __init__(self, id, text=None, img=None, img_name=None):
super(Option, self).__init__()
self.id = id
self.text = text
+ self.img = img
+ self.img_name = img_name
@staticmethod
def from_dict(data):
option = Option(
id=data.get('id', None),
text=data.get('text', None),
+ img=data.get('img', None),
+ img_name=data.get('img_name', None)
)
return option
+ def validate(self):
+ self.errors = []
+
+ if self.img and isinstance(self.img, InMemoryUploadedFile):
+ try:
+ ImageField().to_python(self.img)
+ except DjangoValidationError, e:
+ img_name = self.img.name
+ self.errors.append('%s: %s' % (img_name, e.messages[0]))
+
+ if len(self.errors):
+ raise Option.ValidationError(str(self.errors))
+
class Field(AbastractObject):
@@ -161,6 +184,16 @@ class Field(AbastractObject):
"Respuesta con lista de opciones: necesita "
"al menos una opción."
),
+ 'ImageCheckBox': (
+ lambda f: f.options and len(f.options) > 0,
+ "Respuesta con checks (multiple selección): necesita "
+ "al menos una imagen de opción."
+ ),
+ 'ImageRadioButton': (
+ lambda f: f.options and len(f.options) > 1,
+ "Respuesta con radios (Solo una selección): necesita "
+ "al menos dos imagenes de opciones."
+ ),
'TextInput': (lambda f: True, ""),
}
@@ -169,7 +202,7 @@ class Field(AbastractObject):
self.name = name
self.key = key
- self.options = None
+ self.options = []
self.dependence = None
if widget_type and widget_type not in dict(WIDGET_TYPES).keys():
@@ -205,7 +238,11 @@ class Field(AbastractObject):
opt = Option.from_dict(opt_data)
self.options = self.options if self.options is not None else []
- if opt_data['text'] and opt not in self.options:
+ if opt_data.get('text', None) and opt not in self.options:
+ self.options.append(opt)
+
+ img = opt_data.get('img', None) or opt_data.get('img_name', None)
+ if img and opt not in self.options:
self.options.append(opt)
def add_dependence(self, dependence):
@@ -217,6 +254,13 @@ class Field(AbastractObject):
if not rule(self):
self.errors.append(msg)
+ # Validate option of current field
+ for opt in self.options:
+ try:
+ opt.validate()
+ except Option.ValidationError:
+ self.errors += opt.errors
+
options_id = [opt.id for opt in options]
# TODO: Test
@@ -389,9 +433,15 @@ class Structure(AbastractObject):
options = field_obj.options if field_obj.options else []
for option in options:
_dic_field = _dict[group_order]['fields'][field_order]
+
+ # TODO: Check when opt_data = {}
+ opt_data = {'text': option.text}
+ if option.img_name:
+ opt_data = {'img_name': option.img_name}
_dic_field['options'].update(
- {'%s' % option.id: {'text': option.text}}
+ {'%s' % option.id: opt_data}
)
+
return {'groups': _dict}
def save(self):
@@ -430,3 +480,29 @@ class Structure(AbastractObject):
structure.poll = Poll.get(poll_id)
return structure
+
+ def save_image_options(self, tmp=False):
+ fields = reduce(
+ lambda x, y: x + y, [g.fields for g in self.groups], [])
+ options = reduce(
+ lambda x, y: x + y, [f.options or [] for f in fields], [])
+
+ valid_img_options = filter(lambda opt: not len(opt.errors), options)
+
+ if tmp and self.id:
+ path = settings.IMAGE_OPTIONS_ROOT + '/%s/tmp' % self.id
+ try:
+ os.makedirs(path)
+ except OSError:
+ pass
+
+ # Storing image
+ for img_opt in valid_img_options:
+ if isinstance(img_opt.img, InMemoryUploadedFile):
+ fileExtension = os.path.splitext(img_opt.img.name)[1]
+ opt_img_name = '%s%s' % (img_opt.id, fileExtension)
+ with open(path + '/%s' % opt_img_name, 'wb+') as dst:
+ for chunk in img_opt.img.chunks():
+ dst.write(chunk)
+ dst.close()
+ img_opt.img_name = opt_img_name
diff --git a/webapp/polls/templates/mustache/option_image_thumbnail.html b/webapp/polls/templates/mustache/option_image_thumbnail.html
new file mode 100644
index 0000000..f0ff65a
--- /dev/null
+++ b/webapp/polls/templates/mustache/option_image_thumbnail.html
@@ -0,0 +1,7 @@
+<!-- Template: Field Image option thumbnail -->
+<script type="text/x-mustache-template" name="field_option_image_thumbnail">
+ <input type="hidden" name="groups.[[ group_order ]].fields.[[ field_order ]].options.[[ id ]].img_name" value="[[ img_name ]]" />
+ <div class="thumbnail" style="width: 200px; height: 150px;">
+ <img src="/media/image_options/[[ STRUCTURE_ID ]]/tmp/[[ img_name ]]" style="width: 200px; height: 150px;" />
+ </div>
+</script> \ No newline at end of file
diff --git a/webapp/polls/templates/mustache/option_image.html b/webapp/polls/templates/mustache/option_image_upload.html
index b8cb19b..e81f797 100644
--- a/webapp/polls/templates/mustache/option_image.html
+++ b/webapp/polls/templates/mustache/option_image_upload.html
@@ -1,5 +1,5 @@
-<!-- Template: Field -->
-<script type="text/x-mustache-template" name="field_option_image">
+<!-- Template: Field Image option upload -->
+<script type="text/x-mustache-template" name="field_option_image_upload">
<div class="fileupload fileupload-new" data-provides="fileupload">
<div class="fileupload-new thumbnail" style="width: 200px; height: 150px;">
diff --git a/webapp/polls/templates/poll-structure-form.html b/webapp/polls/templates/poll-structure-form.html
index 9303558..7724946 100644
--- a/webapp/polls/templates/poll-structure-form.html
+++ b/webapp/polls/templates/poll-structure-form.html
@@ -34,7 +34,7 @@
</div>
{% endif %}
- <form class="form-inline" method="post" action="">
+ <form class="form-inline" method="post" action="" enctype="multipart/form-data">
{% if structure.id %}
<input type="hidden" name="id" value="{{ structure.id }}" />
diff --git a/webapp/polls/templates/tags/structure.html b/webapp/polls/templates/tags/structure.html
index 427cd81..c02abc0 100644
--- a/webapp/polls/templates/tags/structure.html
+++ b/webapp/polls/templates/tags/structure.html
@@ -17,7 +17,9 @@
{% include "mustache/option_default.html" %}
-{% include "mustache/option_image.html" %}
+{% include "mustache/option_image_upload.html" %}
+
+{% include "mustache/option_image_thumbnail.html" %}
<!-- Global variables for dynamic_structure.js -->
<script type="text/javascript">
@@ -26,7 +28,8 @@
WIDGET_TYPES = {{ WIDGET_TYPES }},
WITH_OPTIONS = {{ WITH_OPTIONS }},
WITH_IMAGES = {{ WITH_IMAGES }},
- OFFSET_OPTION_ID = {{ OFFSET_OPTION_ID }};
+ OFFSET_OPTION_ID = {{ OFFSET_OPTION_ID }},
+ STRUCTURE_ID = "{{ STRUCTURE_ID }}";
</script>
diff --git a/webapp/polls/templatetags/poll_tags.py b/webapp/polls/templatetags/poll_tags.py
index bde1962..f903155 100644
--- a/webapp/polls/templatetags/poll_tags.py
+++ b/webapp/polls/templatetags/poll_tags.py
@@ -12,6 +12,7 @@ register = template.Library()
@register.simple_tag(takes_context=True)
def render_structure(context, structure):
+ STRUCTURE_ID = structure.id
structure = structure.to_dict(with_errors=True)
groups = SafeUnicode(json.dumps(structure.get("groups", {})))
widget_types = SafeUnicode(json.dumps(
@@ -25,6 +26,7 @@ def render_structure(context, structure):
"WITH_OPTIONS": SafeUnicode(json.dumps(WITH_OPTIONS)),
"WITH_IMAGES": SafeUnicode(json.dumps(WITH_IMAGES)),
"OFFSET_OPTION_ID": SafeUnicode(json.dumps(Field.get_offset_id())),
+ "STRUCTURE_ID": str(STRUCTURE_ID) if STRUCTURE_ID else '',
"groups": groups
}
)
diff --git a/webapp/polls/tests.py b/webapp/polls/tests.py
index 51247ca..e472e25 100644
--- a/webapp/polls/tests.py
+++ b/webapp/polls/tests.py
@@ -7,7 +7,7 @@ from polls.models import (
Poll, Field, Group, Structure, Option, WIDGET_TYPES)
from polls.forms import PollAddForm
-from utils.test import MongoTestCase
+from utils.test import MongoTestCase, mock_in_memory_image, mock_text_file
class PollTests(MongoTestCase):
@@ -153,6 +153,22 @@ class FieldsTests(TestCase):
self.assertEqual(1, len(field.options))
+ def test_add_image_options(self):
+ data = {
+ 'widget_type': 'ImageCheckBox',
+ 'name': 'field_0',
+ }
+ field = Field.from_dict(data)
+
+ img_file = mock_in_memory_image()
+
+ options_data = {
+ '1': {'img': img_file},
+ }
+ field.add_options(options_data)
+
+ self.assertEqual(1, len(field.options))
+
def test_validate(self):
self.field.widget_type = "MultipleCheckBox"
@@ -186,6 +202,25 @@ class OptionTests(TestCase):
self.assertEqual('1', option.id)
self.assertEqual('si', option.text)
+ def test_image_option(self):
+
+ img_name = 'image.jpg'
+ img_file = mock_in_memory_image(img_name)
+
+ data = {'id': '1', 'img': img_file}
+ option = Option.from_dict(data)
+
+ self.assertEqual('1', option.id)
+ self.assertEqual(img_file, option.img)
+
+ def test_validate(self):
+ invalid_img_file = mock_text_file()
+
+ data = {'id': '1', 'img': invalid_img_file}
+ option = Option.from_dict(data)
+
+ self.assertRaises(Option.ValidationError, option.validate)
+
class GroupTests(TestCase):
diff --git a/webapp/polls/views.py b/webapp/polls/views.py
index 18b2eeb..2a26390 100644
--- a/webapp/polls/views.py
+++ b/webapp/polls/views.py
@@ -9,9 +9,11 @@ from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.core.urlresolvers import reverse
from django.views.generic.edit import FormView
from django.contrib import messages
+from django.core.files.uploadedfile import InMemoryUploadedFile
from utils.forms import BadFormValidation
from utils.mongo_connection import get_db
+from utils.data_structure import dict_merge
from polls.models import WIDGET_TYPES, Structure, Poll
from polls.forms import PollAddForm
@@ -36,7 +38,8 @@ def clean_data(value):
value = [clean_data(value_) for value_ in value]
return value
- value = value.strip(' ')
+ if not isinstance(value, InMemoryUploadedFile):
+ value = value.strip(' ')
# TODO: Organizar esto con Flavio.
#if value == '':
@@ -131,8 +134,12 @@ class StructureFormView(TemplateView):
def post(self, request, *args, **kwargs):
context = self.get_context_data()
+ data = {}
- data = DotExpandedDict(dict(request.POST.lists()))
+ data_post = DotExpandedDict(dict(request.POST.lists()))
+ data_files = DotExpandedDict(dict(request.FILES.lists()))
+
+ data = dict_merge(data_post, data_files)
for key, value in data.items():
data[key] = clean_data(value)
@@ -156,6 +163,7 @@ class StructureFormView(TemplateView):
reverse('polls:success', kwargs={"poll_id": str(self.poll.id)})
)
else:
+ structure.save_image_options(tmp=True)
context.update({'errors': structure.errors})
context.update({'structure': structure})
diff --git a/webapp/requirements b/webapp/requirements
index 8bbc37b..f59a2e8 100644
--- a/webapp/requirements
+++ b/webapp/requirements
@@ -5,4 +5,5 @@ nose==1.2.1
django-nose==1.1
fabric==1.5.3
pymongo==2.4.2
-django-jasmine==0.3.2 \ No newline at end of file
+django-jasmine==0.3.2
+PIL==1.1.7 \ No newline at end of file
diff --git a/webapp/utils/data_structure.py b/webapp/utils/data_structure.py
new file mode 100644
index 0000000..730ba7e
--- /dev/null
+++ b/webapp/utils/data_structure.py
@@ -0,0 +1,7 @@
+def dict_merge(d1, d2):
+ for k1, v1 in d1.iteritems():
+ if not k1 in d2:
+ d2[k1] = v1
+ elif isinstance(v1, dict):
+ dict_merge(v1, d2[k1])
+ return d2
diff --git a/webapp/utils/test.py b/webapp/utils/test.py
index c481dd7..d9cbfde 100644
--- a/webapp/utils/test.py
+++ b/webapp/utils/test.py
@@ -1,5 +1,9 @@
+import StringIO
+import Image
+
from django.conf import settings
from django.test import TestCase
+from django.core.files.uploadedfile import InMemoryUploadedFile
from utils.mongo_connection import connect
@@ -23,3 +27,31 @@ class MongoTestCase(TestCase):
self.db.drop_collection(collection_name)
super(MongoTestCase, self)._post_teardown()
+
+
+def mock_text_file():
+ io = StringIO.StringIO()
+ io.write('foo')
+ text_file = InMemoryUploadedFile(io, None, 'foo.txt', 'text', io.len, None)
+ text_file.seek(0)
+ return text_file
+
+
+def mock_image_file(format="jpeg"):
+ io = StringIO.StringIO()
+ size = (200, 200)
+ color = (255, 0, 0, 0)
+ image = Image.new("RGBA", size, color)
+ image.save(io, format=format.upper())
+ io.seek(0)
+ return io
+
+
+def mock_in_memory_image(name='foo.jpg', format="jpeg"):
+ io = mock_image_file(format)
+ content_type = "Image/%s" % format.lower()
+ image_in_memory = InMemoryUploadedFile(
+ io, None, name, content_type, io.len, None
+ )
+ image_in_memory.seek(0)
+ return image_in_memory
diff --git a/webapp/webapp/media/image_options/empty b/webapp/webapp/media/image_options/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webapp/webapp/media/image_options/empty
diff --git a/webapp/webapp/settings.py b/webapp/webapp/settings.py
index 5d3748c..fdae3de 100644
--- a/webapp/webapp/settings.py
+++ b/webapp/webapp/settings.py
@@ -194,6 +194,8 @@ LOGIN_REDIRECT_URL = '/'
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/../js_tests/'
+IMAGE_OPTIONS_ROOT = MEDIA_ROOT + 'image_options'
+
try:
from env_settings import *
except ImportError:
diff --git a/webapp/webapp/static/js/dynamic_structure.js b/webapp/webapp/static/js/dynamic_structure.js
index 9bc87af..9ecf781 100644
--- a/webapp/webapp/static/js/dynamic_structure.js
+++ b/webapp/webapp/static/js/dynamic_structure.js
@@ -27,10 +27,10 @@ var factoryOptionDefault = function(group_order, field_widget, id, default_value
};
-var factoryImageOption = function(id, value, container, group_order, field_order) {
+var factoryImageOptionUpload = function(id, value, container, group_order, field_order) {
var option = $(
- Mustache.render(TEMPLATES['field_option_image'], {
+ Mustache.render(TEMPLATES['field_option_image_upload'], {
"id": id,
"group_order": group_order,
"field_order": field_order
@@ -44,6 +44,25 @@ var factoryImageOption = function(id, value, container, group_order, field_order
};
+var factoryImageOptionThumbnail = function(id, value, container, group_order, field_order) {
+
+ var option = $(
+ Mustache.render(TEMPLATES['field_option_image_thumbnail'], {
+ "id": id,
+ "STRUCTURE_ID": STRUCTURE_ID,
+ "img_name": value['img_name'],
+ "group_order": group_order,
+ "field_order": field_order
+ })
+ );
+
+ // Append new option
+ row_fluid = $('<div class="row-fluid"></div>');
+ row_fluid.append(option);
+ container.append(row_fluid);
+};
+
+
var factoryOption = function(id, value, container, group_order, field_order) {
var option = $(
@@ -144,7 +163,7 @@ var bindFieldAddOptionButton = function(add_option_button) {
if ($.inArray(current_widget_type, WITH_IMAGES) == -1){
factoryOption(OFFSET_OPTION_ID, empty_option_widget, options_container, group_order, field_order );
} else {
- factoryImageOption(OFFSET_OPTION_ID, empty_option_widget, options_container, group_order, field_order );
+ factoryImageOptionUpload(OFFSET_OPTION_ID, empty_option_widget, options_container, group_order, field_order );
}
});
}
@@ -241,12 +260,25 @@ var factoryField = function(order, value) {
bindFieldAddOptionButton(add_option_button);
}
- // Show options
+ // Render options
if ($.inArray(widget_type, WITH_OPTIONS) != -1){
- $.each(value['options'] || [], function(id, opt_value){
- options_container = field_widget.find('.WFieldOptions_container');
- factoryOption(id, opt_value, options_container, group_order, order);
- });
+ 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){