Skip to content

Commit fe9d46f

Browse files
committed
Feat:Add post program mentee relationship endpoint
1 parent 6f43f59 commit fe9d46f

11 files changed

+560
-2
lines changed

app/api/bit_extension.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@
1919

2020
from app.api.resources.organizations import organizations_ns as organization_namespace
2121
api.add_namespace(organization_namespace, path="/")
22+
23+
from app.api.resources.mentorship_relation import mentorship_relation_ns as mentorship_relation_namespace
24+
api.add_namespace(mentorship_relation_namespace, path="/")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import ast
2+
from http import HTTPStatus
3+
from flask import json
4+
from app.database.models.bit_schema.mentorship_relation_extension import MentorshipRelationExtensionModel
5+
from app import messages
6+
from app.api.request_api_utils import AUTH_COOKIE
7+
from app.utils.bitschema_utils import Timezone
8+
9+
10+
class MentorshipRelationExtensionDAO:
11+
12+
"""Data Access Object for Users_Extension functionalities"""
13+
14+
@staticmethod
15+
def createMentorshipRelationExtension(program_id, mentorship_relation_id , mentee_request_date):
16+
"""Creates the extending mentorship relation between organization's program and the user which is logged in.
17+
18+
Arguments:
19+
organization_id: The ID organization, program_id: The ID of program.
20+
21+
Returns:
22+
A dictionary containing "message" which indicates whether or not
23+
the relation was created successfully and "code" for the HTTP response code.
24+
"""
25+
26+
try:
27+
mentorship_relation_extension_object = MentorshipRelationExtensionModel(program_id,mentorship_relation_id)
28+
mentorship_relation_extension_object.mentee_request_date = mentee_request_date
29+
mentorship_relation_extension_object.save_to_db()
30+
return messages.MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED
31+
except:
32+
return messages.INTERNAL_SERVER_ERROR, HTTPStatus.BAD_REQUEST
33+
34+
35+
36+

app/api/models/mentorship_relation.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from flask_restx import fields, Model
2+
3+
from app.utils.enum_utils import MentorshipRelationState
4+
5+
6+
def add_models_to_namespace(api_namespace):
7+
api_namespace.models[send_mentorship_extension_request_body.name] = send_mentorship_extension_request_body
8+
9+
10+
send_mentorship_extension_request_body = Model(
11+
"Send mentorship relation request to organziation model",
12+
{
13+
"mentee_id": fields.Integer(
14+
required=True, description="Mentorship relation mentee ID"
15+
),
16+
"mentee_request_date": fields.Float(
17+
required=True, description="Mentorship relation mentee_request_date in UNIX timestamp format"
18+
),
19+
"end_date": fields.Float(
20+
required=True,
21+
description="Mentorship relation end date in UNIX timestamp format",
22+
),
23+
"notes": fields.String(required=True, description="Mentorship relation notes"),
24+
25+
},
26+
)

app/api/request_api_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,37 @@ def post_request(request_string, data):
4747
return response_message, response_code
4848

4949

50+
def post_request_with_token(request_string, token, data):
51+
request_url = f"{BASE_MS_API_URL}{request_string}"
52+
is_wrong_token = validate_token(token)
53+
54+
if not is_wrong_token:
55+
try:
56+
response = requests.post(
57+
request_url,
58+
json=data,
59+
headers={"Authorization": AUTH_COOKIE["Authorization"].value, "Accept": "application/json"},
60+
)
61+
response.raise_for_status()
62+
response_message = response.json()
63+
response_code = response.status_code
64+
except requests.exceptions.ConnectionError as e:
65+
response_message = messages.INTERNAL_SERVER_ERROR
66+
response_code = json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR)
67+
logging.fatal(f"{e}")
68+
except requests.exceptions.HTTPError as e:
69+
response_message = e.response.json()
70+
response_code = e.response.status_code
71+
except Exception as e:
72+
response_message = messages.INTERNAL_SERVER_ERROR
73+
response_code = json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR)
74+
logging.fatal(f"{e}")
75+
finally:
76+
logging.fatal(f"{response_message}")
77+
return response_message, response_code
78+
return is_wrong_token
79+
80+
5081
def get_user(token):
5182
request_url = "/user"
5283
return get_request(request_url, token, params=None)
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import ast
2+
import json
3+
from http import HTTPStatus, cookies
4+
from datetime import datetime, timedelta
5+
from flask import request
6+
from flask_restx import Resource, marshal, Namespace
7+
from app import messages
8+
from app.api.request_api_utils import (
9+
post_request,
10+
post_request_with_token,
11+
get_request,
12+
put_request,
13+
http_response_checker,
14+
AUTH_COOKIE,
15+
validate_token)
16+
# Common Resources
17+
from app.api.resources.common import auth_header_parser
18+
# Validations
19+
from app.api.validations.user import *
20+
from app.api.validations.task_comment import (
21+
validate_task_comment_request_data,
22+
COMMENT_MAX_LENGTH,
23+
)
24+
from app.utils.validation_utils import get_length_validation_error_message,expected_fields_validator
25+
from app.utils.ms_constants import DEFAULT_PAGE, DEFAULT_USERS_PER_PAGE
26+
# Namespace Models
27+
from app.api.models.mentorship_relation import *
28+
# Databases Models
29+
from app.database.models.bit_schema.user_extension import UserExtensionModel
30+
from app.database.models.ms_schema.mentorship_relation import MentorshipRelationModel
31+
from app.database.models.bit_schema.organization import OrganizationModel
32+
from app.database.models.bit_schema.program import ProgramModel
33+
# DAOs
34+
from app.api.dao.user_extension import UserExtensionDAO
35+
from app.api.dao.personal_background import PersonalBackgroundDAO
36+
from app.api.dao.mentorship_relation_extension import MentorshipRelationExtensionDAO
37+
from app.api.dao.organization import OrganizationDAO
38+
from app.api.dao.program import ProgramDAO
39+
40+
mentorship_relation_ns = Namespace(
41+
"Mentorship Relation",
42+
description="Operations related to " "mentorship relations " "between users",
43+
)
44+
add_models_to_namespace(mentorship_relation_ns)
45+
46+
mentorshipRelationExtensionDAO = MentorshipRelationExtensionDAO()
47+
userExtensionDAO = UserExtensionDAO()
48+
OrganizationDAO = OrganizationDAO()
49+
ProgramDAO = ProgramDAO()
50+
51+
@mentorship_relation_ns.route("organizations/<int:organization_id>/programs/<int:program_id>/send_request")
52+
class SendRequest(Resource):
53+
@classmethod
54+
@mentorship_relation_ns.doc("send_request")
55+
@mentorship_relation_ns.expect(auth_header_parser, send_mentorship_extension_request_body)
56+
@mentorship_relation_ns.response(
57+
HTTPStatus.CREATED, "%s" % messages.MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY
58+
)
59+
@mentorship_relation_ns.response(
60+
HTTPStatus.BAD_REQUEST,
61+
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s"
62+
% (
63+
messages.NO_DATA_WAS_SENT,
64+
messages.MATCH_EITHER_MENTOR_OR_MENTEE,
65+
messages.MENTOR_ID_SAME_AS_MENTEE_ID,
66+
messages.END_TIME_BEFORE_PRESENT,
67+
messages.MENTOR_TIME_GREATER_THAN_MAX_TIME,
68+
messages.MENTOR_TIME_LESS_THAN_MIN_TIME,
69+
messages.MENTOR_NOT_AVAILABLE_TO_MENTOR,
70+
messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED,
71+
messages.MENTOR_ALREADY_IN_A_RELATION,
72+
messages.MENTEE_ALREADY_IN_A_RELATION,
73+
messages.MENTOR_ID_FIELD_IS_MISSING,
74+
messages.MENTEE_ID_FIELD_IS_MISSING,
75+
messages.NOTES_FIELD_IS_MISSING,
76+
messages.ORGANIZATION_DOES_NOT_EXIST,
77+
messages.PROGRAM_DOES_NOT_EXIST,
78+
),
79+
)
80+
@mentorship_relation_ns.response(
81+
HTTPStatus.UNAUTHORIZED,
82+
"%s\n%s\n%s"
83+
% (
84+
messages.TOKEN_HAS_EXPIRED,
85+
messages.TOKEN_IS_INVALID,
86+
messages.AUTHORISATION_TOKEN_IS_MISSING,
87+
),
88+
)
89+
@mentorship_relation_ns.response(
90+
HTTPStatus.NOT_FOUND,
91+
"%s\n%s" % (messages.MENTOR_DOES_NOT_EXIST, messages.MENTEE_DOES_NOT_EXIST),
92+
)
93+
def post(cls,organization_id,program_id):
94+
"""
95+
Creates a new mentorship relation request.
96+
97+
Also, sends an email notification to the recipient about new relation request.
98+
99+
Input:
100+
1. Header: valid access token
101+
2. Body: A dict containing
102+
- mentor_request_date,end_date: UNIX timestamp
103+
- notes: description of relation request
104+
105+
Returns:
106+
Success or failure message. A mentorship request is send to the other
107+
person whose ID is mentioned. The relation appears at /pending endpoint.
108+
"""
109+
110+
token = request.headers.environ["HTTP_AUTHORIZATION"]
111+
is_wrong_token = validate_token(token)
112+
113+
if not is_wrong_token:
114+
try:
115+
user_json = (AUTH_COOKIE["user"].value)
116+
user = ast.literal_eval(user_json)
117+
data = request.json
118+
119+
if not data:
120+
return messages.NO_DATA_WAS_SENT, HTTPStatus.BAD_REQUEST
121+
122+
is_field_valid = expected_fields_validator(data, send_mentorship_extension_request_body)
123+
if not is_field_valid.get("is_field_valid"):
124+
return is_field_valid.get("message"), HTTPStatus.BAD_REQUEST
125+
126+
is_valid = SendRequest.is_valid_data(data)
127+
if is_valid != {}:
128+
return is_valid, HTTPStatus.BAD_REQUEST
129+
130+
# Checking whether organization exists
131+
organization = OrganizationModel.query.filter_by(id=organization_id).first()
132+
if not organization:
133+
return messages.ORGANIZATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND
134+
135+
# Checking whether program exists
136+
program = ProgramModel.find_by_id(program_id)
137+
if not program or (program.organization_id != organization_id):
138+
return messages.PROGRAM_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND
139+
140+
mentor_id = organization.rep_id
141+
mentee_id = data['mentee_id']
142+
143+
mentorship_relation_data={}
144+
mentorship_relation_data['mentee_id'] = mentee_id
145+
mentorship_relation_data['mentor_id'] = int(mentor_id)
146+
mentorship_relation_data['end_date'] = data['end_date']
147+
mentorship_relation_data['notes'] = data['notes']
148+
149+
response = http_response_checker(post_request_with_token("/mentorship_relation/send_request",token, mentorship_relation_data))
150+
if response.status_code == 201:
151+
mentorshipRelationId = MentorshipRelationModel.query.filter_by(mentor_id=mentor_id).filter_by(mentee_id=mentee_id).first().id
152+
return MentorshipRelationExtensionDAO.createMentorshipRelationExtension(program_id, mentorshipRelationId ,data['mentee_request_date'])
153+
else:
154+
return response.message, HTTPStatus.BAD_REQUEST
155+
except ValueError as e:
156+
return e, HTTPStatus.BAD_REQUEST
157+
158+
return is_wrong_token
159+
160+
161+
@staticmethod
162+
def is_valid_data(data):
163+
164+
# Verify if request body has required fields
165+
if "mentee_id" not in data:
166+
return messages.MENTEE_ID_FIELD_IS_MISSING
167+
if "end_date" not in data:
168+
return messages.END_DATE_FIELD_IS_MISSING
169+
if "notes" not in data:
170+
return messages.NOTES_FIELD_IS_MISSING
171+
if "mentee_request_date" not in data:
172+
return messages.MENTEE_REQUEST_DATE_FIELD_IS_MISSING
173+
return {}

app/database/models/bit_schema/mentorship_relation_extension.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ def find_by_id(cls, _id) -> "MentorshipRelationExtensionModel":
6666
_id: The id of a mentorship_relations_extension.
6767
"""
6868
return cls.query.filter_by(id=_id).first()
69+
70+
@classmethod
71+
def find_by_program_id(cls, _id) -> "MentorshipRelationExtensionModel":
72+
73+
"""Returns the mentorship_relations_extension that has the passed program id.
74+
Args:
75+
_id: The id of a program.
76+
"""
77+
return cls.query.filter_by(program_id=_id).first()
6978

7079
def save_to_db(self) -> None:
7180
"""Saves the model to the database."""

app/database/models/bit_schema/program.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def json(self):
116116
def __repr__(self):
117117
"""Returns the program name, creation/start/end date and organization id."""
118118
return (
119-
f"Program id is {self.program.id}\n"
119+
f"Program id is {self.id}\n"
120120
f"Program name is {self.program_name}.\n"
121121
f"Organization's id is {self.organization_id}.\n"
122122
f"Program start date is {self.start_date}\n"

app/messages.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
MENTOR_ID_FIELD_IS_MISSING = {"message": "Mentor ID field is missing."}
7070
MENTEE_ID_FIELD_IS_MISSING = {"message": "Mentee ID field is missing."}
7171
END_DATE_FIELD_IS_MISSING = {"message": "End date field is missing."}
72+
MENTEE_REQUEST_DATE_FIELD_IS_MISSING = {"message": "Mentee request date field is missing."}
7273
NOTES_FIELD_IS_MISSING = {"message": "Notes field is missing."}
7374
USERNAME_FIELD_IS_MISSING = {"message": "The field username is missing."}
7475
PASSWORD_FIELD_IS_MISSING = {"message": "Password field is missing."}
@@ -137,6 +138,9 @@
137138
NO_DATA_FOR_UPDATING_PROFILE_WAS_SENT = {
138139
"message": "No data for updating profile was sent."
139140
}
141+
NO_DATA_WAS_SENT = {
142+
"message": "No data was sent."
143+
}
140144
ADDITIONAL_INFORMATION_DOES_NOT_EXIST = {
141145
"message": "No additional information found with your data. Please provide them now."
142146
}

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
APScheduler==3.6.3
22
Flask==1.1.2
33
Flask-Cors==3.0.9
4-
Flask-JWT-Extended==3.24.1
4+
Flask-JWT-Extended==3.25.0
55
Flask-Mail==0.9.1
66
Flask-Migrate==2.5.3
77
flask-restx==0.2.0

tests/mentorship_relations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)