From 08526cec9673e21bbfd9b5e264f66c8730637abb Mon Sep 17 00:00:00 2001 From: Maya Treacy Date: Sun, 31 May 2020 10:03:17 +1000 Subject: [PATCH 1/4] MS Backend server for BIT development purpose replace db uri --- app/api/dao/user.py | 8 ++-- app/api/models/admin.py | 2 +- app/api/models/user.py | 6 +-- app/api/resources/admin.py | 2 +- app/api/resources/user.py | 10 ++--- app/api/validations/user.py | 8 ++-- app/database/models/mentorship_relation.py | 16 +++---- app/database/models/task_comment.py | 20 ++++----- app/database/models/user.py | 4 +- config.py | 45 ++++++++++--------- run.py | 14 +++--- tests/test_app_config.py | 6 --- tests/users/test_dao_update_user.py | 16 +++---- ...est_validation_update_user_request_data.py | 4 +- 14 files changed, 82 insertions(+), 79 deletions(-) diff --git a/app/api/dao/user.py b/app/api/dao/user.py index fe387e01a..ce7296cb5 100644 --- a/app/api/dao/user.py +++ b/app/api/dao/user.py @@ -259,11 +259,11 @@ def update_user_profile(user_id: int, data: Dict[str, str]): else: user.occupation = None - if "organization" in data: - if data["organization"]: - user.organization = data["organization"] + if "current_organization" in data: + if data["current_organization"]: + user.current_organization = data["current_organization"] else: - user.organization = None + user.current_organization = None if "slack_username" in data: if data["slack_username"]: diff --git a/app/api/models/admin.py b/app/api/models/admin.py index a420a2293..b7e3afb9f 100644 --- a/app/api/models/admin.py +++ b/app/api/models/admin.py @@ -32,7 +32,7 @@ def add_models_to_namespace(api_namespace): "bio": fields.String(required=True, description="User bio"), "location": fields.String(required=True, description="User location"), "occupation": fields.String(required=True, description="User occupation"), - "organization": fields.String(required=True, description="User organization"), + "current_organization": fields.String(required=True, description="User current organization"), "skills": fields.String(required=True, description="User skills"), }, ) diff --git a/app/api/models/user.py b/app/api/models/user.py index 0e64af608..9684e517a 100644 --- a/app/api/models/user.py +++ b/app/api/models/user.py @@ -47,7 +47,7 @@ def add_models_to_namespace(api_namespace): "bio": fields.String(required=True, description="User bio"), "location": fields.String(required=True, description="User location"), "occupation": fields.String(required=True, description="User occupation"), - "organization": fields.String(required=True, description="User organization"), + "current_organization": fields.String(required=True, description="User current_organization"), "interests": fields.String(required=True, description="User interests"), "skills": fields.String(required=True, description="User skills"), "need_mentoring": fields.Boolean( @@ -91,7 +91,7 @@ def add_models_to_namespace(api_namespace): "bio": fields.String(required=False, description="User bio"), "location": fields.String(required=False, description="User location"), "occupation": fields.String(required=False, description="User occupation"), - "organization": fields.String(required=False, description="User organization"), + "current_organization": fields.String(required=False, description="User current_organization"), "slack_username": fields.String( required=False, description="User slack username" ), @@ -179,7 +179,7 @@ def add_models_to_namespace(api_namespace): "bio": fields.String(required=False, description="User bio"), "location": fields.String(required=False, description="User location"), "occupation": fields.String(required=False, description="User occupation"), - "organization": fields.String(required=False, description="User organization"), + "current_organization": fields.String(required=False, description="User current_organization"), "slack_username": fields.String( required=False, description="User slack username" ), diff --git a/app/api/resources/admin.py b/app/api/resources/admin.py index b406d1d5d..75d827bfd 100644 --- a/app/api/resources/admin.py +++ b/app/api/resources/admin.py @@ -98,7 +98,7 @@ def get(cls): A admin user with valid access token can view the list of all admins. The endpoint doesn't take any other input. A JSON array having an object for each admin user is returned. The array contains id, username, name, slack_username, bio, - location, occupation, organization, skills. + location, occupation, current_organization, skills. The current admin user's details are not returned. """ user_id = get_jwt_identity() diff --git a/app/api/resources/user.py b/app/api/resources/user.py index a1d463cb9..012823056 100644 --- a/app/api/resources/user.py +++ b/app/api/resources/user.py @@ -63,8 +63,8 @@ def get(cls): A user with valid access token can view the list of users. The endpoint doesn't take any other input. A JSON array having an object for each user is returned. The array contains id, username, name, slack_username, bio, - location, occupation, organization, interests, skills, need_mentoring, - available_to_mentor, registration_date. The current user's details are not returned. + location, occupation, current_organization, interests, skills, need_mentoring, + available_to_mentor. The current user's details are not returned. """ page = request.args.get("page", default=UserDAO.DEFAULT_PAGE, type=int) @@ -158,7 +158,7 @@ def put(cls): A user with valid access token can use this endpoint to edit his/her own user details. The endpoint takes any of the given parameters (name, username, - bio, location, occupation, organization, slack_username, social_media_links, + bio, location, occupation, current_organization, slack_username, social_media_links, skills, interests, resume_url, photo_url, need_mentoring, available_to_mentor). The response contains a success message. """ @@ -269,7 +269,7 @@ def get(cls): A user with valid access token can view the list of verified users. The endpoint doesn't take any other input. A JSON array having an object for each user is returned. The array contains id, username, name, slack_username, bio, - location, occupation, organization, interests, skills, need_mentoring, + location, occupation, current_organization, interests, skills, need_mentoring, available_to_mentor. The current user's details are not returned. """ @@ -337,7 +337,7 @@ def post(cls): if result[1] is HTTPStatus.CREATED: send_email_verification_message(data["name"], data["email"]) - + print(result[0]) return result diff --git a/app/api/validations/user.py b/app/api/validations/user.py index a88ee6ead..fd2d77dd8 100644 --- a/app/api/validations/user.py +++ b/app/api/validations/user.py @@ -162,13 +162,13 @@ def validate_update_profile_request_data(data): if not is_valid[0]: return is_valid[1] - organization = data.get("organization", None) - if organization: + current_organization = data.get("current_organization", None) + if current_organization: is_valid = validate_length( - len(get_stripped_string(organization)), + len(get_stripped_string(current_organization)), 0, ORGANIZATION_MAX_LENGTH, - "organization", + "current_organization", ) if not is_valid[0]: return is_valid[1] diff --git a/app/database/models/mentorship_relation.py b/app/database/models/mentorship_relation.py index d3d4be6cc..a6fcfb89c 100644 --- a/app/database/models/mentorship_relation.py +++ b/app/database/models/mentorship_relation.py @@ -16,10 +16,10 @@ class MentorshipRelationModel(db.Model): action_user_id: integer indicates id of action user. mentor: relationship between UserModel and mentorship_relation. mentee: relationship between UserModel and mentorship_relation. - creation_date: float that defines the date of creation of the mentorship. - accept_date: float that indicates the date of acceptance of mentorship. - start_date: float that indicates the starting date of mentorship. - end_date: float that indicates the ending date of mentorship. + creation_date: numeric that defines the date of creation of the mentorship. + accept_date: numeric that indicates the date of acceptance of mentorship. + start_date: numeric that indicates the starting date of mentorship. + end_date: numeric that indicates the ending date of mentorship. state: enumeration that indicates state of mentorship. notes: string that indicates any notes. tasks_list_id: integer indicates the id of the tasks_list @@ -47,10 +47,10 @@ class MentorshipRelationModel(db.Model): primaryjoin="MentorshipRelationModel.mentee_id == UserModel.id", ) - creation_date = db.Column(db.Float, nullable=False) - accept_date = db.Column(db.Float) - start_date = db.Column(db.Float) - end_date = db.Column(db.Float) + creation_date = db.Column(db.Numeric("16,6", asdecimal=False), nullable=False) + accept_date = db.Column(db.Numeric("16,6", asdecimal=False)) + start_date = db.Column(db.Numeric("16,6", asdecimal=False)) + end_date = db.Column(db.Numeric("16,6", asdecimal=False)) state = db.Column(db.Enum(MentorshipRelationState), nullable=False) notes = db.Column(db.String(400)) diff --git a/app/database/models/task_comment.py b/app/database/models/task_comment.py index d2f532496..62b5a28f4 100644 --- a/app/database/models/task_comment.py +++ b/app/database/models/task_comment.py @@ -7,14 +7,14 @@ class TaskCommentModel(db.Model): """Defines attributes for the task comment. - Attributes: - task_id: An integer for storing the task's id. - user_id: An integer for storing the user's id. - relation_id: An integer for storing the relation's id. - creation_date: A float indicating comment's creation date. - modification_date: A float indicating the modification date. - comment: A string indicating the comment. - """ + Attributes: + task_id: An integer for storing the task's id. + user_id: An integer for storing the user's id. + relation_id: An integer for storing the relation's id. + creation_date: A numeric indicating comment's creation date. + modification_date: A numeric indicating the modification date. + comment: A string indicating the comment. + """ # Specifying database table used for TaskCommentModel __tablename__ = "tasks_comments" @@ -24,8 +24,8 @@ class TaskCommentModel(db.Model): user_id = db.Column(db.Integer, db.ForeignKey("users.id")) task_id = db.Column(db.Integer, db.ForeignKey("tasks_list.id")) relation_id = db.Column(db.Integer, db.ForeignKey("mentorship_relations.id")) - creation_date = db.Column(db.Float, nullable=False) - modification_date = db.Column(db.Float) + creation_date = db.Column(db.Numeric("16,6", asdecimal=False), nullable=False) + modification_date = db.Column(db.Numeric("16,6", asdecimal=False)) comment = db.Column(db.String(COMMENT_MAX_LENGTH), nullable=False) def __init__(self, user_id, task_id, relation_id, comment): diff --git a/app/database/models/user.py b/app/database/models/user.py index 943ef0d21..02f250ff4 100644 --- a/app/database/models/user.py +++ b/app/database/models/user.py @@ -46,7 +46,7 @@ class UserModel(db.Model): bio = db.Column(db.String(500)) location = db.Column(db.String(80)) occupation = db.Column(db.String(80)) - organization = db.Column(db.String(80)) + current_organization = db.Column(db.String(80)) slack_username = db.Column(db.String(80)) social_media_links = db.Column(db.String(500)) skills = db.Column(db.String(500)) @@ -97,7 +97,7 @@ def json(self): "bio": self.bio, "location": self.location, "occupation": self.occupation, - "organization": self.organization, + "current_organization": self.current_organization, "slack_username": self.slack_username, "social_media_links": self.social_media_links, "skills": self.skills, diff --git a/config.py b/config.py index 5d655f9de..c3cc668f4 100644 --- a/config.py +++ b/config.py @@ -42,6 +42,7 @@ class BaseConfig(object): DB_PASSWORD = os.getenv("DB_PASSWORD") DB_ENDPOINT = os.getenv("DB_ENDPOINT") DB_NAME = os.getenv("DB_NAME") + DB_TEST_NAME = os.getenv("DB_TEST_NAME") UNVERIFIED_USER_THRESHOLD = 2592000 # 30 days @@ -76,23 +77,26 @@ class BaseConfig(object): # mail accounts MAIL_DEFAULT_SENDER = os.getenv("MAIL_DEFAULT_SENDER") + @staticmethod def build_db_uri( - db_type_arg=DB_TYPE, - db_user_arg=DB_USERNAME, - db_password_arg=DB_PASSWORD, - db_endpoint_arg=DB_ENDPOINT, - db_name_arg=DB_NAME, + db_type_arg = os.getenv("DB_TYPE"), + db_user_arg = os.getenv("DB_USERNAME"), + db_password_arg = os.getenv("DB_PASSWORD"), + db_endpoint_arg = os.getenv("DB_ENDPOINT"), + db_name_arg = os.getenv("DB_NAME"), ): - """Build remote database uri using specific environment variables.""" - - return "{db_type}://{db_user}:{db_password}@{db_endpoint}/{db_name}".format( - db_type=db_type_arg, - db_user=db_user_arg, - db_password=db_password_arg, - db_endpoint=db_endpoint_arg, - db_name=db_name_arg, - ) + return f"{db_type_arg}://{db_user_arg}:{db_password_arg}@{db_endpoint_arg}/{db_name_arg}" + + @staticmethod + def build_db_test_uri( + db_type_arg = os.getenv("DB_TYPE"), + db_user_arg = os.getenv("DB_USERNAME"), + db_password_arg = os.getenv("DB_PASSWORD"), + db_endpoint_arg = os.getenv("DB_ENDPOINT"), + db_name_arg = os.getenv("DB_TEST_NAME"), + ): + return f"{db_type_arg}://{db_user_arg}:{db_password_arg}@{db_endpoint_arg}/{db_name_arg}" class ProductionConfig(BaseConfig): @@ -122,10 +126,10 @@ class LocalConfig(BaseConfig): DEBUG = True - # Using a local sqlite database - SQLALCHEMY_DATABASE_URI = "sqlite:///local_data.db" - - + # Using a local postgre database + SQLALCHEMY_DATABASE_URI = "postgresql:///bit_schema" + # SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_uri() + class TestingConfig(BaseConfig): """Testing configuration.""" @@ -133,9 +137,10 @@ class TestingConfig(BaseConfig): MOCK_EMAIL = True # Use in-memory SQLite database for testing - SQLALCHEMY_DATABASE_URI = "sqlite://" - + SQLALCHEMY_DATABASE_URI = "postgresql:///bit_schema_test" + # SQLALCHEMY_DATABASE_URI = BaseConfig.build_db_test_uri() + def get_env_config() -> str: flask_config_name = os.getenv("FLASK_ENVIRONMENT_CONFIG", "dev") if flask_config_name not in ["prod", "test", "dev", "local", "stag"]: diff --git a/run.py b/run.py index 6f7ab83fe..63a208582 100644 --- a/run.py +++ b/run.py @@ -1,7 +1,9 @@ from flask import Flask from config import get_env_config from flask_migrate import Migrate +from flask_cors import CORS +cors = CORS() def create_app(config_filename: str) -> Flask: app = Flask(__name__) @@ -16,6 +18,8 @@ def create_app(config_filename: str) -> Flask: migrate = Migrate(app, db) + cors.init_app(app, resources={r"*": {"origins": "http:localhost:5000"}}) + from app.api.jwt_extension import jwt jwt.init_app(app) @@ -38,12 +42,12 @@ def create_app(config_filename: str) -> Flask: application = create_app(get_env_config()) -@application.before_first_request -def create_tables(): - from app.database.sqlalchemy_extension import db +# @application.before_first_request +# def create_tables(): +# from app.database.sqlalchemy_extension import db - db.create_all() + # db.create_all() if __name__ == "__main__": - application.run(port=5000) + application.run(port=4000) diff --git a/tests/test_app_config.py b/tests/test_app_config.py index 6f33475d0..5bfea9fac 100644 --- a/tests/test_app_config.py +++ b/tests/test_app_config.py @@ -24,7 +24,6 @@ def test_app_testing_config(self): self.assertFalse(application.config["DEBUG"]) self.assertTrue(application.config["TESTING"]) self.assertFalse(application.config["SQLALCHEMY_TRACK_MODIFICATIONS"]) - self.assertEqual("sqlite://", application.config["SQLALCHEMY_DATABASE_URI"]) self.assertIsNotNone(current_app) # testing JWT configurations @@ -89,7 +88,6 @@ def test_app_development_config(self): self.assertTrue(application.config["DEBUG"]) self.assertFalse(application.config["TESTING"]) self.assertFalse(application.config["SQLALCHEMY_TRACK_MODIFICATIONS"]) - # self.assertEqual('mysql_something', application.config['SQLALCHEMY_DATABASE_URI']) self.assertIsNotNone(current_app) # testing JWT configurations @@ -113,9 +111,6 @@ def test_app_development_config(self): self.assertTrue(application.config["DEBUG"]) self.assertFalse(application.config["TESTING"]) self.assertFalse(application.config["SQLALCHEMY_TRACK_MODIFICATIONS"]) - self.assertEqual( - "sqlite:///local_data.db", application.config["SQLALCHEMY_DATABASE_URI"] - ) self.assertIsNotNone(current_app) # testing JWT configurations @@ -143,7 +138,6 @@ def test_app_production_config(self): self.assertFalse(application.config["DEBUG"]) self.assertFalse(application.config["TESTING"]) self.assertFalse(application.config["SQLALCHEMY_TRACK_MODIFICATIONS"]) - # self.assertEqual('mysql_something', application.config['SQLALCHEMY_DATABASE_URI']) self.assertIsNotNone(current_app) # testing JWT configurations diff --git a/tests/users/test_dao_update_user.py b/tests/users/test_dao_update_user.py index c9fd422e3..abcbe8444 100644 --- a/tests/users/test_dao_update_user.py +++ b/tests/users/test_dao_update_user.py @@ -8,30 +8,30 @@ class TestUpdateUserDao(BaseTestCase): def test_dao_update_user(self): self.assertIsNone(self.admin_user.occupation) - self.assertIsNone(self.admin_user.organization) + self.assertIsNone(self.admin_user.current_organization) - data = dict(occupation="good_developer", organization="good_org") + data = dict(occupation="good_developer", current_organization="good_org") UserDAO.update_user_profile(self.admin_user.id, data) self.assertEqual("good_developer", self.admin_user.occupation) - self.assertEqual("good_org", self.admin_user.organization) + self.assertEqual("good_org", self.admin_user.current_organization) def test_update_fields_with_empty_data(self): self.assertIsNone(self.admin_user.occupation) - self.assertIsNone(self.admin_user.organization) + self.assertIsNone(self.admin_user.current_organization) - data = dict(occupation="good_developer", organization="good_org") + data = dict(occupation="good_developer", current_organization="good_org") UserDAO.update_user_profile(self.admin_user.id, data) self.assertEqual("good_developer", self.admin_user.occupation) - self.assertEqual("good_org", self.admin_user.organization) + self.assertEqual("good_org", self.admin_user.current_organization) - data = dict(occupation="", organization="") + data = dict(occupation="", current_organization="") UserDAO.update_user_profile(self.admin_user.id, data) self.assertIsNone(self.admin_user.occupation) - self.assertIsNone(self.admin_user.organization) + self.assertIsNone(self.admin_user.current_organization) if __name__ == "__main__": diff --git a/tests/users/test_validation_update_user_request_data.py b/tests/users/test_validation_update_user_request_data.py index a0dd7211f..103fa3586 100644 --- a/tests/users/test_validation_update_user_request_data.py +++ b/tests/users/test_validation_update_user_request_data.py @@ -45,8 +45,8 @@ def test_update_user_organization_request_data_validation(self): secure_random.choice(ascii_lowercase) for x in range(ORGANIZATION_MAX_LENGTH + 1) ) - field_name = "organization" - request_body = dict(organization=random_generated_organization) + field_name = "current_organization" + request_body = dict(current_organization=random_generated_organization) expected_result = { "message": get_length_validation_error_message( From 8988f980315889a83cac041857d20321fca39ca6 Mon Sep 17 00:00:00 2001 From: Maya Treacy Date: Sun, 3 Jan 2021 10:04:22 +1100 Subject: [PATCH 2/4] add Flask-Cors to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e994c9c3b..1d0862ed7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ APScheduler==3.6.3 coverage==5.3 Flask==1.0.2 +Flask-Cors==3.0.9 Flask-JWT-Extended==3.25.0 Flask-Mail==0.9.1 Flask-Migrate==2.5.3 From d89a3dc4739086ebb0ecc2f82a43f2c761d41015 Mon Sep 17 00:00:00 2001 From: decon-harsh Date: Wed, 3 Mar 2021 02:23:51 +0530 Subject: [PATCH 3/4] Create DAO and New Endpoints for Program Mentorship Relationship --- app/api/api_extension.py | 3 + app/api/dao/program_mentorship_relation.py | 414 ++++++++++++++++++ app/api/email_utils.py | 57 +++ app/api/models/mentorship_relation.py | 30 ++ .../resources/program_mentorship_relation.py | 229 ++++++++++ app/messages.py | 41 +- 6 files changed, 773 insertions(+), 1 deletion(-) create mode 100644 app/api/dao/program_mentorship_relation.py create mode 100644 app/api/resources/program_mentorship_relation.py diff --git a/app/api/api_extension.py b/app/api/api_extension.py index 8f5bce472..531450d4a 100644 --- a/app/api/api_extension.py +++ b/app/api/api_extension.py @@ -6,6 +6,7 @@ from app.api.resources.mentorship_relation import ( mentorship_relation_ns as mentorship_namespace, ) +from app.api.resources.program_mentorship_relation import program_mentorship_relation_ns as program_mentorship_namespace from app.api.resources.task import task_ns as task_namespace from app.api.resources.task_comment import task_comment_ns as task_comment_namespace @@ -52,6 +53,8 @@ def ioslink(): api.add_namespace(mentorship_namespace, path="/") +api.add_namespace(program_mentorship_namespace, path="/") + api.add_namespace(task_namespace, path="/") api.add_namespace(task_comment_namespace, path="/") diff --git a/app/api/dao/program_mentorship_relation.py b/app/api/dao/program_mentorship_relation.py new file mode 100644 index 000000000..65997d287 --- /dev/null +++ b/app/api/dao/program_mentorship_relation.py @@ -0,0 +1,414 @@ +from datetime import datetime, timedelta +from typing import Dict +from http import HTTPStatus +from app import messages +from app.database.models.mentorship_relation import MentorshipRelationModel +from app.database.models.tasks_list import TasksListModel +from app.database.models.user import UserModel +from app.utils.decorator_utils import email_verification_required +from app.utils.enum_utils import MentorshipRelationState + + +class ProgramMentorshipRelationDAO: + """Data Access Object for mentorship relation functionalities. + + Provides various functions pertaining to mentorship. + + Attributes: + MAXIMUM_MENTORSHIP_DURATION + MINIMUM_MENTORSHIP_DURATION + """ + + def create_program_mentorship_relation(self, user_id: int, org_rep_id:int , mentor_id:int, mentee_id:int, relation_id:int, data: Dict[str, str]): + """Creates a relationship between two users. + + Establishes the mentor-mentee relationship. + + Args: + user_id: ID of the user initiating this request. Has to be either the mentor or the mentee. + data: List containing the mentor_id, mentee_id, end_date_timestamp and notes. + + Returns: + message: A message corresponding to the completed action; success if mentorship relationship is established, failure if otherwise. + """ + + # First/Initial request where mentorship relationship gets created for the first time + if not relation_id: + # First request to mentor by program and viceversa + if not mentee_id: + action_user_id = user_id + end_date_timestamp = data["end_date"] + start_date_timestamp = data["start_date"] + notes = data["notes"] + + # user_id has to match either org_representative id or mentor_id + is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + # mentor_id has to be different from org_representative id + if mentor_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + try: + end_date_datetime = datetime.fromtimestamp(end_date_timestamp) + except ValueError: + return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST + + now_datetime = datetime.now() + if end_date_datetime < now_datetime: + return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST + + # validate if mentor user exists + mentor_user = UserModel.find_by_id(mentor_id) + if mentor_user is None: + return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + # validate if mentor is available to mentor + if not mentor_user.available_to_mentor: + return messages.MENTOR_NOT_AVAILABLE_TO_MENTOR, HTTPStatus.BAD_REQUEST + + org_rep_user = UserModel.find_by_id(org_rep_id) + if org_rep_user is None: + return messages.ORG_REP_DOES_NOT_EXIST, HTTPStatus.BAD_REQUEST + + + # TODO add tests for this portion + + all_mentor_relations = ( + mentor_user.mentor_relations + mentor_user.mentee_relations + ) + for relation in all_mentor_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + + # All validations were checked + + tasks_list = TasksListModel() + tasks_list.save_to_db() + + if action_user_id == org_rep_id: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentor_user=mentor_user, + mentee_user=None, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp + + else: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentor_user=mentor_user, + mentee_user=org_rep_user, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp + mentorship_relation.save_to_db() + + # First request to mentee by program and viceversa + elif not mentor_id: + action_user_id = user_id + end_date_timestamp = data["end_date"] + start_date_timestamp = data["start_date"] + notes = data["notes"] + + # user_id has to match either org_representative id or mentee_id + is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + # mentee_id has to be different from org_representative id + if mentee_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + try: + end_date_datetime = datetime.fromtimestamp(end_date_timestamp) + except ValueError: + return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST + + now_datetime = datetime.now() + if end_date_datetime < now_datetime: + return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST + + # validate if mentee user exists + mentee_user = UserModel.find_by_id(mentee_id) + if mentee_user is None: + return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + # validate if mentee is available to be mentored + if not mentee_user.need_mentoring: + return messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED, HTTPStatus.BAD_REQUEST + + org_rep_user = UserModel.find_by_id(org_rep_id) + if org_rep_user is None: + return messages.ORG_REP_DOES_NOT_EXIST, HTTPStatus.BAD_REQUEST + + + # TODO add tests for this portion + + all_mentee_relations = ( + mentee_user.mentor_relations + mentee_user.mentee_relations + ) + for relation in all_mentee_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + + # All validations were checked + + tasks_list = TasksListModel() + tasks_list.save_to_db() + + if action_user_id == org_rep_id: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentor_user=None, + mentee_user=mentee_user, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp + + else: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentor_user=org_rep_user, + mentee_user=mentee_user, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp + mentorship_relation.save_to_db() + + # Second/End requests where relationship is already present but with org_rep_user + elif mentor_id and mentee_id and relation_id: + if mentor_id == mentee_id: + return messages.MENTOR_ID_SAME_AS_MENTEE_ID, HTTPStatus.BAD_REQUEST + + action_user_id = user_id + end_date_timestamp = data["end_date"] + start_date_timestamp = data["start_date"] + notes = data["notes"] + + request = MentorshipRelationModel.find_by_id(relation_id) + if request is None: + return messages.MENTORSHIP_RELATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + request_action_user_id = request.action_user_id + request_mentor_id = request.mentor_id + request_mentee_id = request.mentee_id + request_accept_date = request.accept_date + + if request_accept_date is None: + return messages.MENTORSHIP_RELATION_NOT_IN_ACCEPT_STATE, HTTPStatus.BAD_REQUEST + + # Program to Mentor , accepted then request to/by Mentee + if request_mentee_id is None: + mentee_user = UserModel.find_by_id(mentee_id) + + if mentee_user is None: + return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTEE_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + if mentee_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + # TODO add tests for this portion + + all_mentee_relations = ( + mentee_user.mentor_relations + mentee_user.mentee_relations + ) + for relation in all_mentee_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + + # All validations were checked + if action_user_id == org_rep_id: + request.action_user_id = org_rep_id + + else: + request.action_user_id = mentee_id + + request.mentee_id = mentee_id + request.notes = notes + request.save_to_db() + + # Program to Mentee , accepted then request to/by Mentor + elif request_mentor_id is None: + if not mentor_id: + return messages.MENTOR_ID_FIELD_IS_MISSING, HTTPStatus.BAD_REQUEST + + mentor_user = UserModel.find_by_id(mentor_id) + + if mentor_user is None: + return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + if mentor_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + # TODO add tests for this portion + + all_mentor_relations = ( + mentor_user.mentor_relations + mentor_user.mentee_relations + ) + for relation in all_mentor_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + + if action_user_id == org_rep_id: + request.action_user_id = org_rep_id + else: + request.action_user_id = mentor_id + + request.mentor_id = mentor_id + request.notes = notes + request.save_to_db() + + # Mentor/Mentee to program ,accepted then + else: + # Request to/by Mentee + if request_mentor_id == mentor_id and request_mentee_id!=mentee_id: + + is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTEE_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + if mentee_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + if action_user_id == org_rep_id: + request.action_user_id = org_rep_id + else: + request.action_user_id = mentee_id + + request.mentee_id = mentee_id + request.notes = notes + request.save_to_db() + + # Request to/by Mentor + elif request_mentee_id == mentee_id and request_mentor_id!=mentor_id: + + is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + if mentor_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + if action_user_id == org_rep_id: + request.action_user_id = org_rep_id + else: + request.action_user_id = mentor_id + + request.mentor_id = mentor_id + request.notes = notes + request.save_to_db() + + return messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED + + @staticmethod + @email_verification_required + def accept_request(user_id: int, org_rep_id:int, request_id: int, notes:str): + """Allows a mentorship request. + + Args: + user_id: ID of the user accepting the request. + request_id: ID of the request to be accepted. + + Returns: + message: A message corresponding to the completed action; success if mentorship relation request is accepted, failure if otherwise. + """ + + user = UserModel.find_by_id(user_id) + request = MentorshipRelationModel.find_by_id(request_id) + + # verify if request exists + if request is None: + return ( + messages.MENTORSHIP_RELATION_REQUEST_DOES_NOT_EXIST, + HTTPStatus.NOT_FOUND, + ) + + # verify if request is in pending state + if request.state != MentorshipRelationState.PENDING: + return messages.NOT_PENDING_STATE_RELATION, HTTPStatus.FORBIDDEN + + # verify if I'm the receiver of the request + if request.action_user_id == user_id: + return messages.CANT_ACCEPT_MENTOR_REQ_SENT_BY_USER, HTTPStatus.FORBIDDEN + + # verify if I'm involved in this relation + if not (request.mentee_id == user_id or request.mentor_id == user_id or user_id == org_rep_id): + return messages.CANT_ACCEPT_UNINVOLVED_MENTOR_RELATION, HTTPStatus.FORBIDDEN + + my_requests = user.mentee_relations + user.mentor_relations + + # verify if I'm on a current relation + for my_request in my_requests: + if my_request.state == MentorshipRelationState.ACCEPTED: + return ( + messages.USER_IS_INVOLVED_IN_A_MENTORSHIP_RELATION, + HTTPStatus.FORBIDDEN, + ) + + mentee = request.mentee + mentor = request.mentor + action_user_id = request.action_user_id + + if mentor and mentee: + # If I am mentor : Check if the mentee isn't in any other relation already + if user_id == mentor.id: + mentee_requests = mentee.mentee_relations + mentee.mentor_relations + + for mentee_request in mentee_requests: + if mentee_request.state == MentorshipRelationState.ACCEPTED: + return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + # If I am mentee : Check if the mentor isn't in any other relation already + elif user_id == mentee.id: + mentor_requests = mentor.mentee_relations + mentor.mentor_relations + + for mentor_request in mentor_requests: + if mentor_request.state == MentorshipRelationState.ACCEPTED: + return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + + if ((action_user_id == mentee.id and user_id == mentor.id) or (action_user_id == mentor.id and user_id == mentee.id)) and (user_id != org_rep_id): + return messages.CANT_ACCEPT_UNINVOLVED_MENTOR_RELATION, HTTPStatus.FORBIDDEN + + if action_user_id == org_rep_id: + request.action_user_id = user.id + request.notes = notes + if request.accept_date is not None: + request.state = MentorshipRelationState.ACCEPTED + request.accept_date = datetime.now().timestamp() + request.save_to_db() + + else: + request.action_user_id = org_rep_id + request.notes = notes + if request.accept_date is not None: + request.state = MentorshipRelationState.ACCEPTED + request.accept_date = datetime.now().timestamp() + request.save_to_db() + + return messages.MENTORSHIP_RELATION_WAS_ACCEPTED_SUCCESSFULLY, HTTPStatus.OK diff --git a/app/api/email_utils.py b/app/api/email_utils.py index fe912815d..ec0b51947 100644 --- a/app/api/email_utils.py +++ b/app/api/email_utils.py @@ -137,6 +137,63 @@ def send_email_mentorship_relation_accepted(request_id): ) send_email(request_sender.email, subject, html) +def send_email_program_mentorship_relation_accepted(request_id,org_rep_id): + """ + Sends a notification email to the sender of the mentorship relation request, + stating that his request has been accepted. + + Args: + request_id: Request id of the mentorship request. + """ + + from app.database.models.user import UserModel + from app.database.models.mentorship_relation import MentorshipRelationModel + + # Getting the request from id. + request = MentorshipRelationModel.find_by_id(request_id) + + # Getting the sender and receiver of the mentorship request from their ids. + if not request.mentee: + if request.action_user_id == request.mentor_id: + request_sender = UserModel.find_by_id(org_rep_id) + request_receiver = UserModel.find_by_id(request.mentor_id) + role = "mentor" + else: + request_sender = UserModel.find_by_id(request.mentor_id) + request_receiver = UserModel.find_by_id(org_rep_id) + role = "mentor" + elif not request.mentor: + if request.action_user_id == request.mentee_id: + request_sender = UserModel.find_by_id(org_rep_id) + request_receiver = UserModel.find_by_id(request.mentee_id) + role = "mentee" + else: + request_sender = UserModel.find_by_id(request.mentee_id) + request_receiver = UserModel.find_by_id(org_rep_id) + role = "mentee" + else: + if request.action_user_id == request.mentor_id: + request_sender = UserModel.find_by_id(request.mentor_id) + request_receiver = UserModel.find_by_id(request.mentee_id) + role = "mentee" + else: + request_sender = UserModel.find_by_id(request.mentee_id) + request_receiver = UserModel.find_by_id(request.mentor_id) + role = "mentor" + + end_date = request.end_date + date = datetime.datetime.fromtimestamp(end_date).strftime("%d-%m-%Y") + + subject = "Mentorship relation accepted!" + html = render_template( + "mentorship_relation_accepted.html", + request_sender=request_sender.name, + request_receiver=request_receiver.name, + role=role, + end_date=date, + ) + send_email(request_sender.email, subject, html) + def send_email_new_request(user_sender, user_recipient, notes, sender_role): """Sends a notification html email message to the user_recipient user. diff --git a/app/api/models/mentorship_relation.py b/app/api/models/mentorship_relation.py index 1547dd2de..422dd8de2 100644 --- a/app/api/models/mentorship_relation.py +++ b/app/api/models/mentorship_relation.py @@ -9,6 +9,9 @@ def add_models_to_namespace(api_namespace): api_namespace.models[ send_mentorship_request_body.name ] = send_mentorship_request_body + api_namespace.models[ + send_program_mentorship_request_body.name + ] = send_program_mentorship_request_body api_namespace.models[ mentorship_request_response_body.name ] = mentorship_request_response_body @@ -40,6 +43,33 @@ def add_models_to_namespace(api_namespace): }, ) +send_program_mentorship_request_body = Model( + "Send mentorship relation request model", + { + "mentor_id": fields.Integer( + description="Mentorship relation mentor ID" + ), + "mentee_id": fields.Integer( + description="Mentorship relation mentor ID" + ), + "org_rep_id": fields.Integer( + required=True, description="Mentorship relation Organization Representative's ID" + ), + "relation_id": fields.Integer( + description="Mentorship relation Organization Representative's ID" + ), + "start_date": fields.Float( + required=True, + description="Mentorship relation end date in UNIX timestamp format", + ), + "end_date": fields.Float( + required=True, + description="Mentorship relation end date in UNIX timestamp format", + ), + "notes": fields.String(required=True, description="Mentorship relation notes"), + }, +) + relation_user_response_body = Model( "User", { diff --git a/app/api/resources/program_mentorship_relation.py b/app/api/resources/program_mentorship_relation.py new file mode 100644 index 000000000..d565be7cd --- /dev/null +++ b/app/api/resources/program_mentorship_relation.py @@ -0,0 +1,229 @@ +from flask import request +from flask_restx import Resource, Namespace, marshal +from flask_jwt_extended import jwt_required, get_jwt_identity +from http import HTTPStatus + +from app import messages +from app.api.resources.common import auth_header_parser +from app.api.dao.program_mentorship_relation import ProgramMentorshipRelationDAO +from app.api.dao.user import UserDAO +from app.api.models.mentorship_relation import * +from app.database.models.mentorship_relation import MentorshipRelationModel +from app.api.email_utils import send_email_program_mentorship_relation_accepted +from app.api.email_utils import send_email_new_request + +program_mentorship_relation_ns = Namespace( + "Program Mentorship Relation", + description="Operations related to " "mentorship relations " "between users", +) +add_models_to_namespace(program_mentorship_relation_ns) + +DAO = ProgramMentorshipRelationDAO() +userDAO = UserDAO() + +@program_mentorship_relation_ns.route("program_mentorship_relation/send_request") +class SendRequest(Resource): + @classmethod + @jwt_required + @program_mentorship_relation_ns.doc("send_request for establishing a program mentorship relation.") + @program_mentorship_relation_ns.expect(auth_header_parser, send_program_mentorship_request_body) + @program_mentorship_relation_ns.response( + HTTPStatus.CREATED, f"{messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY}" + ) + @program_mentorship_relation_ns.response( + HTTPStatus.BAD_REQUEST, + f"{messages.MATCH_EITHER_MENTOR_OR_MENTEE}\n" + f"{messages.MENTOR_ID_SAME_AS_MENTEE_ID}\n" + f"{messages.END_TIME_BEFORE_PRESENT}\n" + f"{messages.MENTOR_TIME_GREATER_THAN_MAX_TIME}\n" + f"{messages.MENTOR_TIME_LESS_THAN_MIN_TIME}\n" + f"{messages.MENTOR_NOT_AVAILABLE_TO_MENTOR}\n" + f"{messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED}\n" + f"{messages.MENTOR_ALREADY_IN_A_RELATION}\n" + f"{messages.MENTEE_ALREADY_IN_A_RELATION}\n" + f"{messages.MENTOR_ID_FIELD_IS_MISSING}\n" + f"{messages.MENTEE_ID_FIELD_IS_MISSING}\n" + f"{messages.NOTES_FIELD_IS_MISSING}", + ) + @program_mentorship_relation_ns.response( + HTTPStatus.UNAUTHORIZED, + f"{messages.TOKEN_HAS_EXPIRED}\n" + f"{messages.TOKEN_IS_INVALID}\n" + f"{messages.AUTHORISATION_TOKEN_IS_MISSING}", + ) + @program_mentorship_relation_ns.response( + HTTPStatus.NOT_FOUND, + f"{messages.MENTOR_DOES_NOT_EXIST}\n" f"{messages.MENTEE_DOES_NOT_EXIST}", + ) + def post(cls): + """ + Creates a new mentorship relation request. + + Also, sends an email notification to the recipient about new relation request. + + Input: + 1. Header: valid access token + 2. Body: A dict containing + - mentor_id, mentee_id: One of them must contain user ID + - end_date: UNIX timestamp + - notes: description of relation request + + Returns: + Success or failure message. A mentorship request is send to the other + person whose ID is mentioned. The relation appears at /pending endpoint. + """ + + data = request.json + user_sender_id = get_jwt_identity() + + is_valid = SendRequest.is_valid_data(data) + + if is_valid != {}: + return is_valid, HTTPStatus.BAD_REQUEST + + org_rep_id = data.pop("org_rep_id") + + if "relation_id" in data: + relation_id = data.pop("relation_id") + mentorship_relation = MentorshipRelationModel.find_by_id(relation_id) + if mentorship_relation is None: + return messages.MENTORSHIP_RELATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + mentorship_relation_mentor_id = mentorship_relation.mentor_id + mentorship_relation_mentee_id = mentorship_relation.mentee_id + + if mentorship_relation.accept_date is None: + return messages.MENTORSHIP_RELATION_NOT_IN_ACCEPT_STATE, HTTPStatus.BAD_REQUEST + + # request to/by mentor + if "mentor_id" in data: + if mentorship_relation_mentor_id == data['mentor_id']: + return messages.MENTORSHIP_RELATION_ALREADY_REQUESTED, HTTPStatus.BAD_REQUEST + + mentor_id = data["mentor_id"] + mentee_id = mentorship_relation_mentee_id + + if mentee_id is None or ((mentorship_relation.action_user_id == mentorship_relation.mentor_id) and mentorship_relation.action_user_id != org_rep_id): + return messages.MENTOR_ALREADY_ACCEPTED, HTTPStatus.BAD_REQUEST + + # request to/by mentee + if "mentee_id" in data: + if mentorship_relation_mentee_id == data['mentee_id']: + return messages.MENTORSHIP_RELATION_ALREADY_REQUESTED, HTTPStatus.BAD_REQUEST + + mentor_id = mentorship_relation_mentor_id + mentee_id = data["mentee_id"] + + if mentor_id is None or ((mentorship_relation.action_user_id == mentorship_relation.mentee_id) and mentorship_relation.action_user_id != org_rep_id): + return messages.MENTEE_ALREADY_ACCEPTED, HTTPStatus.BAD_REQUEST + + else: + # request to mentor + if "mentor_id" in data: + mentor_id = data["mentor_id"] + mentee_id = None + + # request to mentee + else: + mentee_id = data["mentee_id"] + mentor_id = None + + relation_id = None + + response = DAO.create_program_mentorship_relation(user_sender_id, org_rep_id, mentor_id, mentee_id, relation_id, data) + + # if the mentorship relation creation failed dont send email and return + if response[1] != HTTPStatus.CREATED.value: + return response + + if user_sender_id == mentor_id: + sender_role = "mentor" + user_recipient_id = org_rep_id + + elif user_sender_id == mentee_id: + sender_role = "mentee" + user_recipient_id = org_rep_id + + else: + sender_role = "Organization" + user_recipient_id = mentor_id if mentor_id else mentee_id + + user_sender = userDAO.get_user(user_sender_id) + user_recipient = userDAO.get_user(user_recipient_id) + notes = data["notes"] + + send_email_new_request(user_sender, user_recipient, notes, sender_role) + + return response + + @staticmethod + def is_valid_data(data): + + # Verify if request body has required fields + if "org_rep_id" not in data: + return messages.ORG_REP_ID_FIELD_IS_MISSING + if "mentor_id" not in data and "mentee_id" not in data: + return messages.MENTEE_OR_MENTOR_ID_FIELD_IS_MISSING + if "mentor_id" in data and "mentee_id" in data: + return messages.MENTEE_AND_MENTOR_ID_FIELDS_ARE_PRESENT + if "start_date" not in data: + return messages.START_DATE_FIELD_IS_MISSING + if "end_date" not in data: + return messages.END_DATE_FIELD_IS_MISSING + if "notes" not in data: + return messages.NOTES_FIELD_IS_MISSING + + return {} + +@program_mentorship_relation_ns.route("program_mentorship_relation//accept/") +class AcceptMentorshipRelation(Resource): + @classmethod + @jwt_required + @program_mentorship_relation_ns.doc("accept_mentorship_relation") + @program_mentorship_relation_ns.expect(auth_header_parser) + @program_mentorship_relation_ns.response( + HTTPStatus.OK, f"{messages.MENTORSHIP_RELATION_WAS_ACCEPTED_SUCCESSFULLY}" + ) + @program_mentorship_relation_ns.response( + HTTPStatus.FORBIDDEN, + f"{messages.NOT_PENDING_STATE_RELATION}\n" + f"{messages.CANT_ACCEPT_MENTOR_REQ_SENT_BY_USER}\n" + f"{messages.CANT_ACCEPT_UNINVOLVED_MENTOR_RELATION}\n" + f"{messages.USER_IS_INVOLVED_IN_A_MENTORSHIP_RELATION}", + ) + @program_mentorship_relation_ns.response( + HTTPStatus.UNAUTHORIZED, + f"{messages.TOKEN_HAS_EXPIRED}\n" + f"{messages.TOKEN_IS_INVALID}\n" + f"{messages.AUTHORISATION_TOKEN_IS_MISSING}", + ) + @program_mentorship_relation_ns.response( + HTTPStatus.NOT_FOUND, f"{messages.MENTORSHIP_RELATION_REQUEST_DOES_NOT_EXIST}" + ) + def put(cls, request_id, org_rep_id): + """ + Accept a mentorship relation. + + Input: + 1. Header: valid access token + 2. Path: ID of request which is to be accepted (request_id) + + Returns: + Success or failure message. + """ + + # check if user id is well parsed + # if it is an integer + + user_id = get_jwt_identity() + data = request.json + + if "notes" not in data: + return messages.NOTES_FIELD_IS_MISSING, HTTPStatus.BAD_REQUEST + + response = DAO.accept_request(user_id=user_id, org_rep_id=org_rep_id , request_id=request_id,notes=data['notes']) + + if response[1] == HTTPStatus.OK.value: + send_email_program_mentorship_relation_accepted(request_id,org_rep_id) + + return response diff --git a/app/messages.py b/app/messages.py index eb73785a7..feb01edcf 100644 --- a/app/messages.py +++ b/app/messages.py @@ -30,6 +30,7 @@ USER_NOT_FOUND = {"message": "User not found."} MENTOR_DOES_NOT_EXIST = {"message": "Mentor user does not exist."} MENTEE_DOES_NOT_EXIST = {"message": "Mentee user does not exist."} +ORG_REP_DOES_NOT_EXIST = {"message": "Organization representative user does not exist."} TASK_DOES_NOT_EXIST = {"message": "Task does not exist."} USER_DOES_NOT_EXIST = {"message": "User does not exist."} TASK_COMMENT_DOES_NOT_EXIST = {"message": "Task comment does not exist."} @@ -40,6 +41,10 @@ # Missing fields MENTOR_ID_FIELD_IS_MISSING = {"message": "Mentor ID field is missing."} MENTEE_ID_FIELD_IS_MISSING = {"message": "Mentee ID field is missing."} +MENTEE_OR_MENTOR_ID_FIELD_IS_MISSING = {"message": "Send either Mentor or Mentee ID field in the request body."} +ORG_REP_ID_FIELD_IS_MISSING = {"message": "Org Representative ID field is missing."} +MENTORSHIP_RELATION_ID_FIELD_IS_MISSING = {"message": "Mentorship relation ID field is missing."} +START_DATE_FIELD_IS_MISSING = {"message": "Start date field is missing."} END_DATE_FIELD_IS_MISSING = {"message": "End date field is missing."} NOTES_FIELD_IS_MISSING = {"message": "Notes field is missing."} USERNAME_FIELD_IS_MISSING = {"message": "The field username is missing."} @@ -90,11 +95,23 @@ MENTEE_ALREADY_IN_A_RELATION = { "message": "Mentee user is already in a" " relationship." } - +# Program already in relation +MENTOR_ALREADY_ACCEPTED = { + "message": "Mentor user has already accepted." +} +MENTEE_ALREADY_ACCEPTED = { + "message": "Mentee user has already accepted" +} # Mismatch of fields MATCH_EITHER_MENTOR_OR_MENTEE = { "message": "Your ID has to match either" " Mentor or Mentee IDs." } +MATCH_EITHER_MENTOR_OR_ORG_REP = { + "message": "Your ID has to match either" " Mentor or Org Representative" +} +MATCH_EITHER_MENTEE_OR_ORG_REP = { + "message": "Your ID has to match either" " Mentee or Org Representative" +} TASK_COMMENT_WAS_NOT_CREATED_BY_YOU = { "message": "You have not created the comment and therefore cannot " "modify it." } @@ -111,6 +128,9 @@ MENTOR_ID_SAME_AS_MENTEE_ID = { "message": "You cannot have a mentorship" " relation with yourself." } +MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID = { + "message": "You cannot have a mentorship" " relation with Organization's Representative." +} END_TIME_BEFORE_PRESENT = {"message": "End date is invalid since date has" " passed."} MENTOR_TIME_GREATER_THAN_MAX_TIME = { "message": "Mentorship relation maximum" " duration is 6 months." @@ -186,6 +206,9 @@ MENTORSHIP_RELATION_NOT_IN_ACCEPT_STATE = { "message": "Mentorship relation is" " not in the accepted state." } +MENTORSHIP_RELATION_ALREADY_REQUESTED = { + "message": "This Mentorship relation has been" " requested already." +} # Login errors USER_ENTERED_INCORRECT_PASSWORD = {"message": "Current password is incorrect."} @@ -232,6 +255,21 @@ MENTORSHIP_RELATION_WAS_CANCELLED_SUCCESSFULLY = { "message": "Mentorship relation was cancelled successfully." } +PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY = { + "message": "Program Mentorship relation" " was sent successfully." +} +PROGRAM_MENTORSHIP_RELATION_WAS_ACCEPTED_SUCCESSFULLY = { + "message": "Program Mentorship" " relation was accepted" " successfully." +} +PROGRAM_MENTORSHIP_RELATION_WAS_DELETED_SUCCESSFULLY = { + "message": "Program Mentorship" " relation was deleted" " successfully." +} +PROGRAM_MENTORSHIP_RELATION_WAS_REJECTED_SUCCESSFULLY = { + "message": "Program Mentorship" " relation was" " rejected successfully." +} +PROGRAM_MENTORSHIP_RELATION_WAS_CANCELLED_SUCCESSFULLY = { + "message": "Program Mentorship relation was cancelled successfully." +} TASK_WAS_CREATED_SUCCESSFULLY = {"message": "Task was created successfully."} TASK_WAS_DELETED_SUCCESSFULLY = {"message": "Task was deleted successfully."} TASK_WAS_ACHIEVED_SUCCESSFULLY = {"message": "Task was achieved" " successfully."} @@ -294,3 +332,4 @@ "message": "Validation error. End date represented by the timestamp is invalid." } NOT_IMPLEMENTED = {"message": "Not implemented."} +MENTEE_AND_MENTOR_ID_FIELDS_ARE_PRESENT = {"messages" : "You cannot send both Mentor and Mentee ID in the request body."} \ No newline at end of file From 096cdd10374a3b495d3a3ff4c586a41ed1ceb8c0 Mon Sep 17 00:00:00 2001 From: decon-harsh Date: Sun, 7 Mar 2021 05:24:07 +0530 Subject: [PATCH 4/4] Divide One DAO function into four small different functions and some changes --- app/api/dao/program_mentorship_relation.py | 585 +++++++++--------- app/api/models/mentorship_relation.py | 30 - app/api/models/program_mentorship_relation.py | 40 ++ .../resources/program_mentorship_relation.py | 52 +- app/messages.py | 4 +- 5 files changed, 361 insertions(+), 350 deletions(-) create mode 100644 app/api/models/program_mentorship_relation.py diff --git a/app/api/dao/program_mentorship_relation.py b/app/api/dao/program_mentorship_relation.py index 65997d287..7fae320c8 100644 --- a/app/api/dao/program_mentorship_relation.py +++ b/app/api/dao/program_mentorship_relation.py @@ -10,323 +10,320 @@ class ProgramMentorshipRelationDAO: - """Data Access Object for mentorship relation functionalities. + """Data Access Object for program mentorship relation functionalities. - Provides various functions pertaining to mentorship. - - Attributes: - MAXIMUM_MENTORSHIP_DURATION - MINIMUM_MENTORSHIP_DURATION + Provides various functions pertaining to program mentorship. """ - def create_program_mentorship_relation(self, user_id: int, org_rep_id:int , mentor_id:int, mentee_id:int, relation_id:int, data: Dict[str, str]): - """Creates a relationship between two users. - - Establishes the mentor-mentee relationship. - - Args: - user_id: ID of the user initiating this request. Has to be either the mentor or the mentee. - data: List containing the mentor_id, mentee_id, end_date_timestamp and notes. - - Returns: - message: A message corresponding to the completed action; success if mentorship relationship is established, failure if otherwise. - """ - - # First/Initial request where mentorship relationship gets created for the first time - if not relation_id: - # First request to mentor by program and viceversa - if not mentee_id: - action_user_id = user_id - end_date_timestamp = data["end_date"] - start_date_timestamp = data["start_date"] - notes = data["notes"] - - # user_id has to match either org_representative id or mentor_id - is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id - if not is_valid_user_ids: - return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + def create_program_mentorship_relation_initial_program_mentor(self, user_id: int, data: Dict[str, str]): + ''' + Create Initial Program Mentorship Relationship between a Program and a Mentor or viceversa + + Args: + user_id: ID of the user initiating this request. Has to be either the mentor or the mentee or the org_rep_id. + + data: List containing the mentor_id, org_rep_id ,start_date_timestamp,end_date_timestamp and notes. + + + ''' + action_user_id = user_id + mentor_id = data["mentor_id"] + org_rep_id = data["org_rep_id"] + end_date_timestamp = data["end_date"] + start_date_timestamp = data["start_date"] + notes = data["notes"] + + # user_id has to match either org_representative id or mentor_id + is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + # mentor_id has to be different from org_representative id + if mentor_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + try: + end_date_datetime = datetime.fromtimestamp(end_date_timestamp) + except ValueError: + return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST + + now_datetime = datetime.now() + if end_date_datetime < now_datetime: + return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST + + # validate if mentor user exists + mentor_user = UserModel.find_by_id(mentor_id) + if mentor_user is None: + return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + # validate if mentor is available to mentor + if not mentor_user.available_to_mentor: + return messages.MENTOR_NOT_AVAILABLE_TO_MENTOR, HTTPStatus.BAD_REQUEST + + # validate if org_rep_user exists + org_rep_user = UserModel.find_by_id(org_rep_id) + if org_rep_user is None: + return messages.ORG_REP_DOES_NOT_EXIST, HTTPStatus.BAD_REQUEST + + # TODO add tests for this portion - # mentor_id has to be different from org_representative id - if mentor_id == org_rep_id: - return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST - - try: - end_date_datetime = datetime.fromtimestamp(end_date_timestamp) - except ValueError: - return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST - - now_datetime = datetime.now() - if end_date_datetime < now_datetime: - return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST - - # validate if mentor user exists - mentor_user = UserModel.find_by_id(mentor_id) - if mentor_user is None: - return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND - - # validate if mentor is available to mentor - if not mentor_user.available_to_mentor: - return messages.MENTOR_NOT_AVAILABLE_TO_MENTOR, HTTPStatus.BAD_REQUEST - - org_rep_user = UserModel.find_by_id(org_rep_id) - if org_rep_user is None: - return messages.ORG_REP_DOES_NOT_EXIST, HTTPStatus.BAD_REQUEST - - - # TODO add tests for this portion + all_mentor_relations = ( + mentor_user.mentor_relations + mentor_user.mentee_relations + ) + for relation in all_mentor_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST - all_mentor_relations = ( - mentor_user.mentor_relations + mentor_user.mentee_relations - ) - for relation in all_mentor_relations: - if relation.state == MentorshipRelationState.ACCEPTED: - return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + # All validations were checked + tasks_list = TasksListModel() + tasks_list.save_to_db() + + if action_user_id == org_rep_id: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentor_user=mentor_user, + mentee_user=None, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp - # All validations were checked + else: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentor_user=mentor_user, + mentee_user=org_rep_user, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp + + mentorship_relation.save_to_db() + return messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED - tasks_list = TasksListModel() - tasks_list.save_to_db() + def create_program_mentorship_relation_initial_program_mentee(self, user_id: int, data: Dict[str, str]): + ''' + Create Initial Program Mentorship Relationship between a Program and a Mentee or viceversa - if action_user_id == org_rep_id: - mentorship_relation = MentorshipRelationModel( - action_user_id=action_user_id, - mentor_user=mentor_user, - mentee_user=None, - creation_date=datetime.now().timestamp(), - end_date=end_date_timestamp, - state=MentorshipRelationState.PENDING, - notes=notes, - tasks_list=tasks_list, - ) - mentorship_relation.start_date = start_date_timestamp - - else: - mentorship_relation = MentorshipRelationModel( - action_user_id=action_user_id, - mentor_user=mentor_user, - mentee_user=org_rep_user, - creation_date=datetime.now().timestamp(), - end_date=end_date_timestamp, - state=MentorshipRelationState.PENDING, - notes=notes, - tasks_list=tasks_list, - ) - mentorship_relation.start_date = start_date_timestamp - mentorship_relation.save_to_db() - - # First request to mentee by program and viceversa - elif not mentor_id: - action_user_id = user_id - end_date_timestamp = data["end_date"] - start_date_timestamp = data["start_date"] - notes = data["notes"] - - # user_id has to match either org_representative id or mentee_id - is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id - if not is_valid_user_ids: - return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + Args: + user_id: ID of the user initiating this request. Has to be either the mentor or the mentee or the org_rep_id. + + data: List containing the mentee_id, org_rep_id ,start_date_timestamp,end_date_timestamp and notes. + ''' + + action_user_id = user_id + mentee_id = data["mentee_id"] + org_rep_id = data["org_rep_id"] + end_date_timestamp = data["end_date"] + start_date_timestamp = data["start_date"] + notes = data["notes"] + + # user_id has to match either org_representative id or mentee_id + is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTEE_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + # mentee_id has to be different from org_representative id + if mentee_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + + try: + end_date_datetime = datetime.fromtimestamp(end_date_timestamp) + except ValueError: + return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST + + now_datetime = datetime.now() + if end_date_datetime < now_datetime: + return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST + + # validate if mentee user exists + mentee_user = UserModel.find_by_id(mentee_id) + if mentee_user is None: + return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + # validate if mentee is needs mentoring + if not mentee_user.need_mentoring: + return messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED, HTTPStatus.BAD_REQUEST + + # validate if org_rep_user exists + org_rep_user = UserModel.find_by_id(org_rep_id) + if org_rep_user is None: + return messages.ORG_REP_DOES_NOT_EXIST, HTTPStatus.BAD_REQUEST + + # TODO add tests for this portion - # mentee_id has to be different from org_representative id - if mentee_id == org_rep_id: - return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST - - try: - end_date_datetime = datetime.fromtimestamp(end_date_timestamp) - except ValueError: - return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST - - now_datetime = datetime.now() - if end_date_datetime < now_datetime: - return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST - - # validate if mentee user exists - mentee_user = UserModel.find_by_id(mentee_id) - if mentee_user is None: - return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND - - # validate if mentee is available to be mentored - if not mentee_user.need_mentoring: - return messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED, HTTPStatus.BAD_REQUEST - - org_rep_user = UserModel.find_by_id(org_rep_id) - if org_rep_user is None: - return messages.ORG_REP_DOES_NOT_EXIST, HTTPStatus.BAD_REQUEST - - - # TODO add tests for this portion + all_mentee_relations = ( + mentee_user.mentor_relations + mentee_user.mentee_relations + ) + for relation in all_mentee_relations: + if relation.state == menteeshipRelationState.ACCEPTED: + return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST - all_mentee_relations = ( - mentee_user.mentor_relations + mentee_user.mentee_relations - ) - for relation in all_mentee_relations: - if relation.state == MentorshipRelationState.ACCEPTED: - return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST - - # All validations were checked - - tasks_list = TasksListModel() - tasks_list.save_to_db() + # All validations were checked + tasks_list = TasksListModel() + tasks_list.save_to_db() - if action_user_id == org_rep_id: - mentorship_relation = MentorshipRelationModel( - action_user_id=action_user_id, - mentor_user=None, - mentee_user=mentee_user, - creation_date=datetime.now().timestamp(), - end_date=end_date_timestamp, - state=MentorshipRelationState.PENDING, - notes=notes, - tasks_list=tasks_list, - ) - mentorship_relation.start_date = start_date_timestamp + if action_user_id == org_rep_id: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentee_user=mentee_user, + mentor_user=None, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp - else: - mentorship_relation = MentorshipRelationModel( - action_user_id=action_user_id, - mentor_user=org_rep_user, - mentee_user=mentee_user, - creation_date=datetime.now().timestamp(), - end_date=end_date_timestamp, - state=MentorshipRelationState.PENDING, - notes=notes, - tasks_list=tasks_list, - ) - mentorship_relation.start_date = start_date_timestamp - mentorship_relation.save_to_db() + else: + mentorship_relation = MentorshipRelationModel( + action_user_id=action_user_id, + mentee_user=mentee_user, + mentor_user=org_rep_user, + creation_date=datetime.now().timestamp(), + end_date=end_date_timestamp, + state=MentorshipRelationState.PENDING, + notes=notes, + tasks_list=tasks_list, + ) + mentorship_relation.start_date = start_date_timestamp + + mentorship_relation.save_to_db() + return messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED + + def create_program_mentorship_relation_final_program_mentor(self, user_id: int, relation_id:int, data: Dict[str, str]): + ''' + Create Final Program Mentorship Relationship between a Program and a Mentor or viceversa - # Second/End requests where relationship is already present but with org_rep_user - elif mentor_id and mentee_id and relation_id: - if mentor_id == mentee_id: - return messages.MENTOR_ID_SAME_AS_MENTEE_ID, HTTPStatus.BAD_REQUEST - - action_user_id = user_id - end_date_timestamp = data["end_date"] - start_date_timestamp = data["start_date"] - notes = data["notes"] - - request = MentorshipRelationModel.find_by_id(relation_id) - if request is None: - return messages.MENTORSHIP_RELATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND - - request_action_user_id = request.action_user_id - request_mentor_id = request.mentor_id - request_mentee_id = request.mentee_id - request_accept_date = request.accept_date - - if request_accept_date is None: - return messages.MENTORSHIP_RELATION_NOT_IN_ACCEPT_STATE, HTTPStatus.BAD_REQUEST - - # Program to Mentor , accepted then request to/by Mentee - if request_mentee_id is None: - mentee_user = UserModel.find_by_id(mentee_id) - - if mentee_user is None: - return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND - - is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id - if not is_valid_user_ids: - return messages.MATCH_EITHER_MENTEE_OR_ORG_REP, HTTPStatus.BAD_REQUEST - - if mentee_id == org_rep_id: - return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + Args: + user_id: ID of the user initiating this request. Has to be either the mentor or the mentee or the org_rep_id. + relation_id : ID of the mentorship_relation request is being sent + + data: List containing the mentor_id, org_rep_id ,start_date_timestamp,end_date_timestamp and notes. + ''' + action_user_id = user_id + mentor_id = data["mentor_id"] + org_rep_id = data["org_rep_id"] + notes = data["notes"] + + request = MentorshipRelationModel.find_by_id(relation_id) + if request is None: + return messages.MENTORSHIP_RELATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + request_action_user_id = request.action_user_id + request_mentor_id = request.mentor_id + request_mentee_id = request.mentee_id + request_accept_date = request.accept_date + + if request_accept_date is None: + return messages.MENTORSHIP_RELATION_NOT_IN_ACCEPT_STATE, HTTPStatus.BAD_REQUEST + + mentor_user = UserModel.find_by_id(mentor_id) + if mentor_user is None: + return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + # validate if mentor is available to mentor + if not mentor_user.available_to_mentor: + return messages.MENTOR_NOT_AVAILABLE_TO_MENTOR, HTTPStatus.BAD_REQUEST + + is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST + + if mentor_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST - # TODO add tests for this portion + # TODO add tests for this portion - all_mentee_relations = ( - mentee_user.mentor_relations + mentee_user.mentee_relations - ) - for relation in all_mentee_relations: - if relation.state == MentorshipRelationState.ACCEPTED: - return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + all_mentor_relations = ( + mentor_user.mentor_relations + mentor_user.mentee_relations + ) + for relation in all_mentor_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST - # All validations were checked - if action_user_id == org_rep_id: - request.action_user_id = org_rep_id + # All validations were checked + if action_user_id == org_rep_id: + request.action_user_id = org_rep_id - else: - request.action_user_id = mentee_id - - request.mentee_id = mentee_id - request.notes = notes - request.save_to_db() - - # Program to Mentee , accepted then request to/by Mentor - elif request_mentor_id is None: - if not mentor_id: - return messages.MENTOR_ID_FIELD_IS_MISSING, HTTPStatus.BAD_REQUEST - - mentor_user = UserModel.find_by_id(mentor_id) - - if mentor_user is None: - return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND - - is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id - if not is_valid_user_ids: - return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST - - if mentor_id == org_rep_id: - return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST + else: + request.action_user_id = mentor_id - # TODO add tests for this portion + request.mentor_id = mentor_id + request.notes = notes + request.save_to_db() + return messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED + + def create_program_mentorship_relation_final_program_mentee(self, user_id: int, relation_id:int, data: Dict[str, str]): + ''' + Create Final Program Mentorship Relationship between a Program and a Mentee or viceversa - all_mentor_relations = ( - mentor_user.mentor_relations + mentor_user.mentee_relations - ) - for relation in all_mentor_relations: - if relation.state == MentorshipRelationState.ACCEPTED: - return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST - - if action_user_id == org_rep_id: - request.action_user_id = org_rep_id - else: - request.action_user_id = mentor_id + Args: + user_id: ID of the user initiating this request. Has to be either the mentor or the mentee or the org_rep_id. + relation_id : ID of the mentorship_relation request is being sent + + data: List containing the mentee_id, org_rep_id ,start_date_timestamp,end_date_timestamp and notes. + ''' + action_user_id = user_id + mentee_id = data["mentee_id"] + org_rep_id = data["org_rep_id"] + notes = data["notes"] + + request = MentorshipRelationModel.find_by_id(relation_id) + if request is None: + return messages.MENTORSHIP_RELATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + request_action_user_id = request.action_user_id + request_mentor_id = request.mentor_id + request_mentee_id = request.mentee_id + request_accept_date = request.accept_date + + if request_accept_date is None: + return messages.MENTORSHIP_RELATION_NOT_IN_ACCEPT_STATE, HTTPStatus.BAD_REQUEST + + mentee_user = UserModel.find_by_id(mentee_id) + if mentee_user is None: + return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND + + # validate if mentee is needs mentoring + if not mentee_user.need_mentoring: + return messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED, HTTPStatus.BAD_REQUEST + + is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id + if not is_valid_user_ids: + return messages.MATCH_EITHER_MENTEE_OR_ORG_REP, HTTPStatus.BAD_REQUEST - request.mentor_id = mentor_id - request.notes = notes - request.save_to_db() - - # Mentor/Mentee to program ,accepted then - else: - # Request to/by Mentee - if request_mentor_id == mentor_id and request_mentee_id!=mentee_id: - - is_valid_user_ids = action_user_id == mentee_id or action_user_id == org_rep_id - if not is_valid_user_ids: - return messages.MATCH_EITHER_MENTEE_OR_ORG_REP, HTTPStatus.BAD_REQUEST - - if mentee_id == org_rep_id: - return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST - - if action_user_id == org_rep_id: - request.action_user_id = org_rep_id - else: - request.action_user_id = mentee_id - - request.mentee_id = mentee_id - request.notes = notes - request.save_to_db() + if mentee_id == org_rep_id: + return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST - # Request to/by Mentor - elif request_mentee_id == mentee_id and request_mentor_id!=mentor_id: - - is_valid_user_ids = action_user_id == mentor_id or action_user_id == org_rep_id - if not is_valid_user_ids: - return messages.MATCH_EITHER_MENTOR_OR_ORG_REP, HTTPStatus.BAD_REQUEST - - if mentor_id == org_rep_id: - return messages.MENTOR_ID_OR_MENTEE_ID_SAME_AS_ORG_REP_ID, HTTPStatus.BAD_REQUEST - - if action_user_id == org_rep_id: - request.action_user_id = org_rep_id - else: - request.action_user_id = mentor_id - - request.mentor_id = mentor_id - request.notes = notes - request.save_to_db() + # TODO add tests for this portion - return messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED + all_mentee_relations = ( + mentee_user.mentor_relations + mentee_user.mentee_relations + ) + for relation in all_mentee_relations: + if relation.state == MentorshipRelationState.ACCEPTED: + return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST + + # All validations were checked + if action_user_id == org_rep_id: + request.action_user_id = org_rep_id + else: + request.action_user_id = mentee_id + + request.mentee_id = mentee_id + request.notes = notes + request.save_to_db() + return messages.PROGRAM_MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED + @staticmethod @email_verification_required def accept_request(user_id: int, org_rep_id:int, request_id: int, notes:str): diff --git a/app/api/models/mentorship_relation.py b/app/api/models/mentorship_relation.py index 422dd8de2..1547dd2de 100644 --- a/app/api/models/mentorship_relation.py +++ b/app/api/models/mentorship_relation.py @@ -9,9 +9,6 @@ def add_models_to_namespace(api_namespace): api_namespace.models[ send_mentorship_request_body.name ] = send_mentorship_request_body - api_namespace.models[ - send_program_mentorship_request_body.name - ] = send_program_mentorship_request_body api_namespace.models[ mentorship_request_response_body.name ] = mentorship_request_response_body @@ -43,33 +40,6 @@ def add_models_to_namespace(api_namespace): }, ) -send_program_mentorship_request_body = Model( - "Send mentorship relation request model", - { - "mentor_id": fields.Integer( - description="Mentorship relation mentor ID" - ), - "mentee_id": fields.Integer( - description="Mentorship relation mentor ID" - ), - "org_rep_id": fields.Integer( - required=True, description="Mentorship relation Organization Representative's ID" - ), - "relation_id": fields.Integer( - description="Mentorship relation Organization Representative's ID" - ), - "start_date": fields.Float( - required=True, - description="Mentorship relation end date in UNIX timestamp format", - ), - "end_date": fields.Float( - required=True, - description="Mentorship relation end date in UNIX timestamp format", - ), - "notes": fields.String(required=True, description="Mentorship relation notes"), - }, -) - relation_user_response_body = Model( "User", { diff --git a/app/api/models/program_mentorship_relation.py b/app/api/models/program_mentorship_relation.py new file mode 100644 index 000000000..e8bcc57e3 --- /dev/null +++ b/app/api/models/program_mentorship_relation.py @@ -0,0 +1,40 @@ +from flask_restx import fields, Model + +from app.utils.enum_utils import MentorshipRelationState +from .task import create_task_request_body, list_tasks_response_body +from .task_comment import task_comment_model, task_comments_model + + +def add_models_to_namespace(api_namespace): + api_namespace.models[ + send_program_mentorship_request_body.name + ] = send_program_mentorship_request_body + + + +send_program_mentorship_request_body = Model( + "Send mentorship relation request model", + { + "mentor_id": fields.Integer( + description="Mentorship relation mentor ID" + ), + "mentee_id": fields.Integer( + description="Mentorship relation mentee ID" + ), + "org_rep_id": fields.Integer( + required=True, description="Organization Representative's ID" + ), + "relation_id": fields.Integer( + description="Mentorship Relation ID" + ), + "start_date": fields.Float( + description="Program start date in UNIX timestamp format", + ), + "end_date": fields.Float( + description="Program end date in UNIX timestamp format", + ), + "notes": fields.String(required=True, description="Mentorship relation notes"), + }, +) + + diff --git a/app/api/resources/program_mentorship_relation.py b/app/api/resources/program_mentorship_relation.py index d565be7cd..f32fe21c3 100644 --- a/app/api/resources/program_mentorship_relation.py +++ b/app/api/resources/program_mentorship_relation.py @@ -7,21 +7,21 @@ from app.api.resources.common import auth_header_parser from app.api.dao.program_mentorship_relation import ProgramMentorshipRelationDAO from app.api.dao.user import UserDAO -from app.api.models.mentorship_relation import * +from app.api.models.program_mentorship_relation import * from app.database.models.mentorship_relation import MentorshipRelationModel from app.api.email_utils import send_email_program_mentorship_relation_accepted from app.api.email_utils import send_email_new_request program_mentorship_relation_ns = Namespace( "Program Mentorship Relation", - description="Operations related to " "mentorship relations " "between users", + description="Operations related to " "program mentorship relations " "between users", ) add_models_to_namespace(program_mentorship_relation_ns) DAO = ProgramMentorshipRelationDAO() userDAO = UserDAO() -@program_mentorship_relation_ns.route("program_mentorship_relation/send_request") +@program_mentorship_relation_ns.route("program_mentorship_relation/send_request/") class SendRequest(Resource): @classmethod @jwt_required @@ -57,19 +57,21 @@ class SendRequest(Resource): ) def post(cls): """ - Creates a new mentorship relation request. + Creates a new program mentorship relation request. Also, sends an email notification to the recipient about new relation request. Input: 1. Header: valid access token 2. Body: A dict containing - - mentor_id, mentee_id: One of them must contain user ID - - end_date: UNIX timestamp + - mentor_id or mentee_id: One of them must be present and contain user ID + - org_rep_id: organization's representative's id + - start_date: program start date in UNIX timestamp + - end_date: program end date in UNIX timestamp - notes: description of relation request Returns: - Success or failure message. A mentorship request is send to the other + Success or failure message. A program mentorship request is send to the other person whose ID is mentioned. The relation appears at /pending endpoint. """ @@ -80,8 +82,8 @@ def post(cls): if is_valid != {}: return is_valid, HTTPStatus.BAD_REQUEST - - org_rep_id = data.pop("org_rep_id") + + org_rep_id = data["org_rep_id"] if "relation_id" in data: relation_id = data.pop("relation_id") @@ -106,6 +108,8 @@ def post(cls): if mentee_id is None or ((mentorship_relation.action_user_id == mentorship_relation.mentor_id) and mentorship_relation.action_user_id != org_rep_id): return messages.MENTOR_ALREADY_ACCEPTED, HTTPStatus.BAD_REQUEST + response = DAO.create_program_mentorship_relation_final_program_mentor(user_sender_id,relation_id,data) + # request to/by mentee if "mentee_id" in data: if mentorship_relation_mentee_id == data['mentee_id']: @@ -117,20 +121,20 @@ def post(cls): if mentor_id is None or ((mentorship_relation.action_user_id == mentorship_relation.mentee_id) and mentorship_relation.action_user_id != org_rep_id): return messages.MENTEE_ALREADY_ACCEPTED, HTTPStatus.BAD_REQUEST - else: - # request to mentor - if "mentor_id" in data: - mentor_id = data["mentor_id"] - mentee_id = None - - # request to mentee - else: - mentee_id = data["mentee_id"] - mentor_id = None + response = DAO.create_program_mentorship_relation_final_program_mentee(user_sender_id,relation_id,data) - relation_id = None - - response = DAO.create_program_mentorship_relation(user_sender_id, org_rep_id, mentor_id, mentee_id, relation_id, data) + else: + # request to/by mentor + if "mentor_id" in data: + mentor_id = data["mentor_id"] + mentee_id = None + response = DAO.create_program_mentorship_relation_initial_program_mentor(user_sender_id,data) + + # request to/by mentee + elif "mentee_id" in data: + mentee_id = data["mentee_id"] + mentor_id = None + response = DAO.create_program_mentorship_relation_initial_program_mentee(user_sender_id,data) # if the mentorship relation creation failed dont send email and return if response[1] != HTTPStatus.CREATED.value: @@ -166,9 +170,9 @@ def is_valid_data(data): return messages.MENTEE_OR_MENTOR_ID_FIELD_IS_MISSING if "mentor_id" in data and "mentee_id" in data: return messages.MENTEE_AND_MENTOR_ID_FIELDS_ARE_PRESENT - if "start_date" not in data: + if "start_date" not in data and "relation_id" not in data: return messages.START_DATE_FIELD_IS_MISSING - if "end_date" not in data: + if "end_date" not in data and "relation_id" not in data: return messages.END_DATE_FIELD_IS_MISSING if "notes" not in data: return messages.NOTES_FIELD_IS_MISSING diff --git a/app/messages.py b/app/messages.py index feb01edcf..896c0cb5e 100644 --- a/app/messages.py +++ b/app/messages.py @@ -41,7 +41,7 @@ # Missing fields MENTOR_ID_FIELD_IS_MISSING = {"message": "Mentor ID field is missing."} MENTEE_ID_FIELD_IS_MISSING = {"message": "Mentee ID field is missing."} -MENTEE_OR_MENTOR_ID_FIELD_IS_MISSING = {"message": "Send either Mentor or Mentee ID field in the request body."} +MENTEE_OR_MENTOR_ID_FIELD_IS_MISSING = {"message": "Mentor or Mentee ID field cannot be empty."} ORG_REP_ID_FIELD_IS_MISSING = {"message": "Org Representative ID field is missing."} MENTORSHIP_RELATION_ID_FIELD_IS_MISSING = {"message": "Mentorship relation ID field is missing."} START_DATE_FIELD_IS_MISSING = {"message": "Start date field is missing."} @@ -332,4 +332,4 @@ "message": "Validation error. End date represented by the timestamp is invalid." } NOT_IMPLEMENTED = {"message": "Not implemented."} -MENTEE_AND_MENTOR_ID_FIELDS_ARE_PRESENT = {"messages" : "You cannot send both Mentor and Mentee ID in the request body."} \ No newline at end of file +MENTEE_AND_MENTOR_ID_FIELDS_ARE_PRESENT = {"messages" : "You cannot send Both Mentor and Mentee ID fields at the same time."} \ No newline at end of file