diff --git a/api/app/invoice/tests.py b/api/app/invoice/tests.py index 260bd5023..802b27fa5 100644 --- a/api/app/invoice/tests.py +++ b/api/app/invoice/tests.py @@ -548,17 +548,6 @@ def __init__(self, *args, **kwargs): def setUp(self): super().setUp() - - def test_prevent_paying_wrong_invoice(self): - line_items = self.get_default_line_items() - invoice = self.add_invoice(self.treasurer_id, self.treasurer_id, line_items, self.treasurer_email) - invoice_id = invoice.id - - header = self.get_auth_header_for(self.applicant_email) - params = {'invoice_id': invoice_id} - response = self.app.post(self.url, headers=header, data=params) - - self.assertEqual(response.status_code, INVOICE_NOT_FOUND[1]) def test_prevent_pay_invoice_again(self): line_items = self.get_default_line_items() diff --git a/api/app/reviews/api.py b/api/app/reviews/api.py index b2b689211..2ef3809e0 100644 --- a/api/app/reviews/api.py +++ b/api/app/reviews/api.py @@ -388,12 +388,14 @@ def post(self): scores = args['scores'] language = args['language'] is_submitted = args['is_submitted'] + is_private = args['is_private'] response_reviewer = review_repository.get_response_reviewer(response_id, reviewer_user_id) if response_reviewer is None: return FORBIDDEN - review_response = ReviewResponse(review_form_id, reviewer_user_id, response_id, language) + review_response = ReviewResponse(review_form_id, reviewer_user_id, response_id, + language, is_private) review_response.review_scores = self.get_review_scores(scores) if is_submitted: review_response.submit() diff --git a/api/app/reviews/mixins.py b/api/app/reviews/mixins.py index a4ae8f152..a8a6b9498 100644 --- a/api/app/reviews/mixins.py +++ b/api/app/reviews/mixins.py @@ -18,6 +18,7 @@ class PostReviewResponseMixin(object): post_req_parser.add_argument('scores', type=dict, required=True, action='append') post_req_parser.add_argument('language', type=str, required=True) post_req_parser.add_argument('is_submitted', type=bool, required=True) + post_req_parser.add_argument('is_private', type=bool, required=True) class GetReviewAssignmentMixin(object): get_req_parser = reqparse.RequestParser() diff --git a/api/app/reviews/models.py b/api/app/reviews/models.py index acaf9c478..a4e0ca4ab 100644 --- a/api/app/reviews/models.py +++ b/api/app/reviews/models.py @@ -195,6 +195,7 @@ class ReviewResponse(db.Model): language = db.Column(db.String(2), nullable=False) is_submitted = db.Column(db.Boolean(), nullable=False) submitted_timestamp = db.Column(db.DateTime(), nullable=True) + is_private = db.Column(db.Boolean(), nullable=False) review_form = db.relationship('ReviewForm', foreign_keys=[review_form_id]) reviewer_user = db.relationship('AppUser', foreign_keys=[reviewer_user_id]) @@ -207,12 +208,14 @@ def __init__(self, review_form_id, reviewer_user_id, response_id, - language): + language, + is_private): self.review_form_id = review_form_id self.reviewer_user_id = reviewer_user_id self.response_id = response_id self.language = language self.is_submitted = False + self.is_private = is_private def submit(self): self.is_submitted = True diff --git a/api/app/reviews/tests.py b/api/app/reviews/tests.py index 67965fdee..9dbf1d182 100644 --- a/api/app/reviews/tests.py +++ b/api/app/reviews/tests.py @@ -332,7 +332,7 @@ def setup_one_reviewer_three_candidates_and_one_completed_review(self): db.session.add_all(response_reviewers) db.session.commit() - review_response = ReviewResponse(1, 1, 1, 'en') + review_response = ReviewResponse(1, 1, 1, 'en', False) review_response.submit() db.session.add(review_response) db.session.commit() @@ -423,10 +423,10 @@ def setup_multiple_reviewers_with_different_subsets_of_candidates_and_reviews_co db.session.commit() review_responses = [ - ReviewResponse(1, 2, 2, 'en'), - ReviewResponse(1, 3, 1, 'en'), - ReviewResponse(1, 3, 2, 'en'), - ReviewResponse(1, 4, 1, 'en') + ReviewResponse(1, 2, 2, 'en', False), + ReviewResponse(1, 3, 1, 'en', False), + ReviewResponse(1, 3, 2, 'en', False), + ReviewResponse(1, 4, 1, 'en', False) ] for rr in review_responses: rr.submit() @@ -556,7 +556,7 @@ def setup_review_response(self): db.session.add(answer) db.session.commit() - self.review_response = ReviewResponse(1, 1, 1, 'en') + self.review_response = ReviewResponse(1, 1, 1, 'en', False) self.review_response.review_scores.append(ReviewScore(1, 'answer1')) self.review_response.review_scores.append(ReviewScore(2, 'answer2')) db.session.add(self.review_response) @@ -583,7 +583,7 @@ def test_review_response(self): def test_prevent_saving_review_response_reviewer_was_not_assigned_to_response(self): self.seed_static_data() - params = json.dumps({'review_form_id': 1, 'response_id': 1, 'scores': [{'review_question_id': 1, 'value': 'test_answer'}], 'language': 'en', 'is_submitted': False}) + params = json.dumps({'review_form_id': 1, 'response_id': 1, 'scores': [{'review_question_id': 1, 'value': 'test_answer'}], 'language': 'en', 'is_submitted': False, 'is_private': False}) header = self.get_auth_header_for('r1@r.com') response = self.app.post('/api/v1/reviewresponse', headers=header, data=params, content_type='application/json') @@ -593,7 +593,7 @@ def test_prevent_saving_review_response_reviewer_was_not_assigned_to_response(se def test_can_still_submit_inactive_response_reviewer(self): self.seed_static_data() self.setup_one_reviewer_three_candidates() - params = json.dumps({'review_form_id': 1, 'response_id': 2, 'scores': [{'review_question_id': 1, 'value': 'test_answer'}], 'language': 'en', 'is_submitted': True}) + params = json.dumps({'review_form_id': 1, 'response_id': 2, 'scores': [{'review_question_id': 1, 'value': 'test_answer'}], 'language': 'en', 'is_submitted': True, 'is_private': False}) header = self.get_auth_header_for('r1@r.com') response = self.app.post('/api/v1/reviewresponse', headers=header, data=params, content_type='application/json') @@ -610,7 +610,7 @@ def setup_response_reviewer(self): def test_saving_review_response(self): self.seed_static_data() self.setup_response_reviewer() - params = json.dumps({'review_form_id': 1, 'response_id': 1, 'scores': [{'review_question_id': 1, 'value': 'test_answer'}], 'language': 'en', 'is_submitted': False}) + params = json.dumps({'review_form_id': 1, 'response_id': 1, 'scores': [{'review_question_id': 1, 'value': 'test_answer'}], 'language': 'en', 'is_submitted': False, 'is_private': False}) header = self.get_auth_header_for('r1@r.com') response = self.app.post('/api/v1/reviewresponse', headers=header, data=params, content_type='application/json') @@ -627,7 +627,7 @@ def setup_existing_review_response(self): db.session.add(response_reviewer) db.session.commit() - review_response = ReviewResponse(1, 1, 1, 'en') + review_response = ReviewResponse(1, 1, 1, 'en', False) review_response.submit() review_response.review_scores = [ReviewScore(1, 'test_answer1'), ReviewScore(2, 'test_answer2')] db.session.add(review_response) @@ -636,7 +636,7 @@ def setup_existing_review_response(self): def test_updating_review_response(self): self.seed_static_data() self.setup_existing_review_response() - params = json.dumps({'review_form_id': 1, 'response_id': 1, 'scores': [{'review_question_id': 1, 'value': 'test_answer3'}, {'review_question_id': 2, 'value': 'test_answer4'}], 'language': 'en', 'is_submitted': True}) + params = json.dumps({'review_form_id': 1, 'response_id': 1, 'scores': [{'review_question_id': 1, 'value': 'test_answer3'}, {'review_question_id': 2, 'value': 'test_answer4'}], 'language': 'en', 'is_submitted': True, 'is_private': False}) header = self.get_auth_header_for('r1@r.com') response = self.app.put('/api/v1/reviewresponse', headers=header, data=params, content_type='application/json') @@ -829,13 +829,13 @@ def setup_count_reviews_allocated_and_completed(self): db.session.add_all(response_reviewers) # review form, review_user_id, response_id review_responses = [ - ReviewResponse(1, 3, 2, 'en'), - ReviewResponse(1, 3, 4, 'en'), - ReviewResponse(1, 2, 1, 'en'), - ReviewResponse(1, 2, 3, 'en'), - ReviewResponse(1, 2, 4, 'en'), - ReviewResponse(2, 1, 5, 'en'), - ReviewResponse(2, 2, 6, 'en') + ReviewResponse(1, 3, 2, 'en', False), + ReviewResponse(1, 3, 4, 'en', False), + ReviewResponse(1, 2, 1, 'en', False), + ReviewResponse(1, 2, 3, 'en', False), + ReviewResponse(1, 2, 4, 'en', False), + ReviewResponse(2, 1, 5, 'en', False), + ReviewResponse(2, 2, 6, 'en', False) ] db.session.add_all(review_responses) @@ -916,11 +916,11 @@ def setup_reviewer_responses_finalverdict_reviewquestion_reviewresponses_and_sco db.session.commit() review_responses = [ - ReviewResponse(1,3,1, 'en'), - ReviewResponse(1,3,2, 'en'), - ReviewResponse(1,2,1, 'en'), - ReviewResponse(1,2,2, 'en'), - ReviewResponse(1,3,3, 'en') + ReviewResponse(1,3,1, 'en', False), + ReviewResponse(1,3,2, 'en', False), + ReviewResponse(1,2,1, 'en', False), + ReviewResponse(1,2,2, 'en', False), + ReviewResponse(1,3,3, 'en', False) ] review_responses[0].review_scores = [ReviewScore(1, '23'), ReviewScore(5, '1')] review_responses[1].review_scores = [ReviewScore(1, '55'), ReviewScore(5, '2')] @@ -1031,9 +1031,9 @@ def setup_reviewresponses_with_unordered_timestamps(self): self.add_response(1, 6, is_submitted=True) self.add_response(1, 7, is_submitted=True) - review_response_1 = ReviewResponse(1,3,1, 'en') - review_response_2 = ReviewResponse(1,3,2, 'en') - review_response_3 = ReviewResponse(1,3,3, 'en') + review_response_1 = ReviewResponse(1,3,1, 'en', False) + review_response_2 = ReviewResponse(1,3,2, 'en', False) + review_response_3 = ReviewResponse(1,3,3, 'en', False) review_response_1.submitted_timestamp = datetime(2019, 1, 1) review_response_2.submitted_timestamp = datetime(2018, 1, 1) review_response_3.submitted_timestamp = datetime(2018, 6, 6) @@ -1142,8 +1142,8 @@ def setup_two_extra_responses_for_reviewer3(self): self.add_response(1, 1, is_submitted=True) review_responses = [ - ReviewResponse(1,3,4, 'en'), - ReviewResponse(1,3,5, 'en') + ReviewResponse(1,3,4, 'en', False), + ReviewResponse(1,3,5, 'en', False) ] review_responses[0].review_scores = [ReviewScore(1, '89'), ReviewScore(5, 'Maybe')] review_responses[1].review_scores = [ReviewScore(1, '75'), ReviewScore(5, 'Yes')] diff --git a/api/app/utils/testing.py b/api/app/utils/testing.py index 2e6b2ef15..fa17d20e3 100644 --- a/api/app/utils/testing.py +++ b/api/app/utils/testing.py @@ -420,7 +420,7 @@ def add_answer(self, response_id, question_id, answer_value): return answer def add_review_response(self, reviewer_user_id, response_id, review_form_id=1, language='en', is_submitted=False): - rr = ReviewResponse(review_form_id, reviewer_user_id, response_id, language) + rr = ReviewResponse(review_form_id, reviewer_user_id, response_id, language, False) if is_submitted: rr.submit() db.session.add(rr) @@ -539,4 +539,4 @@ def add_offer_invoice(self, invoice_id, offer_id): def add_invoice_payment_intent(self, invoice_id, payment_intent="pi_3L7GhOEpDzoopUbL0jGJhE2i"): invoice = db.session.query(Invoice).get(invoice_id) invoice.add_payment_intent(payment_intent) - db.session.commit() \ No newline at end of file + db.session.commit() diff --git a/api/migrations/versions/5d5b0524b4fa_add_parent_id_to_response.py b/api/migrations/versions/5d5b0524b4fa_add_parent_id_to_response.py index 937b0369c..c408ff695 100644 --- a/api/migrations/versions/5d5b0524b4fa_add_parent_id_to_response.py +++ b/api/migrations/versions/5d5b0524b4fa_add_parent_id_to_response.py @@ -1,13 +1,13 @@ """ Revision ID: 5d5b0524b4fa -Revises: 627a96dad7e8 +Revises: 0edb89e87e72 Create Date: 2023-05-27 07:46:16.735763 """ # revision identifiers, used by Alembic. revision = '5d5b0524b4fa' -down_revision = '627a96dad7e8' +down_revision = '0edb89e87e72' from alembic import op import sqlalchemy as sa diff --git a/api/migrations/versions/6d2e1dc65ef6_add_is_private_to_review_response.py b/api/migrations/versions/6d2e1dc65ef6_add_is_private_to_review_response.py new file mode 100644 index 000000000..426a3591c --- /dev/null +++ b/api/migrations/versions/6d2e1dc65ef6_add_is_private_to_review_response.py @@ -0,0 +1,26 @@ +""" +Adds a field called ``is_private`` to review_response table. +It is used to specify whether the response from the reviewer is public, +or private. If it is private, then only the meta-reviewer can see the response. + +Revision ID: 6d2e1dc65ef6 +Revises: 5d5b0524b4fa +Create Date: 2023-08-24 19:44:58.051586 + +""" + +# revision identifiers, used by Alembic. +revision = '6d2e1dc65ef6' +down_revision = '5d5b0524b4fa' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('review_response', sa.Column('is_private', sa.Boolean(), nullable=True)) + op.execute("UPDATE review_response SET is_private = false") + op.alter_column('review_response', 'is_private', nullable=False) + +def downgrade(): + op.drop_column('review_response', 'is_private') diff --git a/api/migrations/versions/9143756e596d_merge_parent_id_and_offer_email_templates.py b/api/migrations/versions/9143756e596d_merge_parent_id_and_offer_email_templates.py deleted file mode 100644 index ae9a5d0a6..000000000 --- a/api/migrations/versions/9143756e596d_merge_parent_id_and_offer_email_templates.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Merge migration that combines parent_id migration with latest -migrations on develop branch - -Revision ID: 9143756e596d -Revises: ('0edb89e87e72', '5d5b0524b4fa') -Create Date: 2023-07-11 07:19:19.202264 - -""" - -# revision identifiers, used by Alembic. -revision = '9143756e596d' -down_revision = ('0edb89e87e72', '5d5b0524b4fa') - -from alembic import op -import sqlalchemy as sa - - -def upgrade(): - pass - - -def downgrade(): - pass diff --git a/app/invoice/invoice.pdf b/app/invoice/invoice.pdf index cb07fbe03..f457ad390 100644 Binary files a/app/invoice/invoice.pdf and b/app/invoice/invoice.pdf differ