diff options
author | Code 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) |
commit | ffe7384641b9b52ff5ede5836827610f1cc23c68 (patch) | |
tree | 99496d43be49ac26a29fb41307f152e278f434bf | |
parent | 32c10ae4b8e62ed6957ea349307a78fee05e86cd (diff) | |
parent | c9236813d1e51d86fcc9955800568bae84e7d73e (diff) |
issue 4234: Show the date and the user who uploaded the results
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> 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> Formato CSV</a> - <div modal="shouldBeOpen" close="close()" options="opts"> - <div class="modal-header"> - <h4>Encuesta: {{ 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> 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}} <a ng-click="sort_by('{{ column.ng_class }}')"><i class="icon-sort"></i></a></th> - {% endfor %} + <th>Nú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 }} (Ponderació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" |