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 |
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." |
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) |