diff --git a/frontend/forms.py b/frontend/forms.py index 5498be46..b333d366 100644 --- a/frontend/forms.py +++ b/frontend/forms.py @@ -333,7 +333,8 @@ class EbookForm(forms.ModelForm): class Meta: model = Ebook - exclude = ('created', 'download_count', 'active', 'filesize', 'version_iter') + #exclude = ('created', 'download_count', 'active', 'filesize', 'version_iter') + fields = ('url', 'format', 'provider', 'version_label', 'rights', 'edition', 'user') widgets = { 'edition': forms.HiddenInput, 'user': forms.HiddenInput, @@ -347,7 +348,7 @@ class EbookForm(forms.ModelForm): url = self.cleaned_data['url'] new_provider = Ebook.infer_provider(url) if url and not new_provider: - raise forms.ValidationError(_("At this time, ebook URLs must point at Internet Archive, Wikisources, Wikibooks, Hathitrust, Project Gutenberg, raw files at Github, or Google Books.")) + raise forms.ValidationError(_("At this time, ebook URLs must point at Internet Archive, Wikisources, Wikibooks, Hathitrust, Project Gutenberg, raw files at Github, Google Books, or OApen.")) return new_provider if new_provider else "Unglue.it" def clean_url(self): @@ -359,7 +360,7 @@ class EbookForm(forms.ModelForm): raise forms.ValidationError(_("There's already an ebook with that url.")) def clean(self): - format = self.cleaned_data['format'] + format = self.cleaned_data.get('format', '') the_file = self.cleaned_data.get('file', None) url = self.cleaned_data.get('url', None) test_file(the_file) diff --git a/frontend/templates/surveys.html b/frontend/templates/surveys.html index 6f133bf2..7a470498 100644 --- a/frontend/templates/surveys.html +++ b/frontend/templates/surveys.html @@ -27,7 +27,7 @@ Completed {{ landing.runinfohistory_set.all.count }} times Set up a new survey for this work.
{% for survey in surveys %} - Export answers to {{ survey }} for this work.
+ Export or Summarize answers to {{ survey }} for this work.
{% endfor %} @@ -40,7 +40,11 @@ Completed {{ landing.runinfohistory_set.all.count }} times Set up a survey using isbn.
{% for survey in surveys %} Export all my answers to {{ survey }}.
- {% if request.user.is_staff %}Export ALL answers to {{ survey }}.
{% endif %} + Summarize my responses to {{ survey }}.
+ + {% if request.user.is_staff %}Export ALL answers to {{ survey }}.
+ Summarize ALL responses to {{ survey }}.
+ {% endif %} {% endfor %} diff --git a/frontend/urls.py b/frontend/urls.py index 2f456fe4..f7b249c8 100644 --- a/frontend/urls.py +++ b/frontend/urls.py @@ -35,6 +35,7 @@ urlpatterns = [ url(r"^rightsholders/surveys/$", views.surveys, name="surveys"), url(r"^rightsholders/new_survey/(?P\d*)/?$", views.new_survey, name="new_survey"), url(r"^rightsholders/surveys/answers_(?P\d+)_(?P\d*).csv$", views.export_surveys, name="survey_answers"), + url(r"^rightsholders/surveys/summary_(?P\d+)_(?P\d*).csv$", views.surveys_summary, name="survey_summary"), url(r"^rh_admin/$", views.rh_admin, name="rh_admin"), url(r"^rh_admin/accepted/$", views.rh_admin, {'facet': 'accepted'}, name="accepted"), url(r"^rh_admin/claims/$", views.rh_admin, {'facet': 'claims'}, name="claims"), diff --git a/frontend/views.py b/frontend/views.py index 76653c8b..0e702bfb 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -135,7 +135,7 @@ from regluit.libraryauth.views import Authenticator, superlogin, login_user from regluit.libraryauth.models import Library from regluit.marc.views import qs_marc_records from regluit.questionnaire.models import Landing, Questionnaire -from regluit.questionnaire.views import export_csv as export_answers +from regluit.questionnaire.views import export_summary as answer_summary, export_csv as export_answers logger = logging.getLogger(__name__) @@ -1842,7 +1842,7 @@ def works_user_can_admin(user): Q(claim__user = user) | Q(claim__rights_holder__owner = user) ) -def export_surveys(request, qid, work_id): +def works_user_can_admin_filter(request, work_id): def work_survey_filter(answers): works = works_user_can_admin(request.user) if work_id == '0' and request.user.is_staff: @@ -1855,29 +1855,41 @@ def export_surveys(request, qid, work_id): return answers.none() else: return answers.filter(run__run_info_histories__landing__works__in=works) - - + return work_survey_filter + +def export_surveys(request, qid, work_id): def extra_entries(subject, run): - landing = None + landing = completed = None try: landing = run.run_info_histories.all()[0].landing + completed = run.run_info_histories.all()[0].completed except IndexError: try: landing = run.run_infos.all()[0].landing + completed = run.run_infos.all()[0].created except IndexError: label = wid = "error" if landing: label = landing.label wid = landing.object_id - return [wid, subject.ip_address, run.id, label] + return [wid, subject.ip_address, run.id, completed, label] if not request.user.is_authenticated() : return HttpResponseRedirect(reverse('surveys')) - extra_headings = [u'work id', u'subject ip address', u'run id', u'landing label'] + extra_headings = [u'work id', u'subject ip address', u'run id', u'date completed', u'landing label'] return export_answers(request, qid, - answer_filter=work_survey_filter, + answer_filter=works_user_can_admin_filter(request, work_id), extra_entries=extra_entries, extra_headings=extra_headings, filecode=work_id) + +def surveys_summary(request, qid, work_id): + if not request.user.is_authenticated() : + return HttpResponseRedirect(reverse('surveys')) + return answer_summary( + request, + qid, + answer_filter=works_user_can_admin_filter(request, work_id), + ) def new_survey(request, work_id): if not request.user.is_authenticated() : diff --git a/questionnaire/admin.py b/questionnaire/admin.py index 08984e56..c82ec5cc 100644 --- a/questionnaire/admin.py +++ b/questionnaire/admin.py @@ -53,8 +53,11 @@ class QuestionnaireAdmin(admin.ModelAdmin): readonly_fields = ('export',) def export(self, obj): - csv_url= reverse("export_csv", args=[obj.id,]) - return '%s' % (csv_url, _("Download data")) + csv_url = reverse("export_csv", args=[obj.id,]) + summary_url = reverse("export_summary", args=[obj.id,]) + return '{} {}'.format( + csv_url, _("Download data"), summary_url, _("Show summary") + ) export.allow_tags = True export.short_description = _('Export to CSV') diff --git a/questionnaire/templates/pages/summaries.html b/questionnaire/templates/pages/summaries.html new file mode 100644 index 00000000..e52ffa96 --- /dev/null +++ b/questionnaire/templates/pages/summaries.html @@ -0,0 +1,35 @@ +{% extends "base-questionnaire.html" %} +{% block questionnaire %} +

+ Survey Results Summary +

+{% for summary in summaries %} + +

Question

+

+{{summary.1|safe}} +

+{% if summary.2 %} +

Choices

+
    +{% for option in summary.2 %} +
  • +{{option.1}}: {{option.2}} +
  • +{% endfor %} +
+{% endif %} +

Free Text Answers

+
    +{% for answer in summary.3 %} +{% if answer %} +
  • +{{answer}} +
  • +{% endif %} +{% endfor %} +
+ +{% endfor %} + +{% endblock %} diff --git a/questionnaire/urls.py b/questionnaire/urls.py index a43c1e81..7439038d 100644 --- a/questionnaire/urls.py +++ b/questionnaire/urls.py @@ -9,6 +9,8 @@ urlpatterns = [ questionnaire, name='questionnaire_noargs'), url(r'^csv/(?P\d+)/$', export_csv, name='export_csv'), + url(r'^summary/(?P\d+)/$', + export_summary, name='export_summary'), url(r'^(?P[^/]+)/progress/$', get_async_progress, name='progress'), url(r'^take/(?P[0-9]+)/$', generate_run), @@ -19,8 +21,6 @@ urlpatterns = [ url(r'^landing/(?P\w+)/$', SurveyView.as_view(), name="landing"), url(r'^(?P[^/]+)/(?P[-]{0,1}\d+)/$', questionnaire, name='questionset'), - url(r'^q/manage/csv/(\d+)/', - export_csv, name="export_csv"), ] if not use_session: diff --git a/questionnaire/utils.py b/questionnaire/utils.py index 345f05c9..fabb9b25 100644 --- a/questionnaire/utils.py +++ b/questionnaire/utils.py @@ -41,7 +41,7 @@ def numal_sort(a, b): bnum, bstr = split_numal(b) cmpnum = cmp(anum, bnum) if(cmpnum == 0): - return cmp(astr, bstr) + return cmp(astr.lower(), bstr.lower()) return cmpnum def numal0_sort(a, b): diff --git a/questionnaire/views.py b/questionnaire/views.py index 61b44542..6af14f5a 100644 --- a/questionnaire/views.py +++ b/questionnaire/views.py @@ -812,22 +812,25 @@ def _table_headers(questions): This will create separate columns for each multiple-choice possiblity and freeform options, to avoid mixing data types and make charting easier. """ - ql = list(questions) - ql.sort(lambda x, y: numal_sort(x.number, y.number)) + ql = list(questions.order_by( + 'questionset__sortid', 'number') + ) + #ql.sort(lambda x, y: numal_sort(x.number, y.number)) columns = [] for q in ql: + qnum = '{}.{}'.format(q.questionset.sortid, q.number) if q.type.startswith('choice-yesnocomment'): - columns.extend([q.number, q.number + "-freeform"]) + columns.extend([qnum, qnum + "-freeform"]) elif q.type.startswith('choice-freeform'): - columns.extend([q.number, q.number + "-freeform"]) + columns.extend([qnum, qnum + "-freeform"]) elif q.type.startswith('choice-multiple'): cl = [c.value for c in q.choice_set.all()] cl.sort(numal_sort) - columns.extend([q.number + '-' + value for value in cl]) + columns.extend([qnum + '-' + value for value in cl]) if q.type == 'choice-multiple-freeform': - columns.append(q.number + '-freeform') + columns.append(qnum + '-freeform') else: - columns.append(q.number) + columns.append(qnum) return columns default_extra_headings = [u'subject', u'run id'] @@ -934,18 +937,19 @@ def answer_export(questionnaire, answers=None, answer_filter=None): ans = str(ans) for choice in ans: col = None + qnum = '{}.{}'.format(answer.question.questionset.sortid, answer.question.number) if type(choice) == list: # freeform choice choice = choice[0] - col = coldict.get(answer.question.number + '-freeform', None) + col = coldict.get(qnum + '-freeform', None) if col is None: # look for enumerated choice column (multiple-choice) - col = coldict.get(answer.question.number + '-' + unicode(choice), None) + col = coldict.get(qnum + '-' + unicode(choice), None) if col is None: # single-choice items if ((not qchoicedict[answer.question.id]) or choice in qchoicedict[answer.question.id]): - col = coldict.get(answer.question.number, None) + col = coldict.get(qnum, None) if col is None: # last ditch, if not found throw it in a freeform column - col = coldict.get(answer.question.number + '-freeform', None) + col = coldict.get(qnum + '-freeform', None) if col is not None: row[col] = choice # and don't forget about the last one @@ -953,8 +957,27 @@ def answer_export(questionnaire, answers=None, answer_filter=None): out.append((subject, run, row)) return headings, out +@login_required +def export_summary(request, qid, + answer_filter=None, + ): + """ + For a given questionnaire id, generate a CSV containing a summary of + answers for all subjects. + qid -- questionnaire_id + answer_filter -- custom filter for the answers. If this is present, the filter must manage access. + """ + if answer_filter is None and not request.user.has_perm("questionnaire.export"): + return HttpResponse('Sorry, you do not have export permissions', content_type="text/plain") + -def answer_summary(questionnaire, answers=None): + questionnaire = get_object_or_404(Questionnaire, pk=int(qid)) + summaries = answer_summary(questionnaire, answer_filter=answer_filter) + + return render(request, "pages/summaries.html", {'summaries':summaries}) + + +def answer_summary(questionnaire, answers=None, answer_filter=None): """ questionnaire -- questionnaire model for summary answers -- query set of answers to include in summary, defaults to all @@ -971,6 +994,8 @@ def answer_summary(questionnaire, answers=None): if answers is None: answers = Answer.objects.all() + if answer_filter: + answers = answer_filter(answers) answers = answers.filter(question__questionset__questionnaire=questionnaire) questions = Question.objects.filter( questionset__questionnaire=questionnaire).order_by(