Skip to content

Commit

Permalink
Merge branch 'development' into enhancement/projects-file-upload
Browse files Browse the repository at this point in the history
# Conflicts:
#	backend/project/__init__.py
#	backend/project/endpoints/courses/course_admin_relation.py
#	backend/project/endpoints/courses/course_details.py
#	backend/project/endpoints/courses/course_student_relation.py
#	backend/project/endpoints/courses/courses.py
#	backend/project/endpoints/courses/courses_utils.py
#	backend/project/endpoints/projects/project_detail.py
#	backend/project/endpoints/projects/projects.py
#	backend/project/utils/misc.py
#	backend/project/utils/query_agent.py
  • Loading branch information
Gerwoud committed Mar 11, 2024
2 parents 72a9b7d + 815fd2a commit e1f79c7
Show file tree
Hide file tree
Showing 32 changed files with 670 additions and 67 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: UGent-3
run-name: ${{ github.actor }} is running tests 🚀
on: [push, pull_request]
on: [pull_request]
jobs:
Frontend-tests:
runs-on: self-hosted
Expand Down Expand Up @@ -37,7 +37,7 @@ jobs:
working-directory: ./frontend
run: npm run lint
Backend-tests:
runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- uses: actions/checkout@v4

Expand Down
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ docs/_build/
dist/
venv/
.env
.run/
7 changes: 5 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
FROM python:3.9
RUN mkdir /app
WORKDIR /app/
WORKDIR /app
ADD ./project /app/
COPY requirements.txt /app/requirements.txt
RUN pip3 install -r requirements.txt
CMD ["python3", "/app"]
COPY . /app
ENTRYPOINT ["python"]
CMD ["__main__.py"]
73 changes: 73 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Project pigeonhole backend
## Prerequisites
**1. Clone the repo**
```sh
git clone git@github.com:SELab-2/UGent-3.git
```
**2. Installing required packages**

If you want the development environment: run both commands. If you only need to deploy, run the deployment command.

The [dev-requirements.txt](dev-requirements.txt) contains everything for writing tests and linters for maintaining quality code.
On the other hand the regular [requirements.txt](requirements.txt) installs the packages needed for
the regular base application.

- Deployment
```sh
pip install -r requirements.txt
```
- Development
```sh
pip install -r dev-requirements.txt
```

## Setting up the environment variables
The project requires a couple of environment variables to run, if you want to develop on this codebase.
Setting values for these variables can be done with a method to your own liking.

| Variable | Description |
|-------------------|----------------------------------------------------------------|
| DB_HOST | Url of where the database is located |
| POSTGRES_USER | Name of the user, needed to login to the postgres database |
| POSTGRES_PASSWORD | Password of the user, needed to login to the postgres database |
| POSTGRES_HOST | IP adress of the postgres database |
| POSTGRES_DB | Name of the postgres database |
| API_HOST | Location of the API root |

All the variables except the last one are for the database setup,
these are needed to make a connection with the database.
The last one is for keeping the API restful since the location of the resource should be located.

## Running the project
Once all the setup is done you can start the development server by
navigating to the backend directory and running:
```sh
python project
```
The server should now be located at `localhost:5000` and you can
start developing.

## Maintaining the codebase
### Writing tests
When writing new code it is important to maintain the right functionality so
writing tests is mandatory for this, the test library used in this codebase is [pytest](https://docs.pytest.org/en/8.0.x/).

If you want to write tests we highly advise to read the pytest documentation on how
to write tests, so they are kept conventional.

For executing the tests and testing your newly added functionality
you can run:
```sh
sudo ./run_tests.sh
```

Located in the backend directory.
### Running the linter
This codebase is kept clean by the [pylint](https://pypi.org/project/pylint/) linter.

If you want to execute the linter on all .py files in the project it can simply be done
with the command:
```sh
find . -type f -name "*.py" | xargs pylint
```
The code needs to get a 10/10 score to get pushed to the repository.
3 changes: 3 additions & 0 deletions backend/project/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .endpoints.index.index import index_bp
from .endpoints.projects.project_endpoint import project_bp
from .endpoints.courses.courses_config import courses_bp
from .endpoints.courses.courses_config import courses_bp
from .endpoints.users import users_bp


def create_app():
Expand All @@ -20,6 +22,7 @@ def create_app():
app.register_blueprint(index_bp)
app.register_blueprint(project_bp)
app.register_blueprint(courses_bp)
app.register_blueprint(users_bp)

return app

Expand Down
12 changes: 6 additions & 6 deletions backend/project/endpoints/courses/course_admin_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from flask import request
from flask_restful import Resource

from project.models.course_relations import CourseAdmin
from project.models.users import User
from project.models.course_relation import CourseAdmin
from project.models.user import User
from project.endpoints.courses.courses_utils import (
execute_query_abort_if_db_error,
commit_abort_if_error,
Expand All @@ -36,22 +36,22 @@ def get(self, course_id):
"""
This function will return all the admins of a course
"""
abort_url = API_URL + "/courses/" + str(course_id) + "/admins"
abort_url = urljoin(f"{RESPONSE_URL}/" , f"{str(course_id)}/", "admins")
get_course_abort_if_not_found(course_id)

return query_selected_from_model(
CourseAdmin,
abort_url,
select_values=["uid"],
url_mapper={"uid": urljoin(API_URL + "/", "users")},
url_mapper={"uid": urljoin(f"{API_URL}/", "users")},
filters={"course_id": course_id},
)

def post(self, course_id):
"""
Api endpoint for adding new admins to a course, can only be done by the teacher
"""
abort_url = API_URL + "/courses/" + str(course_id) + "/admins"
abort_url = urljoin(f"{RESPONSE_URL}/" , f"{str(course_id)}/", "admins")
teacher = request.args.get("uid")
data = request.get_json()
assistant = data.get("admin_uid")
Expand All @@ -76,7 +76,7 @@ def delete(self, course_id):
"""
Api endpoint for removing admins of a course, can only be done by the teacher
"""
abort_url = API_URL + "/courses/" + str(course_id) + "/admins"
abort_url = urljoin(f"{RESPONSE_URL}/" , f"{str(course_id)}/", "admins")
teacher = request.args.get("uid")
data = request.get_json()
assistant = data.get("admin_uid")
Expand Down
12 changes: 6 additions & 6 deletions backend/project/endpoints/courses/course_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from flask_restful import Resource
from sqlalchemy.exc import SQLAlchemyError

from project.models.courses import Course
from project.models.course_relations import CourseAdmin, CourseStudent
from project.models.course import Course
from project.models.course_relation import CourseAdmin, CourseStudent

from project import db
from project.utils.query_agent import delete_by_id_from_model, patch_by_id_from_model
Expand Down Expand Up @@ -64,8 +64,8 @@ def get(self, course_id):

user_url = urljoin(API_URL + "/", "users")

admin_ids = [ urljoin(user_url + "/" , admin[0]) for admin in admins]
student_ids = [ urljoin(user_url + "/", student[0]) for student in students]
admin_ids = [ urljoin(f"{user_url}/" , admin[0]) for admin in admins]
student_ids = [ urljoin(f"{user_url}/", student[0]) for student in students]

result = {
'course_id': course_details.course_id,
Expand All @@ -77,9 +77,9 @@ def get(self, course_id):
}

return {
"message": "Succesfully retrieved course with course_id: " + str(course_id),
"message": f"Succesfully retrieved course with course_id: {str(course_id)}",
"data": result,
"url": urljoin(RESPONSE_URL + "/", str(course_id))
"url": urljoin(f"{RESPONSE_URL}/", str(course_id))
}
except SQLAlchemyError:
return {
Expand Down
20 changes: 10 additions & 10 deletions backend/project/endpoints/courses/course_student_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from flask_restful import Resource

from project import db
from project.models.course_relations import CourseStudent
from project.models.course_relation import CourseStudent
from project.endpoints.courses.courses_utils import (
execute_query_abort_if_db_error,
add_abort_if_error,
Expand Down Expand Up @@ -44,14 +44,14 @@ def get(self, course_id):
to get all the users assigned to a course
everyone can get this data so no need to have uid query in the link
"""
abort_url = API_URL + "/courses/" + str(course_id) + "/students"
abort_url = f"{API_URL}/courses/{str(course_id)}/students"
get_course_abort_if_not_found(course_id)

return query_selected_from_model(
CourseStudent,
abort_url,
select_values=["uid"],
url_mapper={"uid": urljoin(API_URL + "/", "users")},
url_mapper={"uid": urljoin(f"{API_URL}/", "users")},
filters={"course_id": course_id}
)

Expand All @@ -60,7 +60,7 @@ def post(self, course_id):
Allows admins of a course to assign new students by posting to:
/courses/course_id/students with a list of uid in the request body under key "students"
"""
abort_url = API_URL + "/courses/" + str(course_id) + "/students"
abort_url = f"{API_URL}/courses/{str(course_id)}/students"
uid = request.args.get("uid")
data = request.get_json()
student_uids = data.get("students")
Expand All @@ -74,14 +74,14 @@ def post(self, course_id):
if student_relation:
db.session.rollback()
message = (
"Student with uid " + uid + " is already assigned to the course"
f"Student with uid {uid} is already assigned to the course"
)
return json_message(message), 400
add_abort_if_error(CourseStudent(uid=uid, course_id=course_id), abort_url)
commit_abort_if_error(abort_url)
response = json_message("User were succesfully added to the course")
response = json_message("Users were succesfully added to the course")
response["url"] = abort_url
data = {"students": [API_URL + "/users/" + uid for uid in student_uids]}
data = {"students": [f"{API_URL}/users/{uid}" for uid in student_uids]}
response["data"] = data
return response, 201

Expand All @@ -91,7 +91,7 @@ def delete(self, course_id):
/courses/course_id/students with inside the request body
a field "students" = [list of uids to unassign]
"""
abort_url = API_URL + "/courses/" + str(course_id) + "/students"
abort_url = f"{API_URL}/courses/{str(course_id)}/students"
uid = request.args.get("uid")
data = request.get_json()
student_uids = data.get("students")
Expand All @@ -106,6 +106,6 @@ def delete(self, course_id):
delete_abort_if_error(student_relation, abort_url)
commit_abort_if_error(abort_url)

response = json_message("User were succesfully removed from the course")
response["url"] = API_URL + "/courses/" + str(course_id) + "/students"
response = json_message("Users were succesfully removed from the course")
response["url"] = f"{API_URL}/courses/{str(course_id)}/students"
return response
2 changes: 1 addition & 1 deletion backend/project/endpoints/courses/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from flask import request
from flask_restful import Resource

from project.models.courses import Course
from project.models.course import Course
from project.utils.query_agent import query_selected_from_model, insert_into_model

load_dotenv()
Expand Down
18 changes: 9 additions & 9 deletions backend/project/endpoints/courses/courses_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from sqlalchemy.exc import SQLAlchemyError

from project import db
from project.models.course_relations import CourseAdmin
from project.models.users import User
from project.models.courses import Course
from project.models.course_relation import CourseAdmin
from project.models.user import User
from project.models.course import Course

load_dotenv()
API_URL = getenv("API_HOST")
Expand Down Expand Up @@ -100,7 +100,7 @@ def abort_if_not_teacher_or_none_assistant(course_id, teacher, assistant):
HTTPException: If the current user is not authorized or
if the UID of the person to be made an admin is missing in the request body.
"""
url = API_URL + "/courses/" + str(course_id) + "/admins"
url = f"{API_URL}/courses/{str(course_id)}/admins"
abort_if_uid_is_none(teacher, url)

course = get_course_abort_if_not_found(course_id)
Expand Down Expand Up @@ -131,7 +131,7 @@ def abort_if_none_uid_student_uids_or_non_existant_course_id(
403: If the user is not authorized to assign new students to the course.
400: If the request body does not contain the required 'students' field.
"""
url = API_URL + "/courses/" + str(course_id) + "/students"
url = f"{API_URL}/courses/{str(course_id)}/students"
get_course_abort_if_not_found(course_id)
abort_if_no_user_found_for_uid(uid, url)
query = CourseAdmin.query.filter_by(uid=uid, course_id=course_id)
Expand Down Expand Up @@ -177,7 +177,7 @@ def abort_if_no_user_found_for_uid(uid, url):
user = execute_query_abort_if_db_error(query, url)

if not user:
response = json_message("User with uid " + uid + " was not found")
response = json_message(f"User with uid {uid} was not found")
response["url"] = url
abort(404, description=response)
return user
Expand All @@ -196,7 +196,7 @@ def get_admin_relation(uid, course_id):
"""
return execute_query_abort_if_db_error(
CourseAdmin.query.filter_by(uid=uid, course_id=course_id),
url=API_URL + "/courses/" + str(course_id) + "/admins",
url=f"{API_URL}/courses/{str(course_id)}/admins",
)


Expand Down Expand Up @@ -224,11 +224,11 @@ def get_course_abort_if_not_found(course_id):
Course: The course with the given ID.
"""
query = Course.query.filter_by(course_id=course_id)
course = execute_query_abort_if_db_error(query, API_URL + "/courses")
course = execute_query_abort_if_db_error(query, f"{API_URL}/courses")

if not course:
response = json_message("Course not found")
response["url"] = API_URL + "/courses"
response["url"] = f"{API_URL}/courses"
abort(404, description=response)

return course
Loading

0 comments on commit e1f79c7

Please sign in to comment.