def test_done_saves_anonymised_qs(self, mockEvalRow, mockReport): self.maxDiff = None radio_button_q = RadioButton.objects.create(text="this is a radio button question", page=self.page2) for i in range(5): Choice.objects.create(text="This is choice %i" % i, question = radio_button_q) wizard = EncryptedFormWizard.wizard_factory()() PageOneForm = wizard.form_list[0] PageTwoForm = wizard.form_list[1] KeyForm = wizard.form_list[2] page_one = PageOneForm({'question_%i' % self.question1.pk: 'test answer'}) page_one.is_valid() page_two = PageTwoForm({'question_%i' % self.question2.pk: 'another answer to a different question', 'question_%i' % radio_button_q.pk: radio_button_q.choice_set.all()[2].pk}) page_two.is_valid() key_form = KeyForm({'key': self.report_key, 'key2': self.report_key}) key_form.is_valid() mock_report = Report() mock_report.save = Mock() mock_report.owner = self.request.user mockReport.return_value = mock_report mock_eval_row = EvalRow() mock_eval_row.save = Mock() mockEvalRow.return_value = mock_eval_row self._get_wizard_response(wizard, form_list=[page_one, page_two, key_form], request = self.request) mock_eval_row.save.assert_any_call()
def setUp(self): super(ExistingRecordTest, self).setUp() User.objects.create_user(username='******', password='******') self.client.login(username='******', password='******') self.request = HttpRequest() self.request.GET = {} self.request.method = 'GET' self.request.user = User.objects.get(username='******') self.report_text = """[ { "answer": "test answer", "id": %i, "section": 1, "question_text": "first question", "type": "SingleLineText" }, { "answer": "another answer to a different question", "id": %i, "section": 1, "question_text": "2nd question", "type": "SingleLineText" } ]""" % (self.question1.pk, self.question2.pk) self.report = Report(owner=self.request.user) self.report_key = 'bananabread! is not my key' self.report.encrypt_report(self.report_text, self.report_key) self.report.save() row = EvalRow() row.anonymise_record(action=EvalRow.CREATE, report=self.report, decrypted_text=self.report_text) row.save()
def done(self, form_list, **kwargs): req = kwargs.get('request') or self.request if self.object_to_edit: report = self.object_to_edit elif self.storage.extra_data.get('report_id'): report = Report.objects.get(id=self.storage.extra_data.get('report_id')) else: report = Report(owner=req.user) if not report.owner == req.user: self._unauthorized_access(req, report) key = list(form_list)[0].cleaned_data['key'] report_text = json.dumps(self.processed_answers, sort_keys=True) report.encrypt_report(report_text, key, self.object_to_edit) report.save() # save anonymised answers if self.object_to_edit: EvalRow.store_eval_row(action=EvalRow.EDIT, report=report, decrypted_text=report_text) else: EvalRow.store_eval_row(action=EvalRow.CREATE, report=report, decrypted_text=report_text) return self.wizard_complete(report, **kwargs)
def done(self, form_list, **kwargs): req = kwargs.get('request') or self.request report = Report(owner=req.user) if self.object_to_edit: if self.object_to_edit.owner == req.user: report = self.object_to_edit else: return HttpResponseForbidden() key = list(form_list)[-1].cleaned_data['key'] report_text = json.dumps(self.processed_answers, sort_keys=True) report.encrypt_report(report_text, key) report.save() #save anonymised answers try: if self.object_to_edit: action = EvalRow.EDIT else: action = EvalRow.CREATE row = EvalRow() row.anonymise_record(action=action, report=report, decrypted_text=report_text) row.save() except Exception: logger.exception("couldn't save evaluation row on record creation") pass return self.wizard_complete(report, **kwargs)
def submit_to_matching(request, report_id, form_template_name="submit_to_matching.html", confirmation_template_name="submit_to_matching_confirmation.html", report_class=PDFMatchReport): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: if request.method == 'POST': form = SubmitToSchoolForm(owner, report, request.POST) formset = SubmitToMatchingFormSet(request.POST) form.report = report if form.is_valid() and formset.is_valid(): try: match_reports = [] for perp_form in formset: #enter into matching match_report = MatchReport(report=report) match_report.contact_name = conditional_escape(form.cleaned_data.get('name')) match_report.contact_email = form.cleaned_data.get('email') match_report.contact_phone = conditional_escape(form.cleaned_data.get('phone_number')) match_report.contact_voicemail = conditional_escape(form.cleaned_data.get('voicemail')) match_report.contact_notes = conditional_escape(form.cleaned_data.get('contact_notes')) match_report.identifier = perp_form.cleaned_data.get('perp') match_report.name = conditional_escape(perp_form.cleaned_data.get('perp_name')) match_reports.append(match_report) MatchReport.objects.bulk_create(match_reports) if settings.MATCH_IMMEDIATELY: find_matches(report_class=report_class) except Exception: logger.exception("couldn't submit match report for report {}".format(report_id)) return render(request, form_template_name, {'form': form, 'formset': formset, 'school_name': settings.SCHOOL_SHORTNAME, 'submit_error': True}) #record matching submission in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.MATCH, report=report) row.save() except Exception: logger.exception("couldn't save evaluation row on match submission") pass try: _send_user_notification(form, 'match_confirmation') except Exception: # matching was entered even if confirmation email fails, so don't show an error if so logger.exception("couldn't send confirmation to user on match submission") return render(request, confirmation_template_name, {'school_name': settings.SCHOOL_SHORTNAME, 'report': report}) else: form = SubmitToSchoolForm(owner, report) formset = SubmitToMatchingFormSet() return render(request, form_template_name, {'form': form, 'formset': formset, 'school_name': settings.SCHOOL_SHORTNAME}) else: logger.warning("illegal matching attempt on record {} by user {}".format(report_id, owner.id)) return HttpResponseForbidden()
def clean_key(self): key = self.cleaned_data.get('key') report = self.report try: decrypted_report = report.decrypted_report(key) self.decrypted_report = decrypted_report #save anonymous row if one wasn't saved on creation try: row = EvalRow() row.set_identifiers(report) if (EvalRow.objects.filter( record_identifier=row.record_identifier).count() == 0): row.action = EvalRow.FIRST row.add_report_data(decrypted_report) row.save() except Exception: logger.exception( "couldn't save anonymous row on catch-up save") pass except CryptoError: self.decrypted_report = None logger.info("decryption failure on report {}".format(report.id)) raise forms.ValidationError( self.error_messages['wrong_key'], code='wrong_key', ) return key
def clean_key(self): key = self.cleaned_data.get('key') report = self.report try: decrypted_report = report.decrypted_report(key) self.decrypted_report = decrypted_report #save anonymous row if one wasn't saved on creation try: row = EvalRow() row.set_identifiers(report) if (EvalRow.objects.filter( record_identifier=row.record_identifier).count() == 0): row.action = EvalRow.FIRST row.add_report_data(decrypted_report) row.save() except Exception as e: #TODO: real logging bugsnag.notify(e) pass except CryptoError: self.decrypted_report = None raise forms.ValidationError( self.error_messages['wrong_key'], code='wrong_key', ) return key
def test_can_encrypt_a_row(self): user = User.objects.create_user(username="******", password="******") report = Report.objects.create(owner=user, encrypted=b'first report') row = EvalRow(action=EvalRow.CREATE) row.set_identifiers(report) test_row = "{'test_field': 'test_answer'}" row._encrypt_eval_row(test_row) row.save() row.full_clean() self.assertEqual(EvalRow.objects.count(), 1) self.assertIsNotNone(EvalRow.objects.get(id=row.pk).row) self.assertNotEqual(EvalRow.objects.get(id=row.pk).row, test_row)
def test_can_decrypt_a_row(self): gpg = gnupg.GPG() test_key = gpg.import_keys(private_test_key) self.addCleanup(delete_test_key, gpg, test_key.fingerprints[0]) user = User.objects.create_user(username="******", password="******") report = Report.objects.create(owner=user, encrypted=b'first report') row = EvalRow(action=EvalRow.CREATE) row.set_identifiers(report) test_row = "{'test_field': 'test_answer'}" row._encrypt_eval_row(test_row, key=public_test_key) row.save() row.full_clean() self.assertEqual(str(gpg.decrypt(EvalRow.objects.get(id=row.pk).row)), test_row)
def clean_key(self): key = self.cleaned_data.get('key') report = self.report try: decrypted_report = report.decrypted_report(key) self.decrypted_report = decrypted_report #save anonymous row if one wasn't saved on creation try: row = EvalRow() row.set_identifiers(report) if (EvalRow.objects.filter(record_identifier = row.record_identifier).count() == 0): row.action = EvalRow.FIRST row.add_report_data(decrypted_report) row.save() except Exception as e: #TODO: real logging bugsnag.notify(e) pass except CryptoError: self.decrypted_report = None raise forms.ValidationError( self.error_messages['wrong_key'], code='wrong_key', ) return key
def clean_key(self): key = self.cleaned_data.get('key') report = self.report try: decrypted_report = report.decrypted_report(key) self.decrypted_report = decrypted_report #save anonymous row if one wasn't saved on creation try: row = EvalRow() row.set_identifiers(report) if (EvalRow.objects.filter(record_identifier = row.record_identifier).count() == 0): row.action = EvalRow.FIRST row.add_report_data(decrypted_report) row.save() except Exception: logger.exception("couldn't save anonymous row on catch-up save") pass except CryptoError: self.decrypted_report = None logger.info("decryption failure on report {}".format(report.id)) raise forms.ValidationError( self.error_messages['wrong_key'], code='wrong_key', ) return key
def test_can_decrypt_a_row(self): gpg = gnupg.GPG() test_key = gpg.import_keys(private_test_key) self.addCleanup(delete_test_key, gpg, test_key.fingerprints[0]) user = User.objects.create_user(username="******", password="******") report = Report.objects.create(owner=user, encrypted=b'first report') row = EvalRow(action=EvalRow.CREATE) row.set_identifiers(report) test_row = "{'test_field': 'test_answer'}" row._encrypt_eval_row(test_row, key = public_test_key) row.save() row.full_clean() self.assertEqual(six.text_type(gpg.decrypt(six.binary_type(EvalRow.objects.get(id=row.pk).row))), test_row)
def submit_to_school(request, report_id, form_template_name="submit_to_school.html", confirmation_template_name="submit_to_school_confirmation.html", report_class=PDFFullReport): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: if request.method == 'POST': form = SubmitToSchoolForm(owner, report, request.POST) form.report = report if form.is_valid(): try: report.contact_name = conditional_escape(form.cleaned_data.get('name')) report.contact_email = form.cleaned_data.get('email') report.contact_phone = conditional_escape(form.cleaned_data.get('phone_number')) report.contact_voicemail = conditional_escape(form.cleaned_data.get('voicemail')) report.contact_notes = conditional_escape(form.cleaned_data.get('contact_notes')) report_class(report=report, decrypted_report=form.decrypted_report).send_report_to_school() report.save() except Exception: logger.exception("couldn't submit report for report {}".format(report_id)) return render(request, form_template_name, {'form': form, 'school_name': settings.SCHOOL_SHORTNAME, 'submit_error': True}) #record submission in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.SUBMIT, report=report) row.save() except Exception: logger.exception("couldn't save evaluation row on submission") pass try: _send_user_notification(form, 'submit_confirmation') except Exception: # report was sent even if confirmation email fails, so don't show an error if so logger.exception("couldn't send confirmation to user on submission") return render(request, confirmation_template_name, {'form': form, 'school_name': settings.SCHOOL_SHORTNAME, 'report': report}) else: form = SubmitToSchoolForm(owner, report) return render(request, form_template_name, {'form': form, 'school_name': settings.SCHOOL_SHORTNAME}) else: logger.warning("illegal submit attempt on record {} by user {}".format(report_id, owner.id)) return HttpResponseForbidden()
def test_anonymise_record_end_to_end(self): self.maxDiff = None self.set_up_simple_report_scenario() user = User.objects.create_user(username="******", password="******") report = Report.objects.create(owner=user, encrypted=b'dummy report') gpg = gnupg.GPG() test_key = gpg.import_keys(private_test_key) self.addCleanup(delete_test_key, gpg, test_key.fingerprints[0]) row = EvalRow() row.anonymise_record(action=EvalRow.CREATE, report=report, decrypted_text=self.json_report, key = public_test_key) row.save() self.assertEqual(json.loads(six.text_type(gpg.decrypt(six.binary_type(EvalRow.objects.get(id=row.pk).row)))), self.expected)
def test_tracking_of_answered_questions(self): self.maxDiff = None page1 = QuestionPage.objects.create() question1 = SingleLineText.objects.create(text="first question", page=page1) question2 = SingleLineText.objects.create(text="2nd question", page=page1) radio_button_q = RadioButton.objects.create( text="this is a radio button question", page=page1) for i in range(5): Choice.objects.create(text="This is choice %i" % i, question=radio_button_q) choice_ids = [choice.pk for choice in radio_button_q.choice_set.all()] object_ids = [ question1.pk, question2.pk, radio_button_q.pk, ] + choice_ids json_report = json.loads("""[ { "answer": "test answer", "id": %i, "section": 1, "question_text": "first question", "type": "SingleLineText" }, { "answer": " ", "id": %i, "section": 1, "question_text": "2nd question", "type": "SingleLineText" }, { "answer": "", "id": %i, "section": 1, "question_text": "this is a radio button question", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "RadioButton" } ]""" % tuple(object_ids)) expected = { "answered": [question1.pk], "unanswered": [question2.pk, radio_button_q.pk] } anonymised = EvalRow()._extract_answers(json_report) self.assertEqual(anonymised, expected)
def auto_save(self, **kwargs): """ Automatically save what's been entered so far before rendering the next step. We only do this on steps after the first non-key form because we can only save completed steps https://github.com/SexualHealthInnovations/callisto-core/issues/89 """ if not self.object_to_edit and int(self.steps.current) > 1: if self.storage.extra_data.get('report_id'): report = Report.objects.get(id=self.storage.extra_data.get('report_id')) else: req = kwargs.get('request') or self.request report = Report(owner=req.user) forms_so_far = self._get_forms_with_data() report_text = json.dumps(self.process_answers(forms_so_far.values(), form_dict=forms_so_far), sort_keys=True) key = forms_so_far['0'].cleaned_data['key'] report.encrypt_report(report_text, key, edit=self.object_to_edit, autosave=True) report.save() self.storage.extra_data['report_id'] = report.id EvalRow.store_eval_row(action=EvalRow.AUTOSAVE, report=report, decrypted_text=report_text)
def test_tracking_of_answered_questions_checkbox(self): self.maxDiff = None page1 = QuestionPage.objects.create() checkbox_q_1 = Checkbox.objects.create( text="this is a checkbox question", page=page1) for i in range(5): Choice.objects.create(text="This is choice %i" % i, question=checkbox_q_1) checkbox_q_2 = Checkbox.objects.create( text="this is another checkbox question", page=page1) for i in range(5): Choice.objects.create(text="This is choice %i" % i, question=checkbox_q_2) choice_ids_1 = [choice.pk for choice in checkbox_q_1.choice_set.all()] choice_ids_2 = [choice.pk for choice in checkbox_q_2.choice_set.all()] object_ids = [checkbox_q_1.pk] + choice_ids_1 + [ choice_ids_2[1], choice_ids_2[3], checkbox_q_2.pk ] + choice_ids_2 json_report = json.loads("""[ { "answer": [], "id": %i, "section": 1, "question_text": "this is a checkbox question", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "Checkbox" }, { "answer": ["%i", "%i"], "id": %i, "section": 1, "question_text": "this is another checkbox question", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "Checkbox" } ]""" % tuple(object_ids)) expected = { "answered": [checkbox_q_2.pk], "unanswered": [checkbox_q_1.pk] } anonymised = EvalRow()._extract_answers(json_report) self.assertEqual(anonymised, expected)
def test_done_saves_anonymised_qs(self, mockEvalRow, mockReport): self.maxDiff = None radio_button_q = RadioButton.objects.create( text="this is a radio button question", page=self.page2) for i in range(5): Choice.objects.create(text="This is choice %i" % i, question=radio_button_q) wizard = EncryptedFormWizard.wizard_factory()() PageOneForm = wizard.form_list[0] PageTwoForm = wizard.form_list[1] KeyForm = wizard.form_list[2] page_one = PageOneForm( {'question_%i' % self.question1.pk: 'test answer'}) page_one.is_valid() page_two = PageTwoForm({ 'question_%i' % self.question2.pk: 'another answer to a different question', 'question_%i' % radio_button_q.pk: radio_button_q.choice_set.all()[2].pk }) page_two.is_valid() key_form = KeyForm({'key': self.report_key, 'key2': self.report_key}) key_form.is_valid() mock_report = Report() mock_report.save = Mock() mock_report.owner = self.request.user mockReport.return_value = mock_report mock_eval_row = EvalRow() mock_eval_row.save = Mock() mockEvalRow.return_value = mock_eval_row self._get_wizard_response(wizard, form_list=[page_one, page_two, key_form], request=self.request) mock_eval_row.save.assert_any_call()
def done(self, form_list, **kwargs): req = kwargs.get('request') or self.request report = Report(owner=req.user) if self.object_to_edit: if self.object_to_edit.owner == req.user: report = self.object_to_edit else: return HttpResponseForbidden() key = list(form_list)[-1].cleaned_data['key'] report_text = json.dumps(self.processed_answers, sort_keys=True) report.encrypt_report(report_text, key) report.save() #save anonymised answers try: if self.object_to_edit: action = EvalRow.EDIT else: action = EvalRow.CREATE row = EvalRow() row.anonymise_record(action=action, report=report, decrypted_text=report_text) row.save() except Exception as e: #TODO: real logging bugsnag.notify(e) pass #TODO: check if valid? return self.wizard_complete(report, **kwargs)
def test_anonymise_record_end_to_end(self): self.maxDiff = None self.set_up_simple_report_scenario() user = User.objects.create_user(username="******", password="******") report = Report.objects.create(owner=user, encrypted=b'dummy report') gpg = gnupg.GPG() test_key = gpg.import_keys(private_test_key) self.addCleanup(delete_test_key, gpg, test_key.fingerprints[0]) row = EvalRow() row.anonymise_record(action=EvalRow.CREATE, report=report, decrypted_text=self.json_report, key=public_test_key) row.save() self.assertEqual( json.loads( six.text_type( gpg.decrypt( six.binary_type(EvalRow.objects.get(id=row.pk).row)))), self.expected)
def withdraw_from_matching(request, report_id, template_name): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: report.withdraw_from_matching() report.save() # record match withdrawal in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.WITHDRAW, report=report) row.save() except Exception: logger.exception( "couldn't save evaluation row on match withdrawal") pass return render( request, template_name, { 'owner': request.user, 'school_name': settings.SCHOOL_SHORTNAME, 'coordinator_name': settings.COORDINATOR_NAME, 'coordinator_email': settings.COORDINATOR_EMAIL, 'match_report_withdrawn': True }) else: logger.warning( "illegal matching withdrawal attempt on record {} by user {}". format(report_id, owner.id)) return HttpResponseForbidden()
def process_new_matches(matches, identifier, report_class): """Sends a report to the receiving authority and notifies the reporting users. Each user should only be notified one time when a match is found. Args: matches (list of MatchReports): the MatchReports that correspond to this identifier identifier (str): identifier associated with the MatchReports report_class(report generator class, optional): Must have `send_matching_report_to_school` method. (Default value = PDFMatchReport) """ logger.info("new match found") owners_notified = [] for match_report in matches: EvalRow.store_eval_row(action=EvalRow.MATCH_FOUND, report=match_report.report) owner = match_report.report.owner # only send notification emails to new matches if owner not in owners_notified and not match_report.report.match_found \ and not match_report.report.submitted_to_school: send_notification_email(owner, match_report) owners_notified.append(owner) # send report to school report_class(matches, identifier).send_matching_report_to_school()
def withdraw_from_matching(request, report_id, template_name): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: report.withdraw_from_matching() report.save() # record match withdrawal in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.WITHDRAW, report=report) row.save() except Exception: logger.exception("couldn't save evaluation row on match withdrawal") pass return render(request, template_name, {'owner': request.user, 'school_name': settings.SCHOOL_SHORTNAME, 'coordinator_name': settings.COORDINATOR_NAME, 'coordinator_email': settings.COORDINATOR_EMAIL, 'match_report_withdrawn': True}) else: logger.warning("illegal matching withdrawal attempt on record {} by user {}".format(report_id, owner.id)) return HttpResponseForbidden()
def test_report_hashes_correctly(self): user1 = User.objects.create_user(username="******", password="******") report1 = Report.objects.create(owner=user1, encrypted=b'first report') user2 = User.objects.create_user(username="******", password="******") report2 = Report.objects.create(owner=user2, encrypted=b'second report') report3 = Report.objects.create(owner=user1, encrypted=b'third report') row1 = self.save_row_for_report(report1) row2 = self.save_row_for_report(report2) row3 = self.save_row_for_report(report3) row3_edit = EvalRow(action=EvalRow.EDIT) row3_edit.set_identifiers(report3) row3_edit.save() row3_edit.full_clean() self.assertEqual(EvalRow.objects.count(), 4) self.assertNotEqual(EvalRow.objects.get(id=row2.pk).record_identifier, EvalRow.objects.get(id=row3.pk).record_identifier) self.assertNotEqual(EvalRow.objects.get(id=row1.pk).record_identifier, EvalRow.objects.get(id=row2.pk).record_identifier) self.assertEqual(EvalRow.objects.get(id=row3.pk).record_identifier, EvalRow.objects.get(id=row3_edit.pk).record_identifier)
def submit_to_school(request, report_id): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: if request.method == 'POST': form = SubmitToSchoolForm(owner, report, request.POST) form.report = report if form.is_valid(): try: report.contact_name = conditional_escape(form.cleaned_data.get('name')) report.contact_email = form.cleaned_data.get('email') report.contact_phone = conditional_escape(form.cleaned_data.get('phone_number')) report.contact_voicemail = conditional_escape(form.cleaned_data.get('voicemail')) report.contact_notes = conditional_escape(form.cleaned_data.get('contact_notes')) PDFFullReport(owner, report, form.decrypted_report).send_report_to_school() report.save() except Exception as e: #TODO: real logging bugsnag.notify(e) return render(request, 'submit_to_school.html', {'form': form, 'school_name': settings.SCHOOL_SHORTNAME, 'submit_error': True}) #record submission in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.SUBMIT, report=report) row.save() except Exception as e: #TODO: real logging bugsnag.notify(e) pass try: if form.cleaned_data.get('email_confirmation') == "True": notification = EmailNotification.objects.get(name='submit_confirmation') preferred_email = form.cleaned_data.get('email') to_email = set([owner.account.school_email, preferred_email]) from_email = '"Callisto Confirmation" <confirmation@{0}>'.format(settings.APP_URL) notification.send(to=to_email, from_email=from_email) except Exception as e: #TODO: real logging # report was sent even if confirmation email fails, so don't show an error if so bugsnag.notify(e) return render(request, 'submit_to_school_confirmation.html', {'form': form, 'school_name': settings.SCHOOL_SHORTNAME, 'report': report}) else: form = SubmitToSchoolForm(owner, report) return render(request, 'submit_to_school.html', {'form': form, 'school_name': settings.SCHOOL_SHORTNAME}) else: return HttpResponseForbidden()
def test_report_hashes_correctly(self): user1 = User.objects.create_user(username="******", password="******") report1 = Report.objects.create(owner=user1, encrypted=b'first report') user2 = User.objects.create_user(username="******", password="******") report2 = Report.objects.create(owner=user2, encrypted=b'second report') report3 = Report.objects.create(owner=user1, encrypted=b'third report') row1 = self.save_row_for_report(report1) row2 = self.save_row_for_report(report2) row3 = self.save_row_for_report(report3) row3_edit = EvalRow(action=EvalRow.EDIT) row3_edit.set_identifiers(report3) row3_edit.save() row3_edit.full_clean() self.assertEqual(EvalRow.objects.count(), 4) self.assertNotEqual( EvalRow.objects.get(id=row2.pk).record_identifier, EvalRow.objects.get(id=row3.pk).record_identifier) self.assertNotEqual( EvalRow.objects.get(id=row1.pk).record_identifier, EvalRow.objects.get(id=row2.pk).record_identifier) self.assertEqual( EvalRow.objects.get(id=row3.pk).record_identifier, EvalRow.objects.get(id=row3_edit.pk).record_identifier)
def withdraw_from_matching(request, report_id): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: report.withdraw_from_matching() report.save() #record match withdrawal in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.WITHDRAW, report=report) row.save() except Exception as e: #TODO: real logging bugsnag.notify(e) pass return render(request, 'dashboard.html', {'owner': request.user, 'school_name': settings.SCHOOL_SHORTNAME, 'coordinator_name': settings.COORDINATOR_NAME, 'coordinator_email': settings.COORDINATOR_EMAIL, 'match_report_withdrawn': True}) else: return HttpResponseForbidden()
def submit_to_matching(request, report_id): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: if request.method == 'POST': form = SubmitToSchoolForm(owner, report, request.POST) formset = SubmitToMatchingFormSet(request.POST) form.report = report if form.is_valid() and formset.is_valid(): try: match_reports = [] for perp_form in formset: #enter into matching match_report = MatchReport(report=report) match_report.contact_name = conditional_escape(form.cleaned_data.get('name')) match_report.contact_email = form.cleaned_data.get('email') match_report.contact_phone = conditional_escape(form.cleaned_data.get('phone_number')) match_report.contact_voicemail = conditional_escape(form.cleaned_data.get('voicemail')) match_report.contact_notes = conditional_escape(form.cleaned_data.get('contact_notes')) match_report.identifier = perp_form.cleaned_data.get('perp') match_report.name = conditional_escape(perp_form.cleaned_data.get('perp_name')) match_reports.append(match_report) MatchReport.objects.bulk_create(match_reports) if settings.MATCH_IMMEDIATELY: find_matches() except Exception as e: #TODO: real logging bugsnag.notify(e) return render(request, 'submit_to_matching.html', {'form': form, 'formset': formset, 'school_name': settings.SCHOOL_SHORTNAME, 'submit_error': True}) #record matching submission in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.MATCH, report=report) row.save() except Exception as e: #TODO: real logging bugsnag.notify(e) pass try: if form.cleaned_data.get('email_confirmation') == "True": notification = EmailNotification.objects.get(name='match_confirmation') preferred_email = form.cleaned_data.get('email') to_email = set([owner.account.school_email, preferred_email]) from_email = '"Callisto Confirmation" <confirmation@{0}>'.format(settings.APP_URL) notification.send(to=to_email, from_email=from_email) except Exception as e: #TODO: real logging # matching was entered even if confirmation email fails, so don't show an error if so bugsnag.notify(e) return render(request, 'submit_to_matching_confirmation.html', {'school_name': settings.SCHOOL_SHORTNAME, 'report': report}) else: form = SubmitToSchoolForm(owner, report) formset = SubmitToMatchingFormSet() return render(request, 'submit_to_matching.html', {'form': form, 'formset': formset, 'school_name': settings.SCHOOL_SHORTNAME}) else: return HttpResponseForbidden()
def test_extract_answers_with_multiple(self): self.maxDiff = None page1 = QuestionPage.objects.create() single_question = SingleLineText.objects.create(text="single question", page=page1) EvaluationField.objects.create(question=single_question, label="single_q") page2 = QuestionPage.objects.create(multiple=True, name_for_multiple="form") question1 = SingleLineText.objects.create(text="first question", page=page2) question2 = SingleLineText.objects.create(text="2nd question", page=page2) EvaluationField.objects.create(question=question2, label="q2") radio_button_q = RadioButton.objects.create( text="this is a radio button question", page=page2) for i in range(5): Choice.objects.create(text="This is choice %i" % i, question=radio_button_q) EvaluationField.objects.create(question=radio_button_q, label="radio") choice_ids = [choice.pk for choice in radio_button_q.choice_set.all()] selected_id_1 = choice_ids[1] selected_id_2 = choice_ids[4] object_ids = [ question1.pk, question2.pk, radio_button_q.pk, ] + choice_ids answer_set_template = """[ { "answer": "test answer <PREFIX> answer", "id": %i, "section": 1, "question_text": "first question", "type": "SingleLineText" }, { "answer": "<PREFIX> answer to a different question", "id": %i, "section": 1, "question_text": "2nd question", "type": "SingleLineText" }, { "answer": "<SELECTED>", "id": %i, "section": 1, "question_text": "this is a radio button question", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "RadioButton" } ]""" % tuple(object_ids) answer_set_one = answer_set_template.replace("<PREFIX>", "first").replace( "<SELECTED>", str(selected_id_1)) answer_set_two = answer_set_template.replace("<PREFIX>", "second").replace( "<SELECTED>", str(selected_id_2)) json_report = json.loads( """ [ { "answer": "single answer", "id": %i, "section": 1, "question_text": "single question", "type": "SingleLineText" }, { "answers" : [ %s, %s], "page_id" : %i, "prompt" : "form", "section" : 1, "type" : "FormSet" } ]""" % (single_question.pk, answer_set_one, answer_set_two, page2.pk)) expected = { 'single_q': 'single answer', "answered": [single_question.pk], "unanswered": [], 'form_multiple': [{ "q2": "first answer to a different question", "radio": str(selected_id_1), "radio_choices": [{ "id": choice_ids[0], "choice_text": "This is choice 0" }, { "id": choice_ids[1], "choice_text": "This is choice 1" }, { "id": choice_ids[2], "choice_text": "This is choice 2" }, { "id": choice_ids[3], "choice_text": "This is choice 3" }, { "id": choice_ids[4], "choice_text": "This is choice 4" }], "answered": [question1.pk, question2.pk, radio_button_q.pk], "unanswered": [] }, { "q2": "second answer to a different question", "radio": str(selected_id_2), "radio_choices": [{ "id": choice_ids[0], "choice_text": "This is choice 0" }, { "id": choice_ids[1], "choice_text": "This is choice 1" }, { "id": choice_ids[2], "choice_text": "This is choice 2" }, { "id": choice_ids[3], "choice_text": "This is choice 3" }, { "id": choice_ids[4], "choice_text": "This is choice 4" }], "answered": [question1.pk, question2.pk, radio_button_q.pk], "unanswered": [] }] } anonymised = EvalRow()._extract_answers(json_report) self.assertEqual(anonymised, expected)
def save_row_for_report(self, report): row = EvalRow(action=EvalRow.SUBMIT) row.set_identifiers(report) row.full_clean() row.save() return row
def test_extract_answers_with_extra(self): self.maxDiff = None page1 = QuestionPage.objects.create() question1 = RadioButton.objects.create( text="this is a radio button question", page=page1) for i in range(5): choice = Choice.objects.create(text="This is choice %i" % i, question=question1) if i == 0: choice.extra_info_placeholder = "extra box for choice %i" % i choice.save() EvaluationField.objects.create(question=question1, label="q1") question2 = RadioButton.objects.create( text="this is another radio button question", page=page1) for i in range(5): choice = Choice.objects.create(text="This is choice %i" % i, question=question2) if i % 2 == 1: choice.extra_info_placeholder = "extra box for choice %i" % i choice.save() EvaluationField.objects.create(question=question2, label="q2") question3 = RadioButton.objects.create( text="this is a radio button question too", page=page1) for i in range(5): choice = Choice.objects.create(text="This is choice %i" % i, question=question3) if i == 0: choice.extra_info_placeholder = "extra box for choice %i" % i choice.save() EvaluationField.objects.create(question=question3, label="q3") q1_choice_ids = [choice.pk for choice in question1.choice_set.all()] q1_selected_id = q1_choice_ids[1] first_q_object_ids = [q1_selected_id, question1.pk] + q1_choice_ids first_q_output = """{ "answer": "%i", "id": %i, "question_text": "this is a radio button question", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "RadioButton", "section": 1 }""" % tuple(first_q_object_ids) q2_choice_ids = [choice.pk for choice in question2.choice_set.all()] q2_selected_id = q2_choice_ids[3] second_q_object_ids = [q2_selected_id, question2.pk] + q2_choice_ids second_q_output = """{ "answer": "%i", "id": %i, "question_text": "this is another radio button question", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "RadioButton", "section": 1, "extra": { "extra_text": "extra box for choice 3", "answer": "this should be in the report" } }""" % tuple(second_q_object_ids) q3_choice_ids = [choice.pk for choice in question3.choice_set.all()] q3_selected_id = q3_choice_ids[0] third_q_object_ids = [q3_selected_id, question3.pk] + q3_choice_ids third_q_output = """{ "answer": "%i", "id": %i, "section": 1, "question_text": "this is a radio button question too", "choices": [{"id": %i, "choice_text": "This is choice 0"}, {"id": %i, "choice_text": "This is choice 1"}, {"id": %i, "choice_text": "This is choice 2"}, {"id": %i, "choice_text": "This is choice 3"}, {"id": %i, "choice_text": "This is choice 4"}], "type": "RadioButton", "extra": { "extra_text": "extra box for choice 0", "answer": "" } }""" % tuple(third_q_object_ids) json_report = json.loads( "[%s, %s, %s]" % (first_q_output, second_q_output, third_q_output)) expected = { "q1": str(q1_selected_id), "q1_choices": [{ "id": q1_choice_ids[0], "choice_text": "This is choice 0" }, { "id": q1_choice_ids[1], "choice_text": "This is choice 1" }, { "id": q1_choice_ids[2], "choice_text": "This is choice 2" }, { "id": q1_choice_ids[3], "choice_text": "This is choice 3" }, { "id": q1_choice_ids[4], "choice_text": "This is choice 4" }], "q2": str(q2_selected_id), "q2_choices": [{ "id": q2_choice_ids[0], "choice_text": "This is choice 0" }, { "id": q2_choice_ids[1], "choice_text": "This is choice 1" }, { "id": q2_choice_ids[2], "choice_text": "This is choice 2" }, { "id": q2_choice_ids[3], "choice_text": "This is choice 3" }, { "id": q2_choice_ids[4], "choice_text": "This is choice 4" }], "q2_extra": "this should be in the report", "q3": str(q3_selected_id), "q3_choices": [{ "id": q3_choice_ids[0], "choice_text": "This is choice 0" }, { "id": q3_choice_ids[1], "choice_text": "This is choice 1" }, { "id": q3_choice_ids[2], "choice_text": "This is choice 2" }, { "id": q3_choice_ids[3], "choice_text": "This is choice 3" }, { "id": q3_choice_ids[4], "choice_text": "This is choice 4" }], "q3_extra": "", "answered": [question1.pk, question2.pk, question3.pk], "unanswered": [] } anonymised = EvalRow()._extract_answers(json_report) self.assertEqual(anonymised, expected)
def test_extract_answers(self): self.maxDiff = None self.set_up_simple_report_scenario() anonymised = EvalRow()._extract_answers(json.loads(self.json_report)) self.assertEqual(anonymised, self.expected)
def test_make_eval_row(self): user = User.objects.create_user(username="******", password="******") report = Report.objects.create(owner=user, encrypted=b'first report') EvalRow.store_eval_row(EvalRow.CREATE, report=report) self.assertEqual(EvalRow.objects.count(), 1)
def submit_to_matching( request, report_id, form_template_name="submit_to_matching.html", confirmation_template_name="submit_to_matching_confirmation.html", report_class=PDFMatchReport): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: if request.method == 'POST': form = SubmitToSchoolForm(owner, report, request.POST) formset = SubmitToMatchingFormSet(request.POST) form.report = report if form.is_valid() and formset.is_valid(): try: match_reports = [] for perp_form in formset: #enter into matching match_report = MatchReport(report=report) match_report.contact_name = conditional_escape( form.cleaned_data.get('name')) match_report.contact_email = form.cleaned_data.get( 'email') match_report.contact_phone = conditional_escape( form.cleaned_data.get('phone_number')) match_report.contact_voicemail = conditional_escape( form.cleaned_data.get('voicemail')) match_report.contact_notes = conditional_escape( form.cleaned_data.get('contact_notes')) match_report.identifier = perp_form.cleaned_data.get( 'perp') match_report.name = conditional_escape( perp_form.cleaned_data.get('perp_name')) match_reports.append(match_report) MatchReport.objects.bulk_create(match_reports) if settings.MATCH_IMMEDIATELY: find_matches(report_class=report_class) except Exception: logger.exception( "couldn't submit match report for report {}".format( report_id)) return render( request, form_template_name, { 'form': form, 'formset': formset, 'school_name': settings.SCHOOL_SHORTNAME, 'submit_error': True }) #record matching submission in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.MATCH, report=report) row.save() except Exception: logger.exception( "couldn't save evaluation row on match submission") pass try: _send_user_notification(form, 'match_confirmation') except Exception: # matching was entered even if confirmation email fails, so don't show an error if so logger.exception( "couldn't send confirmation to user on match submission" ) return render(request, confirmation_template_name, { 'school_name': settings.SCHOOL_SHORTNAME, 'report': report }) else: form = SubmitToSchoolForm(owner, report) formset = SubmitToMatchingFormSet() return render( request, form_template_name, { 'form': form, 'formset': formset, 'school_name': settings.SCHOOL_SHORTNAME }) else: logger.warning( "illegal matching attempt on record {} by user {}".format( report_id, owner.id)) return HttpResponseForbidden()
def submit_to_school( request, report_id, form_template_name="submit_to_school.html", confirmation_template_name="submit_to_school_confirmation.html", report_class=PDFFullReport): owner = request.user report = Report.objects.get(id=report_id) if owner == report.owner: if request.method == 'POST': form = SubmitToSchoolForm(owner, report, request.POST) form.report = report if form.is_valid(): try: report.contact_name = conditional_escape( form.cleaned_data.get('name')) report.contact_email = form.cleaned_data.get('email') report.contact_phone = conditional_escape( form.cleaned_data.get('phone_number')) report.contact_voicemail = conditional_escape( form.cleaned_data.get('voicemail')) report.contact_notes = conditional_escape( form.cleaned_data.get('contact_notes')) report_class(report=report, decrypted_report=form.decrypted_report ).send_report_to_school() report.save() except Exception: logger.exception( "couldn't submit report for report {}".format( report_id)) return render( request, form_template_name, { 'form': form, 'school_name': settings.SCHOOL_SHORTNAME, 'submit_error': True }) #record submission in anonymous evaluation data try: row = EvalRow() row.anonymise_record(action=EvalRow.SUBMIT, report=report) row.save() except Exception: logger.exception( "couldn't save evaluation row on submission") pass try: _send_user_notification(form, 'submit_confirmation') except Exception: # report was sent even if confirmation email fails, so don't show an error if so logger.exception( "couldn't send confirmation to user on submission") return render( request, confirmation_template_name, { 'form': form, 'school_name': settings.SCHOOL_SHORTNAME, 'report': report }) else: form = SubmitToSchoolForm(owner, report) return render(request, form_template_name, { 'form': form, 'school_name': settings.SCHOOL_SHORTNAME }) else: logger.warning("illegal submit attempt on record {} by user {}".format( report_id, owner.id)) return HttpResponseForbidden()