Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/webapp/webapp
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/webapp')
-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
18 files changed, 486 insertions, 12 deletions
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"