app/soc/views/models/survey.py
changeset 2455 a023b71ce125
parent 2448 325e08ee9d25
child 2463 05af53239799
equal deleted inserted replaced
2454:849956450d69 2455:a023b71ce125
    20 __authors__ = [
    20 __authors__ = [
    21   '"Daniel Diniz" <ajaksu@gmail.com>',
    21   '"Daniel Diniz" <ajaksu@gmail.com>',
    22   '"James Levy" <jamesalexanderlevy@gmail.com>',
    22   '"James Levy" <jamesalexanderlevy@gmail.com>',
    23   ]
    23   ]
    24 
    24 
    25 
       
    26 import csv
    25 import csv
    27 import datetime
    26 import datetime
    28 import re
    27 import re
    29 import StringIO
    28 import StringIO
    30 import string
    29 import string
    31 
       
    32 from google.appengine.ext import db
       
    33 
       
    34 from django import forms
    30 from django import forms
    35 from django import http
    31 from django import http
    36 from django.utils import simplejson
    32 from django.utils import simplejson
    37 
    33 
       
    34 from google.appengine.ext import db
       
    35 
    38 from soc.cache import home
    36 from soc.cache import home
    39 from soc.logic import cleaning
    37 from soc.logic import cleaning
    40 from soc.logic import dicts
    38 from soc.logic import dicts
       
    39 from soc.logic.models.survey import logic as survey_logic
       
    40 from soc.logic.models.survey import results_logic
    41 from soc.logic.models.survey import GRADES
    41 from soc.logic.models.survey import GRADES
    42 from soc.logic.models.survey import logic as survey_logic
       
    43 from soc.logic.models.survey import project_logic
       
    44 from soc.logic.models.survey import grading_logic
       
    45 from soc.logic.models.survey_record import logic as results_logic
       
    46 from soc.logic.models.survey_record import updateSurveyRecord
       
    47 from soc.logic.models.user import logic as user_logic
    42 from soc.logic.models.user import logic as user_logic
    48 from soc.models.survey import Survey
    43 from soc.models.survey import Survey
    49 from soc.models.survey_record import SurveyRecord
    44 from soc.models.survey_record import SurveyRecord
    50 from soc.models.user import User
    45 from soc.models.user import User
    51 from soc.views import out_of_band
    46 from soc.views import out_of_band
   100     # TODO: read/write access needs to match survey
    95     # TODO: read/write access needs to match survey
   101     # TODO: usage requirements
    96     # TODO: usage requirements
   102 
    97 
   103     rights = access.Checker(params)
    98     rights = access.Checker(params)
   104     rights['any_access'] = ['allow']
    99     rights['any_access'] = ['allow']
   105     rights['show'] = [('checkIsSurveyReadable', survey_logic)]
   100     rights['show'] = ['checkIsSurveyReadable']
   106     rights['create'] = ['checkIsUser']
   101     rights['create'] = ['checkIsUser']
   107     rights['edit'] = [('checkIsSurveyWritable', survey_logic)]
   102     rights['edit'] = ['checkIsSurveyWritable']
   108     rights['delete'] = [('checkIsSurveyWritable', survey_logic)]
   103     rights['delete'] = ['checkIsSurveyWritable']
   109     rights['list'] = ['checkDocumentList']
   104     rights['list'] = ['checkDocumentList']
   110     rights['pick'] = ['checkDocumentPick']
   105     rights['pick'] = ['checkDocumentPick']
       
   106     rights['grade'] = ['checkIsSurveyGradable']
   111 
   107 
   112     new_params = {}
   108     new_params = {}
   113     # TODO(ajaksu) pass logic in a way views can use them
       
   114     new_params['logic'] = survey_logic
   109     new_params['logic'] = survey_logic
   115     new_params['rights'] = rights
   110     new_params['rights'] = rights
   116 
   111 
   117     new_params['name'] = "Survey"
   112     new_params['name'] = "Survey"
   118     new_params['pickable'] = True
   113     new_params['pickable'] = True
   125          'soc.views.models.%(module_name)s.json',
   120          'soc.views.models.%(module_name)s.json',
   126          'Export %(name)s as JSON'),
   121          'Export %(name)s as JSON'),
   127         (r'^%(url_name)s/(?P<access_type>results)/%(scope)s$',
   122         (r'^%(url_name)s/(?P<access_type>results)/%(scope)s$',
   128          'soc.views.models.%(module_name)s.results',
   123          'soc.views.models.%(module_name)s.results',
   129          'View survey results for %(name)s'),
   124          'View survey results for %(name)s'),
       
   125         (r'^%(url_name)s/(?P<access_type>show)/user/(?P<link_id>)\w+$',
       
   126          'soc.views.models.%(module_name)s.results',
       
   127          'View survey results for user'),
   130         ]
   128         ]
   131 
   129 
   132     new_params['export_content_type'] = 'text/text'
   130     new_params['export_content_type'] = 'text/text'
   133     new_params['export_extension'] = '.csv'
   131     new_params['export_extension'] = '.csv'
   134     new_params['export_function'] = to_csv
   132     new_params['export_function'] = to_csv
   196                                   params=params, filter=kwargs)
   194                                   params=params, filter=kwargs)
   197 
   195 
   198   def _public(self, request, entity, context):
   196   def _public(self, request, entity, context):
   199     """Survey taking and result display handler.
   197     """Survey taking and result display handler.
   200 
   198 
       
   199 
   201     Args:
   200     Args:
   202       request: the django request object
   201       request: the django request object
   203       entity: the entity to make public
   202       entity: the entity to make public
   204       context: the context object
   203       context: the context object
   205 
   204 
       
   205 
   206     -- Taking Survey Pages Are Not 'Public' --
   206     -- Taking Survey Pages Are Not 'Public' --
   207 
   207 
   208     For surveys, the "public" page is actually the access-protected
   208     For surveys, the "public" page is actually the access-protected
   209     survey-taking page.
   209     survey-taking page.
   210 
   210 
   219     The project selected while taking a survey determines how this_user
   219     The project selected while taking a survey determines how this_user
   220     SurveyRecord will be linked to other SurveyRecords.
   220     SurveyRecord will be linked to other SurveyRecords.
   221 
   221 
   222     --- Deadlines ---
   222     --- Deadlines ---
   223 
   223 
   224     A deadline can also be used as a conditional for updating values,
   224     A survey_end can also be used as a conditional for updating values,
   225     we have a special read_only UI and a check on the POST handler for this.
   225     we have a special read_only UI and a check on the POST handler for this.
   226     Passing read_only=True here allows one to fetch the read_only view.
   226     Passing read_only=True here allows one to fetch the read_only view.
   227     """
   227     """
   228 
   228 
   229     # check ACL
   229     # check ACL
   269          # not submitting completed survey OR we're ignoring late submission
   269          # not submitting completed survey OR we're ignoring late submission
   270         pass
   270         pass
   271       else:
   271       else:
   272         # save/update the submitted survey
   272         # save/update the submitted survey
   273         context['notice'] = "Survey Submission Saved"
   273         context['notice'] = "Survey Submission Saved"
   274         survey_record = updateSurveyRecord(user, survey, survey_record,
   274         survey_record = survey_logic.updateSurveyRecord(user, survey,
   275                                            request.POST)
   275         survey_record, request.POST)
   276     survey_content = survey.survey_content
   276     survey_content = survey.survey_content
   277 
   277 
   278     if not survey_record and read_only:
   278     if not survey_record and read_only:
   279       # no recorded answers, we're either past deadline or want to see answers
   279       # no recorded answers, we're either past survey_end or want to see answers
   280       is_same_user = user.key() == user_logic.getForCurrentAccount().key()
   280       is_same_user = user.key() == user_logic.getForCurrentAccount().key()
   281 
   281 
   282       if not can_write or not is_same_user:
   282       if not can_write or not is_same_user:
   283         # If user who can edit looks at her own taking page, show the default
   283         # If user who can edit looks at her own taking page, show the default
   284         # form as readonly. Otherwise, below, show nothing.
   284         # form as readonly. Otherwise, below, show nothing.
   307     context['read_only'] = read_only
   307     context['read_only'] = read_only
   308     context['project'] = project
   308     context['project'] = project
   309     return True
   309     return True
   310 
   310 
   311   def getStatus(self, request, context, user, survey):
   311   def getStatus(self, request, context, user, survey):
   312     """Determine if the survey is available for taking, check user rights.
   312     """Determine if we're past survey_end or before survey_start, check user rights.
   313     """
   313     """
   314 
   314 
   315     read_only = (context.get("read_only", False) or
   315     read_only = (context.get("read_only", False) or
   316                  request.GET.get("read_only", False) or
   316                  request.GET.get("read_only", False) or
   317                  request.POST.get("read_only", False)
   317                  request.POST.get("read_only", False)
   318                  )
   318                  )
   319     now = datetime.datetime.now()
   319     now = datetime.datetime.now()
   320 
   320 
   321     # check survey end date, see check for start below
   321     # check survey_end, see check for survey_start below
   322     if survey.survey_end and now > survey.survey_end:
   322     if survey.survey_end and now > survey.survey_end:
   323       # are we already passed the deadline?
   323       # are we already passed the survey_end?
   324       context["notice"] = "The Deadline For This Survey Has Passed"
   324       context["notice"] = "The Deadline For This Survey Has Passed"
   325       read_only = True
   325       read_only = True
   326 
   326 
   327     # check if user can edit this survey
   327     # check if user can edit this survey
   328     params = dict(prefix=survey.prefix, scope_path=survey.scope_path)
   328     params = dict(prefix=survey.prefix, scope_path=survey.scope_path)
   329     checker = access.rights_logic.Checker(survey.prefix)
   329     checker = access.rights_logic.Checker(survey.prefix)
   330     roles = checker.getMembership(survey.write_access)
   330     roles = checker.getMembership(survey.write_access)
   331     rights = self._params['rights']
   331     rights = self._params['rights']
   332     can_write = access.Checker.hasMembership(rights, roles, params)
   332     can_write = access.Checker.hasMembership(rights, roles, params)
   333 
   333 
       
   334 
   334     not_ready = False
   335     not_ready = False
   335     # check if we're past the start date
   336     # check if we're past the survey_start date
   336     if survey.survey_start and now < survey.survey_start:
   337     if survey.survey_start and now < survey.survey_start:
   337       not_ready = True
   338       not_ready = True
   338 
   339 
   339       # only users that can edit a survey should see it before it can be taken
   340       # only users that can edit a survey should see it before survey_start
   340       if not can_write:
   341       if not can_write:
   341         context["notice"] = "There is no such survey available."
   342         context["notice"] = "There is no such survey available."
   342         return False
   343         return False
   343       else:
   344       else:
   344         context["notice"] = "This survey is not open for taking yet."
   345         context["notice"] = "This survey is not open for taking yet."
   350     """Set help_text and status for template use.
   351     """Set help_text and status for template use.
   351     """
   352     """
   352 
   353 
   353     if not read_only:
   354     if not read_only:
   354       if not survey.survey_end:
   355       if not survey.survey_end:
   355         deadline_text = ""
   356         survey_end_text = ""
   356       else:
   357       else:
   357         deadline_text = " by " + str(
   358         survey_end_text = " by " + str(
   358       survey.survey_end.strftime("%A, %d. %B %Y %I:%M%p"))
   359       survey.survey_end.strftime("%A, %d. %B %Y %I:%M%p"))
   359 
   360 
   360       if survey_record:
   361       if survey_record:
   361         help_text = "Edit and re-submit this survey" + deadline_text + "."
   362         help_text = "Edit and re-submit this survey" + survey_end_text + "."
   362         status = "edit"
   363         status = "edit"
   363       else:
   364       else:
   364         help_text = "Please complete this survey" + deadline_text + "."
   365         help_text = "Please complete this survey" + survey_end_text + "."
   365         status = "create"
   366         status = "create"
   366 
   367 
   367     else:
   368     else:
   368       help_text = "Read-only view."
   369       help_text = "Read-only view."
   369       status = "view"
   370       status = "view"
   638                 survey_h=entity.survey_content)
   639                 survey_h=entity.survey_content)
   639     context.update(local)
   640     context.update(local)
   640 
   641 
   641     params['edit_form'] = HelperForm(params['edit_form'])
   642     params['edit_form'] = HelperForm(params['edit_form'])
   642     if entity.survey_end and datetime.datetime.now() > entity.survey_end:
   643     if entity.survey_end and datetime.datetime.now() > entity.survey_end:
   643       # are we already passed the survey end date?
   644       # are we already passed the survey_end?
   644       context["passed_deadline"] = True
   645       context["passed_survey_end"] = True
   645 
   646 
   646     return super(View, self).editGet(request, entity, context, params=params)
   647     return super(View, self).editGet(request, entity, context, params=params)
   647 
   648 
   648   def getMenusForScope(self, entity, params):
   649   def getMenusForScope(self, entity, params):
   649     """List featured surveys iff after they are availble to be taken.
   650     """List featured surveys if after the survey_start date and before survey_end.
   650     """
   651     """
   651 
   652 
   652     # only list surveys for registered users
   653     # only list surveys for registered users
   653     user = user_logic.getForCurrentAccount()
   654     user = user_logic.getForCurrentAccount()
   654     if not user:
   655     if not user:
   684 
   685 
   685         # cache ACL for a given entity.read_access
   686         # cache ACL for a given entity.read_access
   686         survey_rights[entity.read_access] = can_read
   687         survey_rights[entity.read_access] = can_read
   687 
   688 
   688         if not can_read:
   689         if not can_read:
   689           continue
   690           pass#continue
   690 
   691 
   691       elif not survey_rights[entity.read_access]:
   692       elif not survey_rights[entity.read_access]:
   692         continue
   693         pass#continue
   693 
   694 
   694       # omit if either before start or after end
   695       # omit if either before survey_start or after survey_end
   695       if entity.survey_start and entity.survey_start > now:
   696       if entity.survey_start and entity.survey_start > now:
   696         continue
   697         pass#continue
   697 
   698 
   698       if entity.survey_end and entity.survey_end < now:
   699       if entity.survey_end and entity.survey_end < now:
   699         continue
   700         pass#continue
   700 
   701 
       
   702       taken_status = ""
       
   703       taken_status = "(new)"
   701       #TODO only if a document is readable it might be added
   704       #TODO only if a document is readable it might be added
   702       submenu = (redirects.getPublicRedirect(entity, self._params),
   705       submenu = (redirects.getPublicRedirect(entity, self._params),
   703                  entity.short_name, 'show')
   706       'Survey ' +  taken_status + ': ' + entity.short_name,
       
   707       'show')
   704 
   708 
   705       submenus.append(submenu)
   709       submenus.append(submenu)
   706     return submenus
   710     return submenus
   707 
   711 
   708   # TODO the following two methods should move to GradingProjectSurvey
       
   709   def activate(self, request, **kwargs):
   712   def activate(self, request, **kwargs):
   710     """This is a hack to support the 'Enable grades' button.
   713     """This is a hack to support the 'Enable grades' button.
   711     """
   714     """
   712     self.activateGrades(request)
   715     self.activateGrades(request)
   713     redirect_path = request.path.replace('/activate/', '/edit/') + '?activate=1'
   716     redirect_path = request.path.replace('/activate/', '/edit/') + '?activate=1'
   727   def viewResults(self, request, access_type, page_name=None,
   730   def viewResults(self, request, access_type, page_name=None,
   728                   params=None, **kwargs):
   731                   params=None, **kwargs):
   729     """View for SurveyRecord and SurveyRecordGroup.
   732     """View for SurveyRecord and SurveyRecordGroup.
   730     """
   733     """
   731 
   734 
   732     entity, context = self.getContextEntity(request, page_name, params, kwargs)
       
   733 
       
   734     if context is None:
       
   735       # user cannot see this page, return error response
       
   736       return entity
       
   737 
       
   738     can_write = False
       
   739     rights = self._params['rights']
       
   740     try:
       
   741       rights.checkIsSurveyWritable({'key_name': entity.key().name(),
       
   742                                     'prefix': entity.prefix,
       
   743                                     'scope_path': entity.scope_path,
       
   744                                     'link_id': entity.link_id,},
       
   745                                    'key_name')
       
   746       can_write = True
       
   747     except out_of_band.AccessViolation:
       
   748       pass
       
   749 
       
   750     user = user_logic.getForCurrentAccount()
   735     user = user_logic.getForCurrentAccount()
   751 
   736 
   752     filter = self._params.get('filter') or {}
   737     # TODO(ajaksu) use the named parameter link_id from the re
   753 
   738     if request.path == '/survey/show/user/' + user.link_id:
   754     # if user can edit the survey, show everyone's results
   739       records = tuple(user.surveys_taken.run())
   755     if can_write:
   740       context = responses.getUniversalContext(request)
   756       filter['survey'] = entity
   741       context['content'] = records[0].survey.survey_content
       
   742       responses.useJavaScript(context, params['js_uses_all'])
       
   743       context['page_name'] = u'Your survey records.'
   757     else:
   744     else:
   758       filter.update({'user': user, 'survey': entity})
   745       entity, context = self.getContextEntity(request, page_name,
   759 
   746                                               params, kwargs)
   760     limit = self._params.get('limit') or 1000
   747 
   761     offset = self._params.get('offset') or 0
   748       if context is None:
   762     order = self._params.get('order') or []
   749         # user cannot see this page, return error response
   763     idx = self._params.get('idx') or 0
   750         return entity
   764 
   751       context['content'] = entity.survey_content
   765     records = results_logic.getForFields(filter=filter, limit=limit,
   752       can_write = False
   766                                       offset=offset, order=order)
   753       rights = self._params['rights']
       
   754       try:
       
   755         rights.checkIsSurveyWritable({'key_name': entity.key().name(),
       
   756                                       'prefix': entity.prefix,
       
   757                                       'scope_path': entity.scope_path,
       
   758                                       'link_id': entity.link_id,},
       
   759                                      'key_name')
       
   760         can_write = True
       
   761       except out_of_band.AccessViolation:
       
   762         pass
       
   763 
       
   764       filter = self._params.get('filter') or {}
       
   765 
       
   766       # if user can edit the survey, show everyone's results
       
   767       if can_write:
       
   768         filter['survey'] = entity
       
   769       else:
       
   770         filter.update({'user': user, 'survey': entity})
       
   771 
       
   772       limit = self._params.get('limit') or 1000
       
   773       offset = self._params.get('offset') or 0
       
   774       order = self._params.get('order') or []
       
   775       idx = self._params.get('idx') or 0
       
   776 
       
   777       records = results_logic.getForFields(filter=filter, limit=limit,
       
   778                                         offset=offset, order=order)
   767 
   779 
   768     updates = dicts.rename(params, params['list_params'])
   780     updates = dicts.rename(params, params['list_params'])
   769     context.update(updates)
   781     context.update(updates)
   770 
   782 
   771     context['results'] = records, records
   783     context['results'] = records, records
   772     context['content'] = entity.survey_content
       
   773 
   784 
   774     template = 'soc/survey/results_page.html'
   785     template = 'soc/survey/results_page.html'
   775     return responses.respond(request, template, context=context)
   786     return responses.respond(request, template, context=context)
   776 
   787 
   777   @decorators.merge_params
   788   @decorators.merge_params
   909   header = _get_csv_header(survey)
   920   header = _get_csv_header(survey)
   910   leading = ['user', 'created', 'modified']
   921   leading = ['user', 'created', 'modified']
   911   properties = leading + survey.survey_content.orderedProperties()
   922   properties = leading + survey.survey_content.orderedProperties()
   912 
   923 
   913   try:
   924   try:
   914     first = survey.getRecords().run().next()
   925     first = survey.survey_records.run().next()
   915   except StopIteration:
   926   except StopIteration:
   916     # bail out early if survey_records.run() is empty
   927     # bail out early if survey_records.run() is empty
   917     return header, survey.link_id
   928     return header, survey.link_id
   918 
   929 
   919   # generate results list
   930   # generate results list
   920   recs = survey.getRecords().run()
   931   recs = survey.survey_records.run()
   921   recs = _get_records(recs, properties)
   932   recs = _get_records(recs, properties)
   922 
   933 
   923   # write results to CSV
   934   # write results to CSV
   924   output = StringIO.StringIO()
   935   output = StringIO.StringIO()
   925   writer = csv.writer(output)
   936   writer = csv.writer(output)