From a5ffd91bf5d17ee5b3bbfc271c96d9b8aa8886ea Mon Sep 17 00:00:00 2001 From: Code Raguet Date: Thu, 26 Sep 2013 16:09:37 +0000 Subject: Merge branch 'DEV' --- diff --git a/webapp/polls/exceptions.py b/webapp/polls/exceptions.py index 76e1fa0..32262a1 100644 --- a/webapp/polls/exceptions.py +++ b/webapp/polls/exceptions.py @@ -4,3 +4,8 @@ class ValidationError(Exception): class UniqueNameError(Exception): pass + + +class ReadOnly(Exception): + """Exception when trying to write a read only object.""" + pass diff --git a/webapp/polls/models.py b/webapp/polls/models.py index 0090164..9402c94 100644 --- a/webapp/polls/models.py +++ b/webapp/polls/models.py @@ -10,9 +10,7 @@ import copy import warnings import glob import errno - from distutils.dir_util import copy_tree -from exceptions import ValidationError, UniqueNameError from bson import ObjectId, DBRef from datetime import datetime @@ -23,8 +21,8 @@ from django.core.exceptions import ValidationError as DjangoValidationError from utils.mongo_connection import get_db from utils.mongo_document import Document from utils.strings import multiple_replace - from pollster.models import Pollster +from polls.exceptions import ReadOnly, ValidationError def is_image_file(value): @@ -70,7 +68,6 @@ class AbstracErrorObject(object): class Poll(Document, AbstracErrorObject): collection_name = 'polls' - UniqueNameError = UniqueNameError OPEN = "Abierta" CLOSED = "Cerrada" @@ -1001,8 +998,9 @@ class Group(AbstractObject, ComponentStructure): class Structure(AbstractObject, ComponentStructure): + """Class that handles the structure of a poll. - """ + Example of data_structure: { 'groups': { '0': { @@ -1022,6 +1020,8 @@ class Structure(AbstractObject, ComponentStructure): } } """ + READ_ONLY_MSG = ('No puede modificar la estructura' + ' de una encuesta con resultados.') def __init__(self, data={}, poll=None, *args, **kwargs): super(Structure, self).__init__(poll, *args, **kwargs) @@ -1168,6 +1168,7 @@ class Structure(AbstractObject, ComponentStructure): return data def save(self): + """Save structure to database. Returns structure's ID.""" structure_id = None self.validate() @@ -1177,6 +1178,8 @@ class Structure(AbstractObject, ComponentStructure): # Prepare dbref to poll object if not self.poll: raise ValidationError("Need a parent poll.") + elif self.is_read_only(): + raise ReadOnly(Structure.READ_ONLY_MSG) else: dbref = DBRef(Poll.collection_name, ObjectId(self.poll.id)) _dict.update({'poll': dbref}) @@ -1317,6 +1320,11 @@ class Structure(AbstractObject, ComponentStructure): # TODO: LOG! pass + def is_read_only(self): + """Tells if the structure can be modified.""" + poll = self.poll + return not poll.is_open() or poll.has_result() + class NodePollResult(object): diff --git a/webapp/polls/templates/poll-structure-form.html b/webapp/polls/templates/poll-structure-form.html index 7de54de..0b6ed50 100644 --- a/webapp/polls/templates/poll-structure-form.html +++ b/webapp/polls/templates/poll-structure-form.html @@ -38,14 +38,16 @@ {% render_structure structure %} + {% if not structure.is_read_only %}
+ {% endif %} {% csrf_token %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/webapp/polls/tests/poll_tests.py b/webapp/polls/tests/poll_tests.py index 19c36c1..0b3a168 100644 --- a/webapp/polls/tests/poll_tests.py +++ b/webapp/polls/tests/poll_tests.py @@ -188,7 +188,6 @@ class PollTests(MongoTestCase): def test_clone(self): poll = Poll(data={'name': 'name'}) - poll.status = Poll.CLOSED poll_id = poll.save() poll = Poll.get(poll_id) diff --git a/webapp/polls/tests/result_tests.py b/webapp/polls/tests/result_tests.py index 57ae49a..0936cf0 100644 --- a/webapp/polls/tests/result_tests.py +++ b/webapp/polls/tests/result_tests.py @@ -96,8 +96,7 @@ class PollResultTests(MongoTestCase): poll_data = { "name": "test", - "pollsters": [pollster], - "status": Poll.CLOSED + "pollsters": [pollster] } structure_data = { @@ -325,10 +324,7 @@ class CsvNoRepitePesoParaOpcionesConElMismoTextDescriptivo(MongoTestCase): """ def setUp(self): - poll_data = { - "name": "test", - "status": Poll.CLOSED - } + poll_data = {"name": "test"} question_name = "repite nombre pregunta" structure_data = { @@ -397,10 +393,14 @@ class CsvNoRepitePesoParaOpcionesConElMismoTextDescriptivo(MongoTestCase): poll = Poll(data=poll_data) poll_id = poll.save() - self.poll = Poll.get(poll_id) - structure = Structure(data=structure_data, poll=self.poll) + poll = Poll.get(poll_id) + structure = Structure(data=structure_data, poll=poll) structure.save() + poll.status = Poll.CLOSED + poll_id = poll.save() + self.poll = Poll.get(poll_id) + def test_get_csv_header_with_order(self): poll = self.poll diff --git a/webapp/polls/tests/structure_tests.py b/webapp/polls/tests/structure_tests.py index 7508248..c9fe0d9 100644 --- a/webapp/polls/tests/structure_tests.py +++ b/webapp/polls/tests/structure_tests.py @@ -7,6 +7,7 @@ from mock import Mock from utils.test import (MongoTestCase, mock_in_memory_image, remove_option_images_dir) from polls.tests.poll_tests import MockImageFileInterface +from polls.exceptions import ReadOnly class StructureTests(MongoTestCase): @@ -162,17 +163,6 @@ class StructureTests(MongoTestCase): self.assertEqual(1, len(structure.errors)) - def test_save(self): - - structure = Structure(data=self.data) - - self.assertRaises(structure.ValidationError, structure.save) - - structure = Structure(data=self.data, poll=self.poll) - structure.save() - - self.assertEqual(1, self.db.structures.count()) - def test_get(self): structure = Structure(data=self.data, poll=self.poll) structure_id = structure.save() @@ -321,6 +311,39 @@ class StructureTests(MongoTestCase): self.assertEqual(1, len(structure.get_image_options())) + def test_it_should_respond_to_ready_only_msg(self): + self.assertTrue(hasattr(Structure, 'READ_ONLY_MSG')) + + +class ReadOnlyTest(MongoTestCase): + + def setUp(self): + poll = Poll({"name": "poll name"}) + poll_id = poll.save() + self.poll = poll.get(poll_id) + + def test_it_should_be_read_only_when_poll_has_results(self): + poll = self.poll + structure = Structure(data={}, poll=poll) + self.assertFalse(poll.has_result()) + self.assertFalse(structure.is_read_only()) + + poll.has_result = Mock(return_value=True) + self.assertTrue(poll.has_result()) + self.assertTrue(structure.is_read_only()) + + def test_read_only_when_poll_is_closed_and_has_not_results(self): + poll = self.poll + poll.status = Poll.CLOSED + poll_id = poll.save() + poll = poll.get(poll_id) + + structure = Structure(data={}, poll=poll) + self.assertFalse(poll.has_result()) + self.assertFalse(poll.is_open()) + + self.assertTrue(structure.is_read_only()) + class UploadOptionImagesTest(MongoTestCase): @@ -400,3 +423,41 @@ class UploadOptionImagesTest(MongoTestCase): def tearDown(self): remove_option_images_dir() + + +class SaveStructureTest(MongoTestCase): + + def setUp(self): + poll = Poll(data={'name': 'name'}) + poll_id = poll.save() + + self.poll = Poll.get(id=poll_id) + + self.data = { + 'poll_id': 'POLL_ID', + 'groups': { + '0': { + 'name': 'group_0', + 'fields': { + '0': { + 'widget_type': Field.TextInput, + 'name': 'field_0_0' + } + } + } + } + } + + def test_save(self): + structure = Structure(data=self.data) + self.assertRaises(structure.ValidationError, structure.save) + + structure = Structure(data=self.data, poll=self.poll) + structure.save() + self.assertEqual(1, self.db.structures.count()) + + def test_it_should_not_save_if_is_read_only(self): + poll = self.poll + structure = Structure(data=self.data, poll=poll) + structure.is_read_only = Mock(return_value=True) + self.assertRaises(ReadOnly, structure.save) diff --git a/webapp/polls/views.py b/webapp/polls/views.py index db297ac..c19eac1 100644 --- a/webapp/polls/views.py +++ b/webapp/polls/views.py @@ -168,7 +168,8 @@ class PollListView(ListView): ), }, 'action_structure_builder': { - 'disabled': "disabled" if not poll.is_open() else "", + 'disabled': ("disabled" if + poll.structure.is_read_only() else ""), 'url': reverse( 'sociologist:structure.builder', kwargs={'poll_id': str(poll.id)} @@ -199,9 +200,13 @@ class StructureFormView(TemplateView): def get(self, request, *args, **kwargs): context = self.get_context_data() + structure = self.poll.structure - context.update({'structure': self.poll.structure}) + read_only = Structure.READ_ONLY_MSG + msg = read_only if structure.is_read_only() else '' + messages.add_message(self.request, messages.WARNING, msg) + context.update({'structure': structure}) return self.render_to_response(context) def get_context_data(self, **kwargs): diff --git a/webapp/webapp/features/cannot_modify_poll_structure.feature b/webapp/webapp/features/cannot_modify_poll_structure.feature new file mode 100644 index 0000000..a424bdd --- /dev/null +++ b/webapp/webapp/features/cannot_modify_poll_structure.feature @@ -0,0 +1,12 @@ +Feature: Researcher can't modify poll's structure + As a Researcher + I want to not be able to modify a poll's structure + when a poll has results + So that there is no chance to get mixed results from a poll + + Scenario: a poll with results + Given I am logged in as "Researcher" + And "poll" exists + And "poll" has "simple.poll_result" + When I visit the "Modificar estructura" page for "poll" poll + Then I should see a message that says "No puede modificar" diff --git a/webapp/webapp/features/terrain.py b/webapp/webapp/features/terrain.py index 632ac37..ed52bf6 100644 --- a/webapp/webapp/features/terrain.py +++ b/webapp/webapp/features/terrain.py @@ -36,9 +36,11 @@ def load_fixture(fixture_name): @before.each_feature def before_each_feature(feature): + """This is the main lettuce hook "@before.each_feature".""" drop_mongo() drop_sqlite() - if feature.name == "Researcher adds images to options": + if feature.name in ("Researcher adds images to options", + "Researcher can't modify poll's structure"): load_fixture("generic_researcher") -- cgit v0.9.1