[IMP] Deadlines on invitations

[REF] Survey auth refactoring

[REF/IMP] Pagination system refactoring + add go back function

[FIX] Small glitches

[REF] Code refactoring

bzr revid: rim@openerp.com-20131212110435-he9h9dkeyb722l1a
This commit is contained in:
Richard Mathot (OpenERP)
2013-12-12 12:04:35 +01:00
parent 186df9b959
commit 0d55f476f0
3 changed files with 173 additions and 118 deletions
+91 -86
View File
@@ -23,16 +23,56 @@ from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp import SUPERUSER_ID
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DTF
from datetime import datetime
import werkzeug
import json
import logging
_logger = logging.getLogger(__name__)
class WebsiteSurvey(http.Controller):
## HELPER METHODS ##
def _check_bad_cases(self, cr, uid, request, survey_obj, survey, user_input_obj, context=None):
# In case of bad survey, redirect to surveys list
if survey_obj.exists(cr, SUPERUSER_ID, survey.id, context=context) == []:
return werkzeug.utils.redirect("/survey/")
# In case of auth required, block public user
if survey.auth_required and uid == request.registry['website'].get_public_user(cr, uid, context):
return request.website.render("website.401")
# In case of non open surveys
if survey.state != 'open':
return request.website.render("survey.notopen")
# If enough surveys completed
if survey.user_input_limit > 0:
completed = user_input_obj.search(cr, uid, [('state', '=', 'done')], count=True)
if completed >= survey.user_input_limit:
return request.website.render("survey.notopen")
# Everything seems to be ok
return None
def _check_deadline(self, cr, uid, user_input, context=None):
'''Prevent opening of the survey if the deadline has turned out
! This will NOT disallow access to users who have already partially filled the survey !'''
if user_input.deadline:
dt_deadline = datetime.strptime(user_input.deadline, DTF)
dt_now = datetime.now()
if dt_now > dt_deadline: # survey is not open anymore
return request.website.render("survey.notopen")
return None
## ROUTES HANDLERS ##
# Survey list
@website.route(['/survey/',
'/survey/list/'],
@@ -56,23 +96,10 @@ class WebsiteSurvey(http.Controller):
survey_obj = request.registry['survey.survey']
user_input_obj = request.registry['survey.user_input']
# In case of bad survey, redirect to surveys list
if survey_obj.exists(cr, uid, survey.id, context=context) == []:
return werkzeug.utils.redirect("/survey/")
# In case of auth required, block public user
if survey.auth_required and uid == request.registry['website'].get_public_user(request.cr, SUPERUSER_ID, request.context):
return request.website.render("website.401")
# In case of non open surveys
if survey.state != 'open':
return request.website.render("survey.notopen")
# If enough surveys completed
if survey.user_input_limit > 0:
completed = user_input_obj.search(cr, uid, [('state', '=', 'done')], count=True)
if completed >= survey.user_input_limit:
return request.website.render("survey.notopen")
# Controls if the survey can be displayed
errpage = self._check_bad_cases(cr, uid, request, survey_obj, survey, user_input_obj, context=context)
if errpage:
return errpage
# Manual surveying
if not token:
@@ -89,73 +116,50 @@ class WebsiteSurvey(http.Controller):
else:
user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
# Prevent opening of the survey if the deadline has turned out
# ! This will NOT disallow access to users who have already partially filled the survey !
# if user_input.deadline > fields.date.now() and user_input.state == 'new':
# return request.website.render("survey.notopen")
# TODO check if this is ok
# Do not open expired survey
errpage = self._check_deadline(cr, uid, user_input, context=context)
if errpage:
return errpage
# Select the right page
if user_input.state == 'new': # Intro page
data = {'survey': survey, 'page': None, 'token': user_input.token}
return request.website.render('survey.survey_init', data)
else:
return request.redirect('/survey/fill/%s/%s' % (survey.id, user_input.token))
# Survey displaying
@website.route(['/survey/fill/<model("survey.survey"):survey>/<string:token>'],
@website.route(['/survey/fill/<model("survey.survey"):survey>/<string:token>',
'/survey/fill/<model("survey.survey"):survey>/<string:token>/<string:prev>'],
type='http', auth='public', multilang=True)
def fill_survey(self, survey, token, **post):
def fill_survey(self, survey, token, prev=None, **post):
'''Display and validates a survey'''
cr, uid, context = request.cr, request.uid, request.context
survey_obj = request.registry['survey.survey']
user_input_obj = request.registry['survey.user_input']
# In case of bad survey, redirect to surveys list
if survey_obj.exists(cr, uid, survey.id, context=context) == []:
return werkzeug.utils.redirect("/survey/")
# Controls if the survey can be displayed
errpage = self._check_bad_cases(cr, uid, request, survey_obj, survey, user_input_obj, context=context)
if errpage:
return errpage
# In case of auth required, block public user
if survey.auth_required and uid == request.registry['website'].get_public_user(request.cr, SUPERUSER_ID, request.context):
return request.website.render("website.401")
# In case of non open surveys
if survey.state != 'open':
return request.website.render("survey.notopen")
# If enough surveys completed
if survey.user_input_limit > 0:
completed = user_input_obj.search(cr, uid, [('state', '=', 'done')], count=True)
if completed >= survey.user_input_limit:
return request.website.render("survey.notopen")
# Manual surveying
if not token:
if survey.visible_to_user:
user_input_id = user_input_obj.create(cr, uid, {'survey_id': survey.id})
user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
else: # An user cannot open hidden surveys without token
return request.website.render("website.403")
# Load the user_input
try:
user_input_id = user_input_obj.search(cr, uid, [('token', '=', token)])[0]
except IndexError: # Invalid token
return request.website.render("website.403")
else:
try:
user_input_id = user_input_obj.search(cr, uid, [('token', '=', token)])[0]
except IndexError: # Invalid token
return request.website.render("website.403")
else:
user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
user_input = user_input_obj.browse(cr, uid, [user_input_id], context=context)[0]
# Prevent opening of the survey if the deadline has turned out
# ! This will NOT disallow access to users who have already partially filled the survey !
# if user_input.deadline > fields.date.now() and user_input.state == 'new':
# return request.website.render("survey.notopen")
# TODO check if this is ok
# Do not display expired survey (even if some pages have already been
# displayed -- There's a time for everything!)
errpage = self._check_deadline(cr, uid, user_input, context=context)
if errpage:
return errpage
# Select the right page
if user_input.state == 'new': # First page
page, page_nr, last = self.find_next_page(survey, user_input)
page, page_nr, last = survey_obj.next_page(cr, uid, user_input, 0, go_back=False, context=context)
data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
if last:
data.update({'last': True})
@@ -163,39 +167,33 @@ class WebsiteSurvey(http.Controller):
elif user_input.state == 'done': # Display success message
return request.website.render('survey.finished', {'survey': survey})
elif user_input.state == 'skip':
page, page_nr, last = self.find_next_page(survey, user_input)
flag = (True if prev and prev == 'prev' else False)
page, page_nr, last = survey_obj.next_page(cr, uid, user_input, user_input.last_displayed_page_id.id, go_back=flag, context=context)
data = {'survey': survey, 'page': page, 'page_nr': page_nr, 'token': user_input.token}
if last:
data.update({'last': True})
return request.website.render('survey.survey', data)
else:
return request.website.render("website.403")
# @website.route(['/survey/prefill/<model("survey.survey"):survey>'], type='json', auth='public', multilang=True):
# AJAX prefilling of a survey
# @website.route(['/survey/prefill/<model("survey.survey"):survey>'],
# type='json', auth='public', multilang=True):
# def prefill(self, survey, **post):
# AJAX validation of some questions
# @website.route(['/survey/validate/<model("survey.survey"):survey>'],
# type='http', auth='public', multilang=True)
# def validate(self, survey, **post):
# _logger.debug('Incoming data: %s', post)
# page_id = int(post['page_id'])
# cr, uid, context = request.cr, request.uid, request.context
# questions_obj = request.registry['survey.question']
# questions_ids = questions_obj.search(cr, uid, [('page_id', '=', page_id)], context=context)
# questions = questions_obj.browse(cr, uid, questions_ids, context=context)
# errors = {}
# for question in questions:
# answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
# errors.update(self.validate_question(question, post, answer_tag))
# ret = {}
# if (len(errors) != 0):
# ret['errors'] = errors
# return json.dumps(ret)
# AJAX submission of a page
@website.route(['/survey/submit/<model("survey.survey"):survey>'],
type='http', auth='public', multilang=True)
def submit(self, survey, **post):
_logger.debug('Incoming data: %s', post)
page_id = int(post['page_id'])
cr, uid, context = request.cr, request.uid, request.context
survey_obj = request.registry['survey.survey']
questions_obj = request.registry['survey.question']
questions_ids = questions_obj.search(cr, uid, [('page_id', '=', page_id)], context=context)
questions = questions_obj.browse(cr, uid, questions_ids, context=context)
@@ -213,6 +211,7 @@ class WebsiteSurvey(http.Controller):
else:
# Store answers into database
user_input_obj = request.registry['survey.user_input']
user_input_line_obj = request.registry['survey.user_input_line']
try:
user_input_id = user_input_obj.search(cr, uid, [('token', '=', post['token'])], context=context)[0]
@@ -222,12 +221,18 @@ class WebsiteSurvey(http.Controller):
answer_tag = "%s_%s_%s" % (survey.id, page_id, question.id)
user_input_line_obj.save_lines(cr, uid, user_input_id, question, post, answer_tag, context=context)
# page, _, _ = self.find_next_page(survey, user_input_obj.browse(cr, uid, [user_input_id], context=context))
# if page:
# user_input_obj.write(cr, uid, user_input_id, {'state': 'skip'})
# else:
user_input_obj.write(cr, uid, user_input_id, {'state': 'done'})
user_input = user_input_obj.browse(cr, uid, user_input_id, context=context)
go_back = post['button_submit'] == 'previous'
next_page, _, last = survey_obj.next_page(cr, uid, user_input, page_id, go_back=go_back, context=context)
vals = {'last_displayed_page_id': page_id}
if next_page is None and not go_back:
vals.update({'state': 'done'})
else:
vals.update({'state': 'skip'})
user_input_obj.write(cr, uid, user_input_id, vals, context=context)
ret['redirect'] = '/survey/fill/%s/%s' % (survey.id, post['token'])
if go_back:
ret['redirect'] += '/prev'
return json.dumps(ret)
# Printing routes
@@ -256,7 +261,7 @@ class WebsiteSurvey(http.Controller):
page_nr = page_nr + 1
else:
nextpage = page
if page_nr == len(survey.page_ids):
if page_nr == len(survey.page_ids) - 1:
last = True
return nextpage, page_nr, last
+79 -29
View File
@@ -150,6 +150,45 @@ class survey_survey(osv.osv):
return super(survey_survey, self).copy(cr, uid, ids, vals,
context=context)
def next_page(self, cr, uid, user_input, page_id, go_back=False, context=None):
'''The next page to display to the user, knowing that page_id is the id
of the last displayed page.
If page_id == 0, it will always return the first page of the survey.
If all the pages have been displayed and go_back == False, it will
return None
If go_back == True, it will return the *previous* page instead of the
next page.
.. note::
It is assumed here that a careful user will not try to set go_back
to True if she knows that the page to display is the first one!
(doing this will probably cause a giant worm to eat her house)'''
survey = user_input.survey_id
pages = list(enumerate(survey.page_ids))
# First page
if page_id == 0:
return (pages[0][1], 0, len(pages) == 1)
current_page_index = pages.index((filter(lambda p: p[1].id == page_id, pages))[0])
# All the pages have been displayed
if current_page_index == len(pages) - 1 and not go_back:
return (None, -1, False)
# Let's get back, baby!
elif go_back and survey.users_can_go_back:
return (pages[current_page_index - 1][1], current_page_index - 1, False)
else:
# This will show the last page
if current_page_index == len(pages) - 2:
return (pages[current_page_index + 1][1], current_page_index + 1, True)
# This will show a regular page
else:
return (pages[current_page_index + 1][1], current_page_index + 1, False)
def action_print_survey_questions(self, cr, uid, ids, context=None):
''' Generates a printable view of an empty survey '''
pass
@@ -287,6 +326,9 @@ class survey_page(osv.osv):
help="An introductory text to your page", translate=True,
oldname="note"),
}
_defaults = {
'sequence': 10
}
# Public methods #
@@ -408,6 +450,7 @@ class survey_question(osv.osv):
}
_defaults = {
'page_id': lambda s, cr, uid, c: c.get('page_id'),
'sequence': 10,
'type': 'free_text',
'matrix_subtype': 'simple',
'column_nb': '12',
@@ -661,7 +704,7 @@ class survey_question(osv.osv):
if question.constr_mandatory:
lines_number = len(question.labels_ids_2)
answer_candidates = dict_keys_startswith(post, answer_tag)
comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, question.comment_children_ids[0].id)), None)
#comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, question.comment_children_ids[0].id)), None)
# Number of lines that have been answered
if question.matrix_subtype == 'simple':
answer_number = len(answer_candidates)
@@ -701,6 +744,9 @@ class survey_label(osv.osv):
'value': fields.char("Suggested value", translate=True,
required=True)
}
defaults = {
'sequence': 10
}
class survey_user_input(osv.osv):
@@ -714,8 +760,9 @@ class survey_user_input(osv.osv):
readonly=1, ondelete='restrict'),
'date_create': fields.datetime('Creation Date', required=True,
readonly=1),
'deadline': fields.date("Deadline",
help="Date by which the person can take part to the survey",
'deadline': fields.datetime("Deadline",
help="Date by which the person can open the survey and submit answers.\
Warning: ",
oldname="date_deadline"),
'type': fields.selection([('manually', 'Manually'), ('link', 'Link')],
'Answer Type', required=1, readonly=1,
@@ -732,6 +779,9 @@ class survey_user_input(osv.osv):
'partner_id': fields.many2one('res.partner', 'Partner', readonly=1),
'email': fields.char("E-mail", readonly=1),
# Displaying data
'last_displayed_page_id': fields.many2one('survey.page',
'Last displayed page'),
# The answers !
'user_input_line_ids': fields.one2many('survey.user_input_line',
'user_input_id', 'Answers'),
@@ -872,7 +922,7 @@ class survey_user_input_line(osv.osv):
'survey_id': fields.related('user_input_id', 'survey_id',
type="many2one", relation="survey.survey",
string='Survey'),
'date_create': fields.datetime('Create Date', required=1), # drop
'date_create': fields.datetime('Create Date', required=1),
'skipped': fields.boolean('Skipped'),
'answer_type': fields.selection([('text', 'Text'),
('number', 'Number'),
@@ -888,7 +938,7 @@ class survey_user_input_line(osv.osv):
}
_defaults = {
'skipped': False,
'date_create': fields.datetime.now
'date_create': fields.datetime.now()
}
def save_lines(self, cr, uid, user_input_id, question, post, answer_tag,
@@ -912,7 +962,7 @@ class survey_user_input_line(osv.osv):
'page_id': question.page_id.id,
'survey_id': question.survey_id.id,
}
if answer_tag in post:
if answer_tag in post and post[answer_tag].strip() != '':
vals.update({'answer_type': 'free_text', 'value_free_text': post[answer_tag]})
else:
vals.update({'skipped': True})
@@ -933,7 +983,7 @@ class survey_user_input_line(osv.osv):
'page_id': question.page_id.id,
'survey_id': question.survey_id.id,
}
if answer_tag in post:
if answer_tag in post and post[answer_tag].strip() != '':
vals.update({'answer_type': 'text', 'value_text': post[answer_tag]})
else:
vals.update({'skipped': True})
@@ -954,10 +1004,10 @@ class survey_user_input_line(osv.osv):
'page_id': question.page_id.id,
'survey_id': question.survey_id.id,
}
if answer_tag in post:
if answer_tag in post and post[answer_tag].strip() != '':
vals.update({'answer_type': 'number', 'value_number': float(post[answer_tag])})
else:
vals.update({'skipped': True})
vals.update({'skipped': True, 'answer_type': None})
old_uil = self.search(cr, uid, [('user_input_id', '=', user_input_id),
('survey_id', '=', question.survey_id.id),
('question_id', '=', question.id)],
@@ -989,26 +1039,26 @@ class survey_user_input_line(osv.osv):
self.create(cr, uid, vals, context=context)
return True
def save_line_simple_choice(self, cr, uid, user_input_id, question, post, answer_tag, context=None):
vals = {
'user_input_id': user_input_id,
'question_id': question.id,
'page_id': question.page_id.id,
'survey_id': question.survey_id.id,
}
if answer_tag in post:
vals.update({'answer_type': 'date', 'value_date': post[answer_tag]})
else:
vals.update({'skipped': True})
old_uil = self.search(cr, uid, [('user_input_id', '=', user_input_id),
('survey_id', '=', question.survey_id.id),
('question_id', '=', question.id)],
context=context)
if old_uil:
self.write(cr, uid, old_uil[0], vals, context=context)
else:
self.create(cr, uid, vals, context=context)
return True
# def save_line_simple_choice(self, cr, uid, user_input_id, question, post, answer_tag, context=None):
# vals = {
# 'user_input_id': user_input_id,
# 'question_id': question.id,
# 'page_id': question.page_id.id,
# 'survey_id': question.survey_id.id,
# }
# if answer_tag in post:
# vals.update({'answer_type': 'date', 'value_date': post[answer_tag]})
# else:
# vals.update({'skipped': True})
# old_uil = self.search(cr, uid, [('user_input_id', '=', user_input_id),
# ('survey_id', '=', question.survey_id.id),
# ('question_id', '=', question.id)],
# context=context)
# if old_uil:
# self.write(cr, uid, old_uil[0], vals, context=context)
# else:
# self.create(cr, uid, vals, context=context)
# return True
def dict_keys_startswith(dictionary, string):
+3 -3
View File
@@ -115,9 +115,9 @@
</div>
</t>
<div class="text-center mt16 mb16">
<button t-if="survey.users_can_go_back and page_nr > 0" type="submit" class="btn btn-default">Previous page</button>
<button t-if="not last" type="submit" class="btn btn-primary">Next page</button>
<button type="submit" t-if="last" class="btn btn-primary">Submit survey</button>
<button t-if="survey.users_can_go_back and page_nr > 0" type="submit" class="btn btn-default" name="button_submit" value="previous">Previous page</button>
<button t-if="not last" type="submit" class="btn btn-primary" name="button_submit" value="next">Next page</button>
<button t-if="last" type="submit" class="btn btn-primary" name="button_submit" value="finish">Submit survey</button>
</div>
</form>