Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new changes #1307

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion api/app/events/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def get_event_status(event_id, user_id):
else:
application_status = 'Not Submitted'

outcome = outcome_repository.get_latest_by_user_for_event(user_id, event_id)
outcome = outcome_repository.get_latest_by_user_for_event_response(user_id,None, event_id)
if outcome is None:
outcome_status = None
else:
Expand Down
2 changes: 1 addition & 1 deletion api/app/offer/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def post(self, event_id):
if existing_offer:
return errors.DUPLICATE_OFFER

existing_outcome = outcome_repository.get_latest_by_user_for_event(user_id, event_id)
existing_outcome = outcome_repository.get_latest_by_user_for_event_response(user_id,None, event_id)
if existing_outcome:
if existing_outcome.status == Status.REJECTED:
return errors.CANDIDATE_REJECTED
Expand Down
2 changes: 1 addition & 1 deletion api/app/offer/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_create_offer(self):
self.assertEqual(response.status_code, 201)
self.assertFalse(data['payment_required'])

outcome = outcome_repository.get_latest_by_user_for_event(OFFER_DATA['user_id'], OFFER_DATA['event_id'])
outcome = outcome_repository.get_latest_by_user_for_event_response(OFFER_DATA['user_id'],None, OFFER_DATA['event_id'])
self.assertEqual(outcome.status, Status.ACCEPTED)

def test_create_offer_with_template(self):
Expand Down
38 changes: 35 additions & 3 deletions api/app/outcome/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from app.outcome.repository import OutcomeRepository as outcome_repository
from app.events.repository import EventRepository as event_repository
from app.users.repository import UserRepository as user_repository
from app.responses.repository import ResponseRepository as response_repository
from app.utils.emailer import email_user

from app.utils.auth import auth_required, event_admin_required
Expand All @@ -28,6 +29,27 @@ def _extract_status(outcome):
'timestamp': fields.DateTime(dt_format='iso8601'),
}

answer_fields = {
'id': fields.Integer,
'question_id': fields.Integer,
'question': fields.String(attribute='question.headline'),
'value': fields.String(attribute='value_display'),
'question_type': fields.String(attribute='question.type')
}

response_fields = {
'id': fields.Integer,
'application_form_id': fields.Integer,
'user_id': fields.Integer,
'is_submitted': fields.Boolean,
'submitted_timestamp': fields.DateTime(dt_format='iso8601'),
'is_withdrawn': fields.Boolean,
'withdrawn_timestamp': fields.DateTime(dt_format='iso8601'),
'started_timestamp': fields.DateTime(dt_format='iso8601'),
'answers': fields.List(fields.Nested(answer_fields))
}


user_fields = {
'id': fields.Integer,
'email': fields.String,
Expand All @@ -41,6 +63,7 @@ def _extract_status(outcome):
'status': fields.String(attribute=_extract_status),
'timestamp': fields.DateTime(dt_format='iso8601'),
'user': fields.Nested(user_fields),
'response': fields.Nested(response_fields),
'updated_by_user': fields.Nested(user_fields)
}

Expand All @@ -51,12 +74,14 @@ class OutcomeAPI(restful.Resource):
def get(self, event_id):
req_parser = reqparse.RequestParser()
req_parser.add_argument('user_id', type=int, required=True)
req_parser.add_argument('response_id', type=int, required=True)
args = req_parser.parse_args()


response_id=args['response_id']
user_id = args['user_id']

try:
outcome = outcome_repository.get_latest_by_user_for_event(user_id, event_id)
outcome = outcome_repository.get_latest_by_user_for_event_response(user_id,response_id, event_id)
if not outcome:
return errors.OUTCOME_NOT_FOUND

Expand All @@ -74,6 +99,7 @@ def get(self, event_id):
def post(self, event_id):
req_parser = reqparse.RequestParser()
req_parser.add_argument('user_id', type=int, required=True)
req_parser.add_argument('response_id', type=int, required=True)
req_parser.add_argument('outcome', type=str, required=True)
args = req_parser.parse_args()

Expand All @@ -84,6 +110,11 @@ def post(self, event_id):
user = user_repository.get_by_id(args['user_id'])
if not user:
return errors.USER_NOT_FOUND

response = response_repository.get_by_id_and_user_id(args['response_id'],args['user_id'])
if not response:
return errors.RESPONSE_NOT_FOUND


try:
status = Status[args['outcome']]
Expand All @@ -92,14 +123,15 @@ def post(self, event_id):

try:
# Set existing outcomes to no longer be the latest outcome
existing_outcomes = outcome_repository.get_all_by_user_for_event(args['user_id'], event_id)
existing_outcomes = outcome_repository.get_all_by_user_for_event_response(args['user_id'],args['response_id'], event_id)
for existing_outcome in existing_outcomes:
existing_outcome.reset_latest()

# Add new outcome
outcome = Outcome(
event_id,
args['user_id'],
args['response_id'],
status,
g.current_user['id'])

Expand Down
4 changes: 4 additions & 0 deletions api/app/outcome/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Outcome(db.Model):
id = db.Column(db.Integer(), primary_key = True, nullable = False)
event_id = db.Column(db.Integer(), db.ForeignKey('event.id'), nullable = False)
user_id = db.Column(db.Integer(), db.ForeignKey('app_user.id'), nullable = False)
response_id = db.Column(db.Integer(), db.ForeignKey('response.id'), nullable=True)
status = db.Column(db.Enum(Status, name='outcome_status'), nullable = False)
timestamp = db.Column(db.DateTime(), nullable = False)
latest = db.Column(db.Boolean(), nullable = False)
Expand All @@ -22,15 +23,18 @@ class Outcome(db.Model):
event = db.relationship('Event', foreign_keys=[event_id])
user = db.relationship('AppUser', foreign_keys=[user_id])
updated_by_user = db.relationship('AppUser', foreign_keys=[updated_by_user_id])
response = db.relationship('Response', foreign_keys=[response_id])

def __init__(self,
event_id,
user_id,
response_id,
status,
updated_by_user_id
):
self.event_id = event_id
self.user_id = user_id
self.response_id = response_id
self.status = status
self.timestamp = datetime.now()
self.latest = True
Expand Down
11 changes: 6 additions & 5 deletions api/app/outcome/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
class OutcomeRepository():

@staticmethod
def get_latest_by_user_for_event(user_id, event_id):
def get_latest_by_user_for_event_response(user_id,response_id,event_id):
outcome = (db.session.query(Outcome)
.filter_by(user_id=user_id, event_id=event_id, latest=True)
.filter_by(user_id=user_id,response_id=response_id, event_id=event_id, latest=True)
.first())
return outcome

@staticmethod
def get_all_by_user_for_event(user_id, event_id):
def get_all_by_user_for_event_response(user_id,response_id, event_id):
outcomes = (db.session.query(Outcome)
.filter_by(user_id=user_id, event_id=event_id)
.filter_by(user_id=user_id,response_id=response_id, event_id=event_id)
.all())
return outcomes

@staticmethod
def get_latest_for_event(event_id):
outcomes = (db.session.query(Outcome)
Expand All @@ -28,5 +28,6 @@ def get_latest_for_event(event_id):
@staticmethod
def add(outcome):
db.session.add(outcome)



8 changes: 4 additions & 4 deletions api/app/outcome/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ def seed_static_data(self):
def test_repository_get_latest_by_user_for_event(self):
"""Test that repository method gets the correct latest outcome for a user."""
self.seed_static_data()
result = outcome_repository.get_latest_by_user_for_event(self.test_user1.id, self.event1.id)
result = outcome_repository.get_latest_by_user_for_event_response(self.test_user1.id,None, self.event1.id)
self.assertEqual(result.id, self.event1_user1_outcome2.id)
self.assertEqual(result.status, Status.ACCEPTED)

result = outcome_repository.get_latest_by_user_for_event(self.test_user2.id, self.event1.id)
result = outcome_repository.get_latest_by_user_for_event_response(self.test_user2.id,None, self.event1.id)
self.assertEqual(result.id, self.event1_user2_outcome.id)
self.assertEqual(result.status, Status.REJECTED)

def test_get_all_by_user_for_event(self):
"""Test that repository method gets all outcomes for a user."""
self.seed_static_data()
result = outcome_repository.get_all_by_user_for_event(self.test_user1.id, self.event1.id)
result = outcome_repository.get_all_by_user_for_event_response(self.test_user1.id,None, self.event1.id)
self.assertEqual(len(result), 2)
self.assertCountEqual([o.id for o in result], [self.event1_user1_outcome1.id, self.event1_user1_outcome2.id])

Expand Down Expand Up @@ -138,7 +138,7 @@ def test_post(self):
data = json.loads(response.data)
self.assertEqual(response.status_code, 201)

outcomes = outcome_repository.get_all_by_user_for_event(self.test_user1_id, self.event2.id)
outcomes = outcome_repository.get_all_by_user_for_event_response(self.test_user1_id,None, self.event2.id)
outcomes = list(sorted(outcomes, key=lambda o: o.timestamp))
print(outcomes)

Expand Down
14 changes: 10 additions & 4 deletions api/app/responses/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ def get(self):
responses = response_repository.get_all_for_user_application(current_user_id, form.id)

# TODO: Link outcomes to responses rather than events to cater for multiple submissions.
outcome = outcome_repository.get_latest_by_user_for_event(current_user_id, event_id)
for response in responses:
outcome = outcome_repository.get_latest_by_user_for_event_response(current_user_id,response.id, event_id)

response.outcome = outcome

return marshal(responses, ResponseAPI.response_fields), 200
Expand All @@ -136,6 +137,10 @@ def post(self):
is_submitted = args['is_submitted']
application_form_id = args['application_form_id']
language = args['language']
parent_id = args.get('parent_id', None)
allow_multiple_submissions = args['multiple_submission']
print(args)

if len(language) != 2:
language = 'en' # Fallback to English if language doesn't look like an ISO 639-1 code

Expand All @@ -146,10 +151,10 @@ def post(self):
user = user_repository.get_by_id(user_id)
responses = response_repository.get_all_for_user_application(user_id, application_form_id)

if not application_form.nominations and len(responses) > 0:
if not allow_multiple_submissions and len(responses) > 0:
return errors.RESPONSE_ALREADY_SUBMITTED

response = Response(application_form_id, user_id, language)
response = Response(application_form_id, user_id, language,parent_id)
response_repository.save(response)

answers = []
Expand Down Expand Up @@ -375,6 +380,7 @@ def get(self, event_id):
language = args['language']

responses = response_repository.get_all_for_event(event_id, not include_unsubmitted)


review_config = review_configuration_repository.get_configuration_for_event(event_id)
required_reviewers = 1 if review_config is None else review_config.num_reviews_required + review_config.num_optional_reviews
Expand Down Expand Up @@ -624,5 +630,5 @@ def _get_files(application_form, answers):
as_attachment=True,
attachment_filename=f"response_{response.id}.zip"
)


2 changes: 2 additions & 0 deletions api/app/responses/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class ResponseMixin(object):
post_req_parser.add_argument('application_form_id', type=int, required=True)
post_req_parser.add_argument('answers', type=list, required=True, location='json')
post_req_parser.add_argument('language', type=str, required=True)
post_req_parser.add_argument('parent_id', type=int, required=True)
post_req_parser.add_argument('multiple_submission', type=bool, required=True)

put_req_parser = reqparse.RequestParser()
put_req_parser.add_argument('id', type=int, required=True)
Expand Down
2 changes: 1 addition & 1 deletion api/app/responses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __init__(self, application_form_id, user_id, language, parent_id=None):
self.submitted_timestamp = None
self.is_withdrawn = False
self.withdrawn_timestamp = None
self.started_timestamp = date.today()
self.started_timestamp = datetime.now()
self.language = language
self.parent_id = parent_id

Expand Down
1 change: 1 addition & 0 deletions api/migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# A generic, single database configuration.

[alembic]

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

Expand Down
42 changes: 42 additions & 0 deletions api/migrations/versions/4923563b3a63_add_reponseID_outcome.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
""" Add foreign key response_id to outcome

Revision ID: 4923563b3a63
Revises: bf4cb562e36c
Create Date: 2024-12-07 19:07:18.021334

"""

# revision identifiers, used by Alembic.
revision = '4923563b3a63'
down_revision = 'bf4cb562e36c'


from alembic import op
import sqlalchemy as sa


def upgrade():
op.add_column(
'outcome',
sa.Column('response_id', sa.Integer(), nullable=True)
)

op.create_foreign_key(
'fk_outcome_response_id',
'outcome',
'response',
['response_id'],
['id']
)


def downgrade():
op.drop_constraint(
'fk_outcome_response_id',
'outcome',
type_='foreignkey'
)

op.drop_column('outcome', 'response_id')


2 changes: 1 addition & 1 deletion api/migrations/versions/a4662031beca_add_outcome_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('outcome')
outcome_status.drop(op.get_bind())
# ### end Alembic commands ###
# ### end Alembic commands ###
38 changes: 38 additions & 0 deletions api/migrations/versions/bf4cb562e36c_add_new_status_outcome.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
""" Add new status outcome

Revision ID: bf4cb562e36c
Revises: 062abca36566
Create Date: 2024-12-07 19:05:30.962104

"""
# revision identifiers, used by Alembic.
revision = 'bf4cb562e36c'
down_revision = '062abca36566'

from alembic import op
import sqlalchemy as sa


def upgrade():
op.execute("ALTER TYPE outcome_status ADD VALUE 'REVIEW';")
op.execute("ALTER TYPE outcome_status ADD VALUE 'ACCEPT_W_REVISION';")
op.execute("ALTER TYPE outcome_status ADD VALUE 'REJECT_W_ENCOURAGEMENT';")


def downgrade():
op.execute("""
DO $$
BEGIN
ALTER TYPE outcome_status RENAME TO outcome_status_old;

CREATE TYPE outcome_status AS ENUM('ACCEPTED', 'REJECTED', 'WAITLIST');

ALTER TABLE outcome
ALTER COLUMN status
TYPE outcome_status
USING status::text::outcome_status;

DROP TYPE outcome_status_old;
END $$;
""")

Loading