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-09-06 17:12:53 (GMT)
committer Code Raguet <ignacio.code@gmail.com>2013-09-06 17:12:53 (GMT)
commitffe7384641b9b52ff5ede5836827610f1cc23c68 (patch)
tree99496d43be49ac26a29fb41307f152e278f434bf
parent32c10ae4b8e62ed6957ea349307a78fee05e86cd (diff)
parentc9236813d1e51d86fcc9955800568bae84e7d73e (diff)
issue 4234: Show the date and the user who uploaded the results
-rw-r--r--webapp/polls/models.py122
-rw-r--r--webapp/polls/tests/poll_result_file_tests.py176
-rw-r--r--webapp/polls/tests/poll_tests.py128
-rw-r--r--webapp/polls/views.py114
-rw-r--r--webapp/pollster/templates/pollster-assigned-polls.html2
-rw-r--r--webapp/sociologist/templates/poll-result-list.html134
-rw-r--r--webapp/sociologist/views.py141
-rw-r--r--webapp/utils/clock.py11
-rw-r--r--webapp/utils/tests/__init__.py0
-rw-r--r--webapp/utils/tests/clock_tests.py20
-rw-r--r--webapp/webapp/features/active_resultados_button.feature11
-rw-r--r--webapp/webapp/features/active_resultados_button.py11
-rw-r--r--webapp/webapp/features/download_csv_button.py22
-rw-r--r--webapp/webapp/features/download_csv_button_exists.feature11
-rw-r--r--webapp/webapp/features/download_poll_result_file.feature12
-rw-r--r--webapp/webapp/features/fixtures/2schools.poll_result33
-rw-r--r--webapp/webapp/features/fixtures/simple.poll_result21
-rw-r--r--webapp/webapp/features/fixtures/simple2.poll_result33
-rw-r--r--webapp/webapp/features/index.py13
-rw-r--r--webapp/webapp/features/poll_result_details.feature20
-rw-r--r--webapp/webapp/features/poll_result_details.py74
-rw-r--r--webapp/webapp/features/resultados_need_poll_name.feature7
-rw-r--r--webapp/webapp/features/steps.py116
-rw-r--r--webapp/webapp/features/terrain.py33
-rw-r--r--webapp/webapp/features/upload_poll_result.feature14
-rw-r--r--webapp/webapp/features/upload_poll_result.py43
-rw-r--r--webapp/webapp/settings.py8
-rw-r--r--webapp/webapp/test_settings.py16
28 files changed, 1033 insertions, 313 deletions
diff --git a/webapp/polls/models.py b/webapp/polls/models.py
index 0020eff..129d47e 100644
--- a/webapp/polls/models.py
+++ b/webapp/polls/models.py
@@ -8,9 +8,11 @@ import Image
import base64
import copy
import warnings
+import glob
+import errno
from distutils.dir_util import copy_tree
-from exceptions import *
+from exceptions import ValidationError, UniqueNameError
from bson import ObjectId, DBRef
from datetime import datetime
@@ -72,7 +74,7 @@ class Poll(Document, AbstracErrorObject):
def __init__(self, data={}, *args, **kwargs):
super(Poll, self).__init__(data=data, *args, **kwargs)
-
+ self.results_path = settings.RESULT_BCK_ROOT
self.name = data.get('name', None)
self.status = data.get('status', Poll.OPEN)
self.pollsters = data.get('pollsters', [])
@@ -379,12 +381,9 @@ class Poll(Document, AbstracErrorObject):
get_db().polls.update({"_id": self.id}, {"$set": {"result": dbref}})
def get_result(self):
- object_data = get_db().poll_results.find_one(
- {'poll_id': str(self.id)})
- if object_data:
- return PollResult(object_data)
-
- return None
+ poll_result_files = self.get_result_files()
+ datas = map(lambda prf: prf.get_data(), poll_result_files)
+ return PollResult(data=datas)
def get_questions(self):
questions = []
@@ -442,6 +441,48 @@ class Poll(Document, AbstracErrorObject):
return ";".join(header)
+ def get_resutls_path(self):
+ results_path = self._results_path
+ poll_id = self.id
+ if poll_id is None:
+ raise Exception("Please, save poll first!!")
+ return os.path.join(results_path, str(poll_id))
+
+ def set_results_path(self, path):
+ self._results_path = path
+
+ results_path = property(get_resutls_path, set_results_path)
+
+ def get_result_files(self, as_instance_of=None):
+ result_file = PollResultFile if not as_instance_of else as_instance_of
+ results_path = self.results_path
+
+ result_files = []
+ for file_path in glob.glob(results_path + '/*'):
+ result_files.append(result_file(file_path))
+ return result_files
+
+ def add_result_files(self, path_and_name):
+ results_path = self.results_path
+ cp = lambda src, dst: shutil.copyfile(src, dst)
+ for path, name in path_and_name:
+ src = path
+ file_name = name
+ dst = os.path.join(results_path, file_name)
+ try:
+ cp(src, dst)
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ os.mkdir(results_path)
+ cp(src, dst)
+ else:
+ raise e
+
+ def has_result(self, as_instance_of=None):
+ result_file = PollResultFile if not as_instance_of else as_instance_of
+ results = self.get_result_files(as_instance_of=result_file)
+ return len(results) > 0
+
class AbstractObject(AbstracErrorObject):
@@ -848,7 +889,6 @@ class Field(AbstractObject, ComponentStructure):
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:
@@ -1292,7 +1332,7 @@ class PollResult(AbstractObject):
_merge = []
_data = {}
if isinstance(data, list):
- _data = copy.deepcopy(data[0])
+ _data = copy.deepcopy(data[0]) if len(data) else {}
if _data.get('result', None):
del _data['result']
# Merge results
@@ -1471,3 +1511,65 @@ class PollResult(AbstractObject):
for result_id in sorted_keys:
yield NodePollResult(str(result_id), answers=data[str(result_id)])
+
+
+class PollResultFile(object):
+
+ def __init__(self, file_path):
+ self.file_path = file_path
+ self.name = os.path.basename(self.file_path)
+ with open(self.file_path) as f:
+ f.seek(0)
+ self.json_str = f.read()
+ self.data = json.loads(self.json_str, 'utf-8')
+
+ def get_numero_escuela(self):
+ results = self.data['result']
+
+ all_numero_escuela = []
+ for i in results:
+ numero_escuela = results[i]['polled']['NUM_ESC']
+ all_numero_escuela.append(numero_escuela)
+ return set(all_numero_escuela)
+
+ def get_pollster_username(self):
+ return self.data['pollster_username']
+
+ def get_polled_count(self):
+ polled_count = len(self.data['result'].keys())
+ return polled_count
+
+ def get_file_name(self):
+ return self.name
+
+ def get_upload_timestamp(self):
+ time_string = self.data.get('upload_timestamp', None)
+ return time_string
+
+ def set_upload_timestamp(self, time_string):
+ data = self.data
+ data['upload_timestamp'] = time_string
+ self._save()
+
+ def _save(self):
+ data = self.data
+ with open(self.file_path, 'w') as file_:
+ j = json.dumps(data, sort_keys=True, indent=4,
+ separators=(',', ': '))
+ file_.seek(0)
+ file_.write(j)
+
+ def save(self):
+ name = self.get_file_name()
+ file_path = self.file_path
+ poll_id = self.data['poll_id']
+ poll = Poll.get(poll_id)
+ poll.add_result_files([(file_path, name)])
+
+ def get_data(self):
+ return self.data
+
+ def get_absolute_url(self):
+ poll_id = str(self.data['poll_id'])
+ return os.path.join(
+ settings.RESULT_BCK_URL, poll_id, self.get_file_name())
diff --git a/webapp/polls/tests/poll_result_file_tests.py b/webapp/polls/tests/poll_result_file_tests.py
new file mode 100644
index 0000000..7da0117
--- /dev/null
+++ b/webapp/polls/tests/poll_result_file_tests.py
@@ -0,0 +1,176 @@
+import json
+import tempfile
+import os
+
+from polls.models import PollResultFile, Poll
+from utils.test import MongoTestCase
+from django.conf import settings
+
+
+def json_construc(data):
+ return json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
+
+
+class PollResultFileTest(MongoTestCase):
+
+ def setUp(self):
+ data = {}
+ data['result'] = {}
+ data['result']['0'] = {}
+ data['result']['0']['polled'] = {}
+ self.data = data
+
+ def test_it_should_respond_to_pollster_username(self):
+ data = self.data
+ pollster_username = "encuestador1"
+ data['pollster_username'] = pollster_username
+ file_path = self.make_temp_file(data)
+ result = PollResultFile(file_path)
+ self.assertEqual(pollster_username, result.get_pollster_username())
+
+ def test_it_should_respond_to_polled_count(self):
+ data = {}
+ data['result'] = {}
+
+ for polled_count in range(1, 3):
+ data['result'][str(polled_count - 1)] = {}
+ file_path = self.make_temp_file(data)
+ result = PollResultFile(file_path)
+ self.assertEqual(polled_count, result.get_polled_count())
+
+ def test_it_should_respond_to_poll_result_filename(self):
+ file_path = self.make_temp_file({})
+ poll_result = PollResultFile(file_path)
+
+ filename = os.path.basename(file_path)
+ self.assertEqual(filename, poll_result.get_file_name())
+
+ def test_it_should_respond_to_upload_timestamp(self):
+ time_string = "31/12/2000 23:59hs"
+
+ data = self.data
+ data['upload_timestamp'] = time_string
+ file_path = self.make_temp_file(data)
+ result = PollResultFile(file_path)
+ self.assertEqual(time_string, result.get_upload_timestamp())
+
+ def test_it_should_return_None_when_there_is_no_timestamp(self):
+ data = self.data
+ file_path = self.make_temp_file(data)
+ result = PollResultFile(file_path)
+ self.assertIsNone(result.get_upload_timestamp())
+
+ def test_it_should_set_the_upload_timestamp(self):
+ data = self.data
+ file_path = self.make_temp_file(data)
+ result = PollResultFile(file_path)
+ expected_time_string = "31/12/1999 23:59hs"
+
+ result.set_upload_timestamp(expected_time_string)
+
+ time_string = result.get_upload_timestamp()
+ self.assertEqual(expected_time_string, time_string)
+
+ def test_the_upload_timestamp_should_be_persistent(self):
+ expected_time_string = "31/12/1999 23:59hs"
+ data = self.data
+ file_path = self.make_temp_file(data)
+
+ result = PollResultFile(file_path)
+ result.set_upload_timestamp(expected_time_string)
+ del(result)
+
+ new_result_same_file = PollResultFile(file_path)
+ time_string = new_result_same_file.get_upload_timestamp()
+ self.assertEqual(expected_time_string, time_string)
+
+ def test_it_should_be_available_for_his_related_poll_when_it_saves(self):
+ poll = Poll({'name': 'poll'})
+ poll_id = str(poll.save())
+ poll = Poll.get(poll_id)
+ self.assertEqual(0, len(poll.get_result_files()))
+
+ data = self.data
+ data['poll_id'] = poll_id
+
+ file_path = self.make_temp_file(data)
+ result_file = PollResultFile(file_path)
+ result_file.save()
+
+ self.assertEqual(1, len(poll.get_result_files()))
+
+ def make_temp_file(self, data):
+ json_str = json_construc(data)
+ file_ = tempfile.NamedTemporaryFile(suffix='.poll_result',
+ delete=False)
+ file_.write(json_str)
+ file_.close()
+ return file_.name
+
+ def test_it_should_saves_with_a_chosen_name(self):
+ poll = Poll({'name': 'poll'})
+ poll_id = str(poll.save())
+ poll = Poll.get(poll_id)
+ self.assertEqual(0, len(poll.get_result_files()))
+
+ data = self.data
+ data['poll_id'] = poll_id
+
+ file_path = self.make_temp_file(data)
+ result_file = PollResultFile(file_path)
+ result_file.name = chosen_name = 'super_name.poll_result'
+ result_file.save()
+
+ result_name = poll.get_result_files()[0].get_file_name()
+ self.assertEqual(chosen_name, result_name)
+
+ def test_it_should_respond_with_data_structure(self):
+ data = self.data
+ file_path = self.make_temp_file(data)
+ poll_result_file = PollResultFile(file_path)
+
+ expected_data = data
+ self.assertEqual(expected_data, poll_result_file.get_data())
+
+ def test_it_should_respond_with_absolute_url_for_poll_result_file(self):
+ poll = Poll({'name': 'poll'})
+ poll_id = str(poll.save())
+ poll = Poll.get(poll_id)
+ data = self.data
+ data['poll_id'] = poll_id
+
+ file_path = self.make_temp_file(data)
+ result_file = PollResultFile(file_path)
+
+ expected_url = os.path.join(
+ settings.RESULT_BCK_URL, poll_id, result_file.name)
+ self.assertEqual(expected_url, result_file.get_absolute_url())
+
+
+class NumeroEscuelaTest(MongoTestCase):
+
+ def test_it_should_get_all_numero_escuela_as_unique_items(self):
+ data = {}
+ data['result'] = {}
+ data['result']['0'] = {}
+ data['result']['0']['polled'] = {}
+ numero_escuela1 = '1'
+ data['result']['0']['polled']['NUM_ESC'] = numero_escuela1
+ numero_escuela2 = '2'
+ data['result']['1'] = {}
+ data['result']['1']['polled'] = {}
+ data['result']['1']['polled']['NUM_ESC'] = numero_escuela2
+ data['result']['2'] = {}
+ data['result']['2']['polled'] = {}
+ data['result']['2']['polled']['NUM_ESC'] = numero_escuela2
+
+ json_str = json_construc(data)
+ file_ = tempfile.NamedTemporaryFile(suffix='.poll_result',
+ delete=False)
+ file_.write(json_str)
+ file_.close()
+ file_path = file_.name
+
+ result = PollResultFile(file_path)
+ expected = set([numero_escuela1, numero_escuela2])
+ self.assertEqual(expected, result.get_numero_escuela())
diff --git a/webapp/polls/tests/poll_tests.py b/webapp/polls/tests/poll_tests.py
index 20043d3..a3f11c5 100644
--- a/webapp/polls/tests/poll_tests.py
+++ b/webapp/polls/tests/poll_tests.py
@@ -1,12 +1,16 @@
# -*- encoding: utf-8 -*-
import json
import warnings
-
+import tempfile
from bson import ObjectId
+import os
from polls.models import Poll, Field, Structure
+from polls.views import clean_data, is_image_file
from polls.forms import PollForm
from pollster.models import Pollster
+from django.conf import settings
+from django.test import TestCase
from utils.test import MongoTestCase
@@ -356,6 +360,47 @@ class PollTests(MongoTestCase):
self.assertEqual(expected, template)
+ def test_results_path_shoud_be_what_is_in_settings_plus_poll_id(self):
+ poll_without_id = Poll({})
+ with self.assertRaises(Exception):
+ poll_without_id.results_path
+
+ poll_id = ObjectId()
+ poll_with_id = Poll({'id': poll_id})
+ expected = settings.RESULT_BCK_ROOT
+ self.assertIn(expected, poll_with_id.results_path)
+
+ def test_it_should_a_PollResult_when_get_result(self):
+ poll = Poll(data={'name': "poll"})
+ poll_id = poll.save()
+ poll = Poll.get(poll_id)
+
+ poll_result = poll.get_result()
+ self.assertTrue(hasattr(poll_result, "to_csv"))
+
+ def test_has_result_with_no_results(self):
+ poll = Poll(data={'name': "poll"})
+ poll_id = poll.save()
+ poll = Poll.get(poll_id)
+
+ self.assertFalse(poll.has_result())
+
+ def test_has_result_with_at_least_one_result(self):
+ poll = Poll(data={'name': "poll"})
+ poll_id = poll.save()
+ poll = Poll.get(poll_id)
+
+ temp_dir = '/tmp'
+ uploaded_file = tempfile.NamedTemporaryFile(dir=temp_dir,
+ suffix='.poll_result')
+ uploaded_file_path = uploaded_file.name
+ uploaded_filename = os.path.basename(uploaded_file_path)
+
+ poll.add_result_files([(uploaded_file_path, uploaded_filename)])
+ stub = lambda path: os.path.basename(path)
+
+ self.assertTrue(poll.has_result(as_instance_of=stub))
+
class PollFormTests(MongoTestCase):
@@ -379,3 +424,84 @@ class PollFormTests(MongoTestCase):
form = PollForm()
self.assertFalse(form.is_valid())
+
+
+class PollResultFilesTest(MongoTestCase):
+
+ def setUp(self):
+ name = 'nombre de encuesta'
+ data = {'name': name}
+ poll = Poll(data=data)
+ poll_id = poll.save()
+ self.poll = Poll.get(poll_id)
+
+ def test_it_should_return_empty_list_with_no_files(self):
+ poll = self.poll
+ self.assertListEqual([], poll.get_result_files())
+
+ def test_it_should_return_one_result_file(self):
+ poll = self.poll
+
+ tmp_path = tempfile.mkstemp(suffix='.poll_result')[1]
+ with open(tmp_path, 'w') as tmp:
+ tmp.write(
+ json.dumps(
+ '', sort_keys=True, indent=4, separators=(',', ': ')
+ )
+ )
+
+ uploaded_file_path = tmp_path
+ name = 'result.poll_result'
+ poll.add_result_files([(uploaded_file_path, name)])
+ results_files = poll.get_result_files()
+ self.assertEqual(1, len(results_files))
+ self.assertTrue(hasattr(results_files[0], 'get_numero_escuela'))
+
+
+class AddResultFilesTest(MongoTestCase):
+
+ def setUp(self):
+ name = 'nombre de encuesta'
+ data = {'name': name}
+ poll = Poll(data=data)
+ poll_id = poll.save()
+ self.poll = Poll.get(poll_id)
+
+ def test_add_a_result_file(self):
+ temp_dir = '/tmp'
+ uploaded_file = tempfile.NamedTemporaryFile(dir=temp_dir,
+ suffix='.poll_result')
+ uploaded_file_path = uploaded_file.name
+ uploaded_filename = 'other_name.poll_result'
+ poll = self.poll
+
+ poll.add_result_files([(uploaded_file_path, uploaded_filename)])
+ stub = lambda path: os.path.basename(path)
+ results = poll.get_result_files(as_instance_of=stub)
+ result_filename = results[0]
+ self.assertEqual(uploaded_filename, result_filename)
+
+
+class MockImageFileInterface(object):
+
+ def __init__(self):
+ self.name = "a_name.jpg"
+
+
+class CleanDataMustCheckImagesTypes(TestCase):
+
+ def test_is_image(self):
+ image_mock = MockImageFileInterface()
+ self.assertTrue(is_image_file(image_mock))
+
+ def test_it_should_no_raises_with_a_image_file_value(self):
+ data_to_clean = {'some_img': MockImageFileInterface()}
+ try:
+ clean_data(data_to_clean)
+ except AttributeError as e:
+ self.fail(e)
+
+ def test_it_should_strip_values_that_are_not_image_files(self):
+ data_to_clean = {'value': " to strip "}
+ cleaned_data = clean_data(data_to_clean)
+ self.assertEqual("to strip", cleaned_data['value'])
diff --git a/webapp/polls/views.py b/webapp/polls/views.py
index a2239a3..5dc023d 100644
--- a/webapp/polls/views.py
+++ b/webapp/polls/views.py
@@ -3,6 +3,7 @@ import os
import json
import StringIO
import warnings
+from importlib import import_module
from datetime import datetime
@@ -16,18 +17,25 @@ 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 django.shortcuts import render_to_response
from django.conf import settings
from utils.forms import BadFormValidation
from utils.data_structure import dict_merge
-from polls.models import WIDGET_TYPES, Structure, Poll, PollResult
+from polls.models import (WIDGET_TYPES, Structure, Poll, PollResult,
+ PollResultFile)
from polls.forms import PollForm
from pollster.models import Pollster
+module_path = settings.CLOCK_CLASS.split('.')[:-1]
+module_path = ".".join(module_path)
+module = import_module(module_path)
+class_name = settings.CLOCK_CLASS.split('.')[-1:][0]
+Clock = getattr(module, class_name)
+
+
__all__ = [
'StructureFormView',
'PollFormView',
@@ -36,6 +44,10 @@ __all__ = [
]
+def is_image_file(value):
+ return hasattr(value, "name")
+
+
def clean_data(value):
if isinstance(value, dict):
@@ -50,7 +62,7 @@ def clean_data(value):
value = [clean_data(value_) for value_ in value]
return value
- if not isinstance(value, InMemoryUploadedFile):
+ if not is_image_file(value):
value = value.strip(' ')
if value == '':
@@ -145,7 +157,7 @@ class PollListView(ListView):
'is_open': poll.is_open(),
'assigned_to': assigned_to(poll) if assigned_to(poll) else "-",
'action_result_view': {
- 'disabled': "disabled" if not poll.get_result() else "",
+ 'disabled': "disabled" if not poll.has_result() else "",
'url': reverse(
'sociologist:poll_result_detail',
kwargs={'poll_id': str(poll.id)}
@@ -331,12 +343,9 @@ def opt_thumb(request, poll_id, img_name):
def csv_download(request, poll_id):
+ csv = Poll.get(poll_id).get_result().to_csv()
- poll_result = Poll.get(poll_id).get_result()
-
- # Download csv file
- response = HttpResponse(
- poll_result.to_csv(), content_type='text/csv;')
+ response = HttpResponse(csv, content_type='text/csv;')
response['Content-Disposition'] = (
'attachment; filename="%s_result.csv"' % poll_id)
return response
@@ -351,10 +360,13 @@ class UnploadPollResultFormView(TemplateView):
files = dict(request.FILES.lists()).get('result', [])
+ # si se subió algún archivo:
if len(files):
+ # Esto hace todo
to_json = []
+ # genera una lista de dict python en to_json
for file in files:
json_dst = StringIO.StringIO()
for chunk in file.chunks():
@@ -363,6 +375,7 @@ class UnploadPollResultFormView(TemplateView):
to_json.append(json.load(json_dst, 'utf-8'))
file.seek(0)
+ # comprueba si los archivos ya existían y los excluye
poll_id = to_json[0].get("poll_id", None)
results_path = "%s/%s" % (settings.RESULT_BCK_ROOT, poll_id)
uploaded_files = []
@@ -378,26 +391,12 @@ class UnploadPollResultFormView(TemplateView):
", ".join(uploaded_files))
messages.add_message(self.request, messages.INFO, msg)
+ # si no hay más archivos, vuelve al usuario
if not len(files):
return self.render_to_response(context)
- # Validation: Partial files must be from same poll!!!
- fields_to_compare = ['poll_id', 'pollster_id', 'poll_type']
- valid_data = {}
- for to_compare in fields_to_compare:
- comparation = to_json[0].get(to_compare, None)
- valid_data[to_compare] = comparation
-
- for data in to_json[1:]:
- for to_compare in fields_to_compare:
- _value = data.get(to_compare, None)
- if _value != valid_data[to_compare]:
- msg = u'Los resultados parciales no pertenecen \
- a una misma encuesta.'
- messages.add_message(
- self.request, messages.ERROR, msg)
- return self.render_to_response(context)
-
+ # revisa si el primero de los archivos es del usuario actual
+ # si no, vuelve al usuario
if len(to_json):
pollster_id = to_json[0].get("pollster_id", None)
if str(request.user.pollster.id) != pollster_id:
@@ -413,11 +412,13 @@ class UnploadPollResultFormView(TemplateView):
messages.add_message(request, messages.ERROR, msg)
return self.render_to_response(context)
+ # crea un poll result con todos los archivos implicados
poll_result = PollResult(to_json)
poll = poll_result.get_poll()
pollster = Pollster.get_by_user_id(request.user.id)
+ # valida que el pollster esté asignado a la poll en cuestión
assigned_polls_id = [
p.id for p in poll.assigned_to_pollster(pollster)]
@@ -426,47 +427,28 @@ class UnploadPollResultFormView(TemplateView):
que no le fué asignada.'
messages.add_message(self.request, messages.ERROR, msg)
else:
- if poll_result.is_valid():
- try:
- poll_result_id = poll_result.save()
- except Exception:
- msg = u'Ocurrió un error, no se \
- guardaron los resultados.'
- messages.add_message(self.request, messages.ERROR, msg)
- else:
- poll = poll_result.get_poll()
- poll.add_result(poll_result_id)
-
- poll_id = to_json[0].get("poll_id", None)
- results_path = "%s/%s" % (
- settings.RESULT_BCK_ROOT, poll_id)
- try:
- os.makedirs(results_path)
- os.chmod(results_path, 0755)
- except:
- pass
-
- for file in files:
- file_path = "%s/%s" % (results_path, file.name)
- with open(file_path, 'wb+') as dst:
- for chunk in file.chunks():
- dst.write(chunk)
- dst.close()
-
- processed_files = [file.name for file in files]
- messages.add_message(
- self.request,
- messages.SUCCESS,
- u'Los resultados se guardaron con exito.\
- Los siguientes archivos fueron procesados: \
- %s' % ", ".join(processed_files)
- )
-
- else:
- context.update({'errors': poll_result.errors})
-
- context.update({'poll_result': poll_result})
+ # End validations
+
+ date_time_string = Clock.get_time_string()
+ uploaded_files = [
+ (f.name, f.temporary_file_path()) for f in files
+ ]
+ for name, path in uploaded_files:
+ result_file = PollResultFile(path)
+ result_file.name = name
+ result_file.set_upload_timestamp(date_time_string)
+ result_file.save()
+
+ processed_files = [file.name for file in files]
+ messages.add_message(
+ self.request,
+ messages.SUCCESS,
+ u'Los resultados se guardaron con éxito.\
+ Los siguientes archivos fueron procesados: \
+ %s' % ", ".join(processed_files)
+ )
+ # si no hay ningún archivo
else:
msg = u'Necesita seleccionar un archivo.'
messages.add_message(self.request, messages.INFO, msg)
diff --git a/webapp/pollster/templates/pollster-assigned-polls.html b/webapp/pollster/templates/pollster-assigned-polls.html
index d06bf28..da4ae17 100644
--- a/webapp/pollster/templates/pollster-assigned-polls.html
+++ b/webapp/pollster/templates/pollster-assigned-polls.html
@@ -54,7 +54,7 @@
{% for poll in closed_polls %}
<tr>
<td>{{ poll.name|capfirst }}</td>
- <td>{% if poll.get_result %}Existen resultados para esta encuesta y pueden ser visualizados por los investigadores.{% else %}No tiene resultados.{% endif %}</td>
+ <td>{% if poll.has_result %}Existen resultados para esta encuesta y pueden ser visualizados por los investigadores.{% else %}No tiene resultados.{% endif %}</td>
<td>
<a class="btn" href="{% url pollsters:poll_download poll_id=poll.id %}">
<i class="icon-download-alt"></i>&nbsp;Descargar encuesta
diff --git a/webapp/sociologist/templates/poll-result-list.html b/webapp/sociologist/templates/poll-result-list.html
index cfd98aa..53c92e5 100644
--- a/webapp/sociologist/templates/poll-result-list.html
+++ b/webapp/sociologist/templates/poll-result-list.html
@@ -1,114 +1,46 @@
{% extends "base-poll.html" %}
-{% load poll_tags util_tags %}
-{% block main_container %}
-
- <script type="text/javascript">
- var sortingOrder = {{ sorting_order|json }};
- var searchableFields = {{ searchable_fields|json }};
- </script>
-
- <div ng-app="poll-result-list" ng-controller="ctrlRead">
-
- <div ng-controller="ModalPollCtrl" class="pull-right">
- <button class="btn" ng-click="open()">Visualizar preguntas</button>
- <a class="btn btn-default" href="{% url sociologist:csv_download poll.id %}"><i class="icon-download"></i>&nbsp;Formato CSV</a>
- <div modal="shouldBeOpen" close="close()" options="opts">
- <div class="modal-header">
- <h4>Encuesta:&nbsp;{{ poll.name|capfirst }}</h4>
- </div>
- <div class="modal-body">
- <ul>
- <li ng-repeat="item in items">{% ng item %}</li>
- </ul>
- </div>
- <div class="modal-footer">
- <button class="btn btn-warning cancel" ng-click="close()">Cerrar</button>
- </div>
- </div>
- </div>
-
- <div class="input-append">
- <input type="text" ng-model="query" ng-change="search()" class="input-large search-query" placeholder="Buscar">
- <span class="add-on"><i class="icon-search"></i></span>
- </div>
+{% block extra_css %}
+<style type="text/css">
+ .row-fluid {padding: 10px 0px 10px 0px;}
+</style>
+{% endblock %}
+{% block main_container %}
+<div class="row-fluid">
+ <div><h2>{{ poll_name }}</h2></div>
+ <center>
+ <a name="download_results_as_csv" class="btn btn-default" href="{% url sociologist:csv_download poll_id %}">
+ <i class="icon-download"></i>&nbsp;Descargar resultados (csv)
+ </a>
+ </center>
+</div>
+<div class="row-fluid">
+ <div class="span12">
<table class="table table-condensed table-hover table-bordered">
<thead>
<tr>
- {% for column in column_names %}
- <th class="{{ column.ng_class }} span5 {{ column.classes}} ">{{column.label}}&nbsp;<a ng-click="sort_by('{{ column.ng_class }}')"><i class="icon-sort"></i></a></th>
- {% endfor %}
+ <th>N&uacute;mero de escuela</th>
+ <th>Nombre de Encuestador</th>
+ <th>Cantidad de Encuestados</th>
+ <th>Nombre del Archivo .polls_results</th>
+ <th>Time Stamp</th>
</tr>
</thead>
<tbody>
- <tr ng-controller="CollapseDemoCtrl" ng-click="isCollapsed = !isCollapsed" style="cursor: pointer;" ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse" >
- {% for column in column_names %}
- {% with item_attr="item."|add:column.ng_class %}
- {% if forloop.first %}
- <td class="span12">
- <strong>{% ng item_attr %}</strong>
- <div collapse="isCollapsed">
- <ul>
- {% for question in poll.get_questions %}
- {% with question_name=question|clean_question %}
- {% with field_options="item.answers."|add:question_name|add:".options" field_weight="item.answers."|add:question_name|add:".weight" %}
- <strong>{{ question }}&nbsp;(Ponderaci&oacute;n: {% ng field_weight %})</strong>:
- <li ng-bind-html-unsafe="{{field_options}}">
- {% ng field_options %}
- </li>
- {% endwith %}
- {% endwith %}
- {% endfor %}
- </ul>
- </div>
- </td>
- {% else %}
- {% if "hide" not in column.classes|default_if_none:"" %}
- <td><strong>{% ng item_attr %}</strong></td>
- {% endif %}
- {% endif %}
- {% endwith %}
- {% endfor %}
- </tr>
+ {% for poll_result in poll_results %}
+ <tr>
+ <td>{{ poll_result.numero_escuela|join:", " }}</td>
+ <td>{{ poll_result.pollster }}</td>
+ <td>{{ poll_result.cantidad_encuestados }}</td>
+ <td>
+ <a name="poll_result_file" href="{{ poll_result.absolute_url }}">{{ poll_result.file_name }}</a>
+ </td>
+ <td>{{ poll_result.upload_timestamp|default_if_none:"Sin fecha registrada" }}</td>
+ </tr>
+ {% endfor %}
</tbody>
- {% include "paginator_footer.html" %}
</table>
-
</div>
-
- {% paginator_for results|json %}
-
- <script src="{{ STATIC_URL }}js/ui-bootstrap-0.2.0.min.js"></script>
- <script src="{{ STATIC_URL }}js/ui-bootstrap-tpls-0.2.0.min.js"></script>
-
- <script type="text/javascript">
-
- angular.module('poll-result-list', ['ui.bootstrap']);
-
- function CollapseDemoCtrl($scope) {
- $scope.isCollapsed = true;
- }
-
- function ModalPollCtrl($scope) {
-
- $scope.open = function () {
- $scope.shouldBeOpen = true;
- };
-
- $scope.close = function () {
- $scope.closeMsg = 'I was closed at: ' + new Date();
- $scope.shouldBeOpen = false;
- };
-
- $scope.items = {{ poll.get_questions|json }};
-
- $scope.opts = {
- backdropFade: true,
- dialogFade:true
- };
-
- };
-
- </script>
+</div>
{% endblock %} \ No newline at end of file
diff --git a/webapp/sociologist/views.py b/webapp/sociologist/views.py
index 4f0a0af..85066a9 100644
--- a/webapp/sociologist/views.py
+++ b/webapp/sociologist/views.py
@@ -3,16 +3,16 @@ from bson import ObjectId, DBRef
from django.views.generic.list import ListView
from django.views.generic.edit import FormView
+from django.views.generic.base import TemplateView
from django.http import HttpResponseRedirect, Http404
from django.core.urlresolvers import reverse
from django.contrib import messages
-from django.template.loader import render_to_string
from utils.mongo_connection import get_db
from utils.forms import BadFormValidation
from pollster.models import Pollster
-from polls.models import Poll, Option
+from polls.models import Poll
from pollster.forms import PollsterForm, PollAsignationForm
from sociologist.forms import SociologistForm
@@ -26,11 +26,9 @@ __all__ = [
]
-# TODO: Refactoring!
-class PollResultListView(ListView):
+class PollResultListView(TemplateView):
template_name = "poll-result-list.html"
- context_object_name = "results"
def dispatch(self, *args, **kwargs):
@@ -49,127 +47,26 @@ class PollResultListView(ListView):
context = super(PollResultListView, self).get_context_data(
**kwargs)
- column_names = [
- {'ng_class': "polled_name", 'label': "Encuestado"},
- {'ng_class': "completed_questions", 'classes': "hide"},
- {'ng_class': "polled_escuela", 'label': "Escuela"},
- {'ng_class': "polled_grado", 'label': "Grado"},
- {'ng_class': "polled_grupo", 'label': "Grupo"},
- ]
- searchable_fields = [
- 'polled_name',
- 'completed_questions',
- 'polled_escuela',
- 'polled_grado',
- 'polled_grupo',
- ]
-
- sorting_order = "polled_name"
-
- if self.poll_type == "general":
- searchable_fields[0] = 'polled_id'
- column_names[0] = {
- 'ng_class': "polled_id", 'classes': "", 'label': "Encuesta ID"
+ poll = self.poll
+ poll_result_files = poll.get_result_files()
+
+ poll_results = []
+ for poll_result_file in poll_result_files:
+ poll_result = {
+ 'numero_escuela': poll_result_file.get_numero_escuela(),
+ 'pollster': poll_result_file.get_pollster_username(),
+ 'cantidad_encuestados': poll_result_file.get_polled_count(),
+ 'file_name': poll_result_file.get_file_name(),
+ 'upload_timestamp': poll_result_file.get_upload_timestamp(),
+ 'absolute_url': poll_result_file.get_absolute_url()
}
- sorting_order = "polled_grado"
-
- context.update({
- 'poll': self.poll,
- 'do_no_respond': self.do_no_respond,
- 'searchable_fields': searchable_fields,
- 'column_names': column_names,
- 'sorting_order': sorting_order
- })
+ poll_results.append(poll_result)
+ context['poll_results'] = poll_results
+ context['poll_id'] = str(poll.id)
+ context['poll_name'] = poll.name.capitalize()
return context
- def get_queryset(self, *args, **kwargs):
- _list = []
-
- poll_result = self.poll.get_result()
-
- questions = map(Poll.clean_question, self.poll.get_questions())
-
- self.poll_type = poll_result.to_python()['poll_type']
-
- index = 0
- for id_polled, data in poll_result.to_python()['result'].iteritems():
- item = {
- "answers": {},
- "polled_name": data['polled'].get('NOMBRE', index),
- "polled_escuela": data['polled']['NUM_ESC'],
- "polled_grado": data['polled']['GRADO'],
- "polled_grupo": data['polled']['GRUPO']
- }
-
- if self.poll_type == "general":
- del item['polled_name']
- item.update({
- "polled_id": "Encuesta #%s" % index
- })
-
- answers = data['answers']
- if len(answers):
- completed_questions = []
- for group_order, fields in answers.iteritems():
- fields = fields['fields']
- for field_order, field_data in fields.iteritems():
- _answers = []
- _weight = 0
- answer = field_data['answer']
- for option_id, option_selected in answer.iteritems():
- if "Image" in field_data['widget_type']:
- img_src, media_url = Option.get_img(
- str(self.poll.id), option_id)
- if img_src is not None:
- img = render_to_string(
- 'image_option_thumbnail.html',
- {"img": open(img_src)}
- )
- _answers.append(img)
- text = option_selected.get('text', None)
- if text:
- _answers.append(text)
- else:
- _answers.append(
- option_selected.get('text', ""))
- _weight += option_selected.get('weight', 0)
-
- if len(_answers):
- completed_questions.append(field_data['name'])
- field_name = Poll.clean_question(
- field_data['name'])
- item['answers'].update({
- field_name: {
- "weight": str(_weight),
- "options": ", ".join(_answers)
- }
- })
-
- item['completed_questions'] = " ".join(
- completed_questions)
-
- for field_name in questions:
- if field_name not in item['answers'].keys():
- item['answers'].update({
- field_name: {
- "weight": "-",
- "options": "No responde."
- }
- })
-
- _list.append(item)
- index += 1
- elif not len(answers) and self.poll_type == "monitoreo":
- self.do_no_respond.append({
- "polled_name": data['polled'].get('NOMBRE', index),
- "polled_escuela": data['polled']['NUM_ESC'],
- "polled_grado": data['polled']['GRADO'],
- "polled_grupo": data['polled']['GRUPO']
- })
-
- return _list
-
class PollsterListView(ListView):
diff --git a/webapp/utils/clock.py b/webapp/utils/clock.py
new file mode 100644
index 0000000..a7fc23a
--- /dev/null
+++ b/webapp/utils/clock.py
@@ -0,0 +1,11 @@
+from datetime import datetime
+
+
+class Clock(object):
+
+ datetime = datetime
+
+ @classmethod
+ def get_time_string(cls):
+ date_str_format = "%d/%m/%Y %H:%Mhs"
+ return cls.datetime.now().strftime(date_str_format)
diff --git a/webapp/utils/tests/__init__.py b/webapp/utils/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webapp/utils/tests/__init__.py
diff --git a/webapp/utils/tests/clock_tests.py b/webapp/utils/tests/clock_tests.py
new file mode 100644
index 0000000..27207e3
--- /dev/null
+++ b/webapp/utils/tests/clock_tests.py
@@ -0,0 +1,20 @@
+import unittest
+from datetime import datetime
+
+from utils.clock import Clock
+
+
+class MockDatetime(object):
+
+ def now(self):
+ return datetime(1999, 12, 31, 23, 59, 00)
+
+
+class ClockTests(unittest.TestCase):
+
+ def test_it_shoult_respond_to_get_time_string(self):
+ self.assertTrue(hasattr(Clock, "get_time_string"))
+
+ def test_get_time_string_should_respond_with_date_format_string(self):
+ Clock.datetime = MockDatetime()
+ self.assertEqual("31/12/1999 23:59hs", Clock.get_time_string())
diff --git a/webapp/webapp/features/active_resultados_button.feature b/webapp/webapp/features/active_resultados_button.feature
new file mode 100644
index 0000000..0ab83a9
--- /dev/null
+++ b/webapp/webapp/features/active_resultados_button.feature
@@ -0,0 +1,11 @@
+Feature: Button for show results is active when a poll has results
+ As a researcher, I can do click in "Resultados" button
+ of "list of polls" page to see "Resultados" page
+
+ Scenario: A poll with .poll_results uploaded
+ Given I am a researcher
+ And "poll1" exists
+ And "encuestador1" is binded to "poll1"
+ And with "simple.poll_result" uploaded by "encuestador1" on "31/12/2000 24:00hs"
+ When I visit the "list of polls" page
+ Then I should see active a "Resultados" button \ No newline at end of file
diff --git a/webapp/webapp/features/active_resultados_button.py b/webapp/webapp/features/active_resultados_button.py
new file mode 100644
index 0000000..7876d59
--- /dev/null
+++ b/webapp/webapp/features/active_resultados_button.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+from lettuce import step, world
+
+
+@step(u'.*I should see active a "([^"]*)" button')
+def i_should_see_active_a_status_button(step, status):
+ browser = world.browser
+ xpath = "/html/body/div/div[2]/div/table/tbody/tr[1]/td[4]/a"
+ element = browser.find_by_xpath(xpath).first
+
+ assert not element.has_class('disabled')
diff --git a/webapp/webapp/features/download_csv_button.py b/webapp/webapp/features/download_csv_button.py
new file mode 100644
index 0000000..8543f94
--- /dev/null
+++ b/webapp/webapp/features/download_csv_button.py
@@ -0,0 +1,22 @@
+from lettuce import step, world
+from lettuce.django import django_url
+from nose.tools import assert_true, assert_equals
+from django.core.urlresolvers import reverse
+
+
+@step(u'Then I should see a "([^"]*)" button')
+def then_i_should_see_a_label_button(step, button_label):
+ browser = world.browser
+ button_names = {"Descargar resultados (csv)": "download_results_as_csv"}
+ button_name = button_names[button_label]
+ assert_true(
+ browser.is_element_present_by_name(button_name))
+ world.button = browser.find_by_name(button_name)[0]
+
+
+@step(u'And the button links to "([^"]*)" for "([^"]*)"')
+def button_links_to_url_name_for_poll_name(step, url_name, poll_name):
+ poll_id = str(world.poll_id)
+ button = world.button
+ csv_download_url = reverse(url_name, kwargs={'poll_id': poll_id})
+ assert_equals(django_url(csv_download_url), button['href'])
diff --git a/webapp/webapp/features/download_csv_button_exists.feature b/webapp/webapp/features/download_csv_button_exists.feature
new file mode 100644
index 0000000..50ddf76
--- /dev/null
+++ b/webapp/webapp/features/download_csv_button_exists.feature
@@ -0,0 +1,11 @@
+Feature: Button for download results as csv
+ As a reasearcher
+ I want see a csv button
+ So that I can download all results
+
+ Scenario: Visit "Resultados" page with at least one .poll_result
+ Given I am a researcher
+ And "poll1" exists
+ When I visit the "Resultados" page for "poll1" poll
+ Then I should see a "Descargar resultados (csv)" button
+ And the button links to "sociologist:csv_download" for "poll1"
diff --git a/webapp/webapp/features/download_poll_result_file.feature b/webapp/webapp/features/download_poll_result_file.feature
new file mode 100644
index 0000000..97acd08
--- /dev/null
+++ b/webapp/webapp/features/download_poll_result_file.feature
@@ -0,0 +1,12 @@
+Feature: Link to download .poll_result file
+ As a reasearcher
+ I want to be able to download a .poll_result of a poll
+ So that I can analyse it as it is
+
+ Scenario: One .poll_result uploaded
+ Given I am a researcher
+ And "poll1" is "Cerrada"
+ And "encuestador1" is binded to "poll1"
+ And with "simple.poll_result" uploaded by "encuestador1" on "31/12/2000 23:59hs"
+ When I visit the "Resultados" page for "poll1" poll
+ Then I should see link to "simple.poll_result" \ No newline at end of file
diff --git a/webapp/webapp/features/fixtures/2schools.poll_result b/webapp/webapp/features/fixtures/2schools.poll_result
new file mode 100644
index 0000000..707c8ea
--- /dev/null
+++ b/webapp/webapp/features/fixtures/2schools.poll_result
@@ -0,0 +1,33 @@
+{
+ "poll_id":"",
+ "poll_name":"",
+ "poll_type":"general",
+ "pollster_id":"",
+ "pollster_username":"",
+ "result":{
+ "0":{
+ "answers":{},
+ "polled":{
+ "DEPARTAMENTO":"MONTEVIDEO",
+ "GRADO":"2",
+ "GRUPO":"A",
+ "ID":"0",
+ "NUM_ESC":"1",
+ "RUEE":"1101236",
+ "TIPO_GRUPO":"1"
+ }
+ },
+ "1":{
+ "answers":{},
+ "polled":{
+ "DEPARTAMENTO":"MONTEVIDEO",
+ "GRADO":"2",
+ "GRUPO":"A",
+ "ID":"0",
+ "NUM_ESC":"2",
+ "RUEE":"1101236",
+ "TIPO_GRUPO":"1"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/webapp/webapp/features/fixtures/simple.poll_result b/webapp/webapp/features/fixtures/simple.poll_result
new file mode 100644
index 0000000..18c7308
--- /dev/null
+++ b/webapp/webapp/features/fixtures/simple.poll_result
@@ -0,0 +1,21 @@
+{
+ "poll_id":"",
+ "poll_name":"",
+ "poll_type":"general",
+ "pollster_id":"",
+ "pollster_username":"",
+ "result":{
+ "0":{
+ "answers":{},
+ "polled":{
+ "DEPARTAMENTO":"MONTEVIDEO",
+ "GRADO":"2",
+ "GRUPO":"A",
+ "ID":"0",
+ "NUM_ESC":"236",
+ "RUEE":"1101236",
+ "TIPO_GRUPO":"1"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/webapp/webapp/features/fixtures/simple2.poll_result b/webapp/webapp/features/fixtures/simple2.poll_result
new file mode 100644
index 0000000..bb9168b
--- /dev/null
+++ b/webapp/webapp/features/fixtures/simple2.poll_result
@@ -0,0 +1,33 @@
+{
+ "poll_id":"",
+ "poll_name":"",
+ "poll_type":"general",
+ "pollster_id":"",
+ "pollster_username":"",
+ "result":{
+ "0":{
+ "answers":{},
+ "polled":{
+ "DEPARTAMENTO":"MONTEVIDEO",
+ "GRADO":"2",
+ "GRUPO":"A",
+ "ID":"0",
+ "NUM_ESC":"236",
+ "RUEE":"1101236",
+ "TIPO_GRUPO":"1"
+ }
+ },
+ "1":{
+ "answers":{},
+ "polled":{
+ "DEPARTAMENTO":"MONTEVIDEO",
+ "GRADO":"2",
+ "GRUPO":"A",
+ "ID":"0",
+ "NUM_ESC":"236",
+ "RUEE":"1101236",
+ "TIPO_GRUPO":"1"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/webapp/webapp/features/index.py b/webapp/webapp/features/index.py
index e273bd9..b7e2a72 100644
--- a/webapp/webapp/features/index.py
+++ b/webapp/webapp/features/index.py
@@ -1,14 +1,8 @@
-from lettuce import *
-from splinter import Browser
+from lettuce import step, world
from nose.tools import assert_equals
from lettuce.django import django_url
-@before.all
-def set_browser():
- world.browser = Browser('phantomjs')
-
-
@step(r'I access the url "(.*)"')
def access_url(step, url):
url = django_url(url)
@@ -18,8 +12,3 @@ def access_url(step, url):
@step(r'I should see "(.*)" as title')
def then_i_should_see_the_title_group1(step, title):
assert_equals(world.browser.title, title)
-
-
-@after.all
-def exit_browser(total):
- world.browser.quit()
diff --git a/webapp/webapp/features/poll_result_details.feature b/webapp/webapp/features/poll_result_details.feature
new file mode 100644
index 0000000..331bad3
--- /dev/null
+++ b/webapp/webapp/features/poll_result_details.feature
@@ -0,0 +1,20 @@
+Feature: A Researcher sees the .poll_result details of a poll
+ As a Researcher
+ I want see the .poll_result details of a given poll
+ So that I can inspect a particular .poll_result
+ And I can see the .poll_results uploaded during the progress of a poll
+
+ Scenario: 3 .poll_results uploaded
+ Given I am a researcher
+ And "poll1" is "Cerrada"
+ And "encuestador1" is binded to "poll1"
+ And "encuestador2" is binded to "poll1"
+ And with "simple.poll_result" uploaded by "encuestador1" on "31/12/2000 24:00hs"
+ And with "simple2.poll_result" uploaded by "encuestador2" on "01/01/2001 00:01hs"
+ And with "2schools.poll_result" uploaded by "encuestador2" on "15/09/2013 09:14hs"
+ When I visit the "Resultados" page for "poll1" poll
+ Then I should see the poll_result details:
+ | Número de escuela | Nombre de Encuestador | Cantidad de Encuestados | Nombre del Archivo .polls_results | Time Stamp |
+ | 236 | encuestador1 | 1 | simple.poll_result | 31/12/2000 24:00hs |
+ | 236 | encuestador2 | 2 | simple2.poll_result | 01/01/2001 00:01hs |
+ | 1, 2 | encuestador2 | 2 | 2schools.poll_result | 15/09/2013 09:14hs |
diff --git a/webapp/webapp/features/poll_result_details.py b/webapp/webapp/features/poll_result_details.py
new file mode 100644
index 0000000..a63c246
--- /dev/null
+++ b/webapp/webapp/features/poll_result_details.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+import json
+import os
+import tempfile
+import shutil
+
+from lettuce import step, world, after
+from lettuce.django import django_url
+from nose.tools import assert_equals
+from django.conf import settings
+
+from pollster.models import Pollster
+from polls.models import Poll
+
+
+def create_poll_result_file(poll_result_data, poll_result_name):
+ tmp_file = tempfile.NamedTemporaryFile(suffix='.poll_result', delete=False)
+ try:
+ tmp_file.write(
+ json.dumps(poll_result_data, sort_keys=True, indent=4,
+ separators=(',', ': '))
+ )
+ tmp_dir = os.path.dirname(tmp_file.name)
+ poll_result_path = os.path.join(tmp_dir, poll_result_name)
+ os.rename(tmp_file.name, poll_result_path)
+ finally:
+ tmp_file.close()
+ return poll_result_path
+
+
+def get_pollster_by_username(encuestador_name):
+ return filter(lambda e: e.username == encuestador_name, Pollster.all())[0]
+
+
+@step(u'When I visit the "([^"]*)" page for "([^"]*)" poll')
+def visit_page_for_poll(step, page_name, poll_name):
+ page_list = {'Resultados': 'results'}
+ page = page_list[page_name]
+ poll_id = world.poll_id.__str__()
+ b = world.browser
+ url = '/manager/polls/{poll_id}/{page}/'.format(poll_id=poll_id, page=page)
+ b.visit(django_url(url))
+
+
+@step(u'Then I should see the poll_result details:')
+def then_i_should_see_the_poll_result_details(step):
+ b = world.browser
+ for poll_result_details in step.hashes:
+ for detail_desc in poll_result_details.keys():
+ assert b.is_text_present(detail_desc), \
+ detail_desc.encode('utf-8') + " is not present"
+ value = poll_result_details[detail_desc]
+ assert b.is_text_present(value), \
+ value.encode('utf-8') + " is not present"
+
+
+@after.each_feature
+def remove_poll_results_files(feature):
+ if feature.name == 'Poll result details':
+ MEDIA_RESULTS = settings.RESULT_BCK_ROOT
+ result_dir = os.path.join(MEDIA_RESULTS, world.poll_id.__str__())
+ shutil.rmtree(result_dir)
+
+
+@step(u'Then I should see link to "([^"]*)"')
+def then_i_should_see_link_to_poll_result_name(step, poll_result_name):
+ browser = world.browser
+ poll_id = str(world.poll_id)
+ poll = Poll.get(poll_id)
+ poll_result_file = poll.get_result_files()[0]
+
+ poll_result_url = poll_result_file.get_absolute_url()
+ element = browser.find_by_name("poll_result_file").first
+ assert_equals(django_url(poll_result_url), element['href'])
diff --git a/webapp/webapp/features/resultados_need_poll_name.feature b/webapp/webapp/features/resultados_need_poll_name.feature
new file mode 100644
index 0000000..5ac23da
--- /dev/null
+++ b/webapp/webapp/features/resultados_need_poll_name.feature
@@ -0,0 +1,7 @@
+Feature: Result page for a poll needs poll name
+
+ Scenario: "Result" page has name of current poll
+ Given I am a researcher
+ And "poll1" exists
+ When I visit the "Resultados" page for "poll1" poll
+ Then I should see a message that says "Poll1"
diff --git a/webapp/webapp/features/steps.py b/webapp/webapp/features/steps.py
new file mode 100644
index 0000000..1f5a2fd
--- /dev/null
+++ b/webapp/webapp/features/steps.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+import os
+import json
+import shutil
+
+from lettuce.django import django_url
+from lettuce import step, world
+from django.conf import settings
+from nose.tools import assert_true
+
+from sociologist.models import Sociologist
+from pollster.models import Pollster
+from polls.models import Poll
+from poll_result_details import get_pollster_by_username
+
+from poll_result_details import create_poll_result_file
+
+
+@step(u'When I visit the "([^"]*)" page$')
+def when_i_visit_the_page_name(step, page_name):
+ page_list = {
+ 'upload': 'polls/upload_result',
+ 'list of polls': 'manager/polls/'
+ }
+ page = page_list[page_name]
+ b = world.browser
+ b.visit(django_url(page))
+
+
+@step(u'Given I am a researcher')
+def given_i_am_a_researcher(step):
+ username = "researcher"
+ password = username
+ create_user_and_login(Sociologist, username, password)
+
+
+def login(browser, username, password):
+ browser.visit(django_url('/accounts/login'))
+ browser.fill_form({'username': username, 'password': password})
+ login_xpath = '/html/body/div/div[2]/div/form/fieldset/button'
+ browser.find_by_xpath(login_xpath).first.click()
+
+
+@step(u'Given I am a pollster: "([^"]*)"')
+def given_i_am_a_pollster_username(step, username):
+ password = username
+ create_user_and_login(Pollster, username, password)
+
+
+def create_user_and_login(rol, username, password):
+ rol.create(username=username, password=password)
+ b = world.browser
+ login(b, username, password)
+
+
+def create_poll(name, status=Poll.OPEN):
+ poll = Poll(data={'name': name, 'status': status})
+ world.poll_id = poll.save()
+
+
+@step(u'And "([^"]*)" exists')
+def and_poll_name_exists(step, poll_name):
+ create_poll(poll_name)
+
+
+@step(u'And "([^"]*)" is "([^"]*)"')
+def and_poll_is_status(step, poll_name, status):
+ status = Poll.CLOSED if status == "Cerrada" else Poll.OPEN
+ create_poll(poll_name, status)
+
+
+@step(u'And "([^"]*)" is binded to "([^"]*)"')
+def and_encuestador_is_binded_to_poll(step, encuestador, poll_name):
+ username = encuestador
+ password = username
+ try:
+ pollster = get_pollster_by_username(encuestador)
+ except IndexError:
+ pollster = Pollster.create(username=username, password=password)
+ polls = [world.poll_id]
+ Poll.pollster_assignment(pollster.id, polls)
+
+
+def get_fixture(poll_result):
+ path = os.path.abspath(os.path.dirname(__file__))
+ path = os.path.join(path, "fixtures", poll_result)
+ return path
+
+
+@step(u'And with "([^"]*)" uploaded by "([^"]*)" on "([^"]*)"')
+def poll_result_uploaded_by_encuestador(step, poll_result_name,
+ encuestador_name, datetime_str):
+ poll_result_path = get_fixture(poll_result_name)
+ encuestador = get_pollster_by_username(encuestador_name)
+
+ with open(poll_result_path) as f:
+ poll_result_data = json.load(f, "utf-8")
+ poll_result_data['poll_id'] = world.poll_id.__str__()
+ poll_result_data['pollster_username'] = encuestador.username
+ poll_result_data['pollster_id'] = encuestador.id.__str__()
+ poll_result_data['upload_timestamp'] = datetime_str
+
+ poll_result_path = create_poll_result_file(poll_result_data,
+ poll_result_name)
+
+ media_results = settings.RESULT_BCK_ROOT
+ results_dir = os.path.join(media_results, world.poll_id.__str__())
+ if not os.path.exists(results_dir):
+ os.mkdir(results_dir)
+ shutil.move(poll_result_path, results_dir)
+
+
+@step(u'Then I should see a message that says "([^"]*)"$')
+def i_should_see_a_message_that_says_text(step, text):
+ b = world.browser
+ assert_true(b.is_text_present(text))
diff --git a/webapp/webapp/features/terrain.py b/webapp/webapp/features/terrain.py
new file mode 100644
index 0000000..70f079b
--- /dev/null
+++ b/webapp/webapp/features/terrain.py
@@ -0,0 +1,33 @@
+from lettuce import world, before, after
+from splinter import Browser
+from fabric.api import local
+from fabric.context_managers import hide
+from django.conf import settings
+from django.core import management
+
+
+@before.all
+def set_browser():
+ world.browser = Browser('phantomjs')
+
+
+@after.all
+def exit_browser(total):
+ world.browser.quit()
+
+
+def drop_mongo():
+ db = settings.MONGO_SETTINGS['NAME']
+ with hide('running'):
+ cmd = "mongo {mongo_db} --eval 'db.dropDatabase()' > /dev/null"
+ local(cmd.format(mongo_db=db))
+
+
+def drop_sqlite():
+ management.call_command('flush', interactive=False, verbosity=0)
+
+
+@before.each_feature
+def before_each_feature(feature):
+ drop_mongo()
+ drop_sqlite()
diff --git a/webapp/webapp/features/upload_poll_result.feature b/webapp/webapp/features/upload_poll_result.feature
new file mode 100644
index 0000000..fa01a95
--- /dev/null
+++ b/webapp/webapp/features/upload_poll_result.feature
@@ -0,0 +1,14 @@
+Feature: Upload .poll_result files
+ As a pollster
+ I want to upload .poll_result files
+ So that researcher can compute them like a chunk of the whole poll result
+
+ Scenario: Upload one valid .poll_result file
+ Given I am a pollster: "encuestador1"
+ And "poll1" is "Cerrada"
+ And "encuestador1" is binded to "poll1"
+ And "valid.poll_result" is authored by "encuestador1"
+ When I visit the "upload" page
+ And I upload "valid.poll_result" on "31/12/1999 23:59hs"
+ Then I should see a message that says "valid.poll_result" and "éxito"
+ And "valid.poll_result" has the time string "31/12/1999 23:59hs"
diff --git a/webapp/webapp/features/upload_poll_result.py b/webapp/webapp/features/upload_poll_result.py
new file mode 100644
index 0000000..dc15c42
--- /dev/null
+++ b/webapp/webapp/features/upload_poll_result.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+from lettuce import step, world
+from nose.tools import assert_true, assert_equals
+
+from polls.models import Poll
+from poll_result_details import (create_poll_result_file,
+ get_pollster_by_username)
+
+
+@step(u'And "([^"]*)" is authored by "([^"]*)"')
+def result_file_is_authored_by_pollster(step, file_name, pollster_username):
+ pollster = get_pollster_by_username(pollster_username)
+ data = {}
+ data['poll_id'] = world.poll_id.__str__()
+ data['pollster_username'] = pollster.username
+ data['pollster_id'] = pollster.id.__str__()
+ data['result'] = {}
+ world.poll_result_path = create_poll_result_file(data, file_name)
+
+
+@step(u'And I upload "([^"]*)" on "([^"]*)"')
+def upload_file_name_on_time_string(step, file_name, time_string):
+ b = world.browser
+ b.find_by_xpath('/html/body/div/div[2]/form/fieldset/div/button').click()
+ b.attach_file('result', world.poll_result_path)
+ b.find_by_xpath('/html/body/div/div[2]/form/div[1]/div/button').click()
+
+
+@step(u'Then I should see a message that says "([^"]*)" and "([^"]*)"')
+def i_should_see_a_message_that_says_text1_and_text2(step, text1, text2):
+ b = world.browser
+ assert_true(b.is_text_present(text1))
+ assert_true(b.is_text_present(text2))
+
+
+@step(u'And "([^"]*)" has the time string "([^"]*)"')
+def file_name_has_the_time_string_time_string(step, file_name, time_string):
+ poll = Poll.get(world.poll_id)
+ result_files = poll.get_result_files()
+ result_file = filter(
+ lambda f: file_name == f.get_file_name(), result_files)[0]
+ # Using (mock) Clock class from django settings
+ assert_equals(time_string, result_file.get_upload_timestamp())
diff --git a/webapp/webapp/settings.py b/webapp/webapp/settings.py
index 865817b..b3e5671 100644
--- a/webapp/webapp/settings.py
+++ b/webapp/webapp/settings.py
@@ -126,6 +126,10 @@ TEMPLATE_DIRS = (
PROJECT_ROOT + "/templates",
)
+FILE_UPLOAD_HANDLERS = (
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",
+)
+
INSTALLED_APPS = (
# Test Runner
@@ -215,12 +219,16 @@ IMAGES_ROOT = MEDIA_ROOT + 'images'
RESULT_BCK_ROOT = MEDIA_ROOT + 'results_bck'
+RESULT_BCK_URL = MEDIA_URL + 'results_bck'
+
IMAGE_OPTIONS_MEDIA_URL = MEDIA_URL + 'image_options'
IMAGES_MEDIA_URL = MEDIA_URL + 'images'
THUMBNAIL_DEBUG = True
+CLOCK_CLASS = "utils.clock.Clock"
+
try:
from env_settings import *
except ImportError:
diff --git a/webapp/webapp/test_settings.py b/webapp/webapp/test_settings.py
index 099352f..5e8b856 100644
--- a/webapp/webapp/test_settings.py
+++ b/webapp/webapp/test_settings.py
@@ -23,3 +23,19 @@ register_connection(
LETTUCE_SERVER_PORT = 63001
+
+
+DATABASES['default']['TEST_NAME'] = '/tmp/testserver.db'
+
+
+RESULT_BCK_ROOT = '/tmp'
+
+
+class MockClock(object):
+
+ @staticmethod
+ def get_time_string():
+ return "31/12/1999 23:59hs"
+
+
+CLOCK_CLASS = "webapp.test_settings.MockClock"