add api routes to crud operation on student models for coordinator
This commit is contained in:
parent
e051f3a101
commit
5607f3cd08
@ -8,6 +8,7 @@ from .config import config
|
|||||||
from .dependencies import db, ma
|
from .dependencies import db, ma
|
||||||
from .commands.startapp import startapp
|
from .commands.startapp import startapp
|
||||||
from .utils import import_models
|
from .utils import import_models
|
||||||
|
from .api import api_bp
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_name: str = None) -> Flask:
|
def create_app(config_name: str = None) -> Flask:
|
||||||
@ -29,8 +30,7 @@ def create_app(config_name: str = None) -> Flask:
|
|||||||
|
|
||||||
Migrate(app, db)
|
Migrate(app, db)
|
||||||
|
|
||||||
# register blueprints
|
app.register_blueprint(api_bp)
|
||||||
# app.register_blueprint(blueprint)
|
|
||||||
|
|
||||||
# register commands
|
# register commands
|
||||||
app.cli.add_command(startapp)
|
app.cli.add_command(startapp)
|
||||||
|
7
backend/app/api.py
Normal file
7
backend/app/api.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
from .coordinator.routes import bp as coordinator_bp
|
||||||
|
|
||||||
|
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
# register blueprints here
|
||||||
|
api_bp.register_blueprint(coordinator_bp)
|
0
backend/app/base/__init__.py
Normal file
0
backend/app/base/__init__.py
Normal file
15
backend/app/base/models.py
Normal file
15
backend/app/base/models.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from ..dependencies import db
|
||||||
|
|
||||||
|
|
||||||
|
class Base(db.Model):
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Person(db.Model):
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
first_name = db.Column(db.String(255), nullable=False)
|
||||||
|
last_name = db.Column(db.String(255), nullable=False)
|
||||||
|
email = db.Column(db.String(120), unique=True)
|
0
backend/app/coordinator/__init__.py
Normal file
0
backend/app/coordinator/__init__.py
Normal file
8
backend/app/coordinator/exceptions.py
Normal file
8
backend/app/coordinator/exceptions.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class CSVException(Exception):
|
||||||
|
"""Main csv exception"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidNameOrTypeHeaderException(CSVException):
|
||||||
|
"""Throw if csv file has invalid name or type of header"""
|
||||||
|
pass
|
5
backend/app/coordinator/models.py
Normal file
5
backend/app/coordinator/models.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# from ..base.models import Base
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Coordinator(Base):
|
||||||
|
# __tablename__ = 'coordinators'
|
7
backend/app/coordinator/routes/__init__.py
Normal file
7
backend/app/coordinator/routes/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
from .students import bp as students_bp
|
||||||
|
|
||||||
|
bp = Blueprint("coordinator", __name__, url_prefix="/coordinator")
|
||||||
|
|
||||||
|
bp.register_blueprint(students_bp)
|
100
backend/app/coordinator/routes/students.py
Normal file
100
backend/app/coordinator/routes/students.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
from flask import Blueprint, request, make_response, jsonify, Response
|
||||||
|
from marshmallow import ValidationError
|
||||||
|
|
||||||
|
from ...students.models import Student
|
||||||
|
from ..schemas import StudentSchema, StudentEditSchema, StudentCreateSchema
|
||||||
|
from ...dependencies import db
|
||||||
|
from ..utils import parse_csv
|
||||||
|
from ..exceptions import InvalidNameOrTypeHeaderException
|
||||||
|
|
||||||
|
bp = Blueprint("students", __name__, url_prefix="/students")
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/", methods=["GET"])
|
||||||
|
def list_students() -> Tuple[dict, int]:
|
||||||
|
students_schema = StudentSchema(many=True)
|
||||||
|
students = Student.query.all()
|
||||||
|
return {"students": students_schema.dump(students)}, 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:index>/", methods=["GET"])
|
||||||
|
def detail_student(index: int) -> Tuple[dict, int]:
|
||||||
|
student_schema = StudentSchema()
|
||||||
|
student = Student.query.filter_by(index=index).first()
|
||||||
|
if student is not None:
|
||||||
|
return student_schema.dump(student), 200
|
||||||
|
return {"error": f"Student with {index} index doesn't exist!"}, 404
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:index>/", methods=["DELETE"])
|
||||||
|
def delete_student(index: int) -> Tuple[dict, int]:
|
||||||
|
student = Student.query.filter_by(index=index).first()
|
||||||
|
if student is not None:
|
||||||
|
db.session.delete(student)
|
||||||
|
db.session.commit()
|
||||||
|
return {"message": "Student was deleted!"}, 202
|
||||||
|
return {"error": f"Student with {index} index doesn't exist!"}, 404
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:index>/", methods=["PUT"])
|
||||||
|
def edit_student(index: int) -> Tuple[dict, int]:
|
||||||
|
request_data = request.get_json()
|
||||||
|
student_schema = StudentEditSchema()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = student_schema.load(request_data)
|
||||||
|
if not data:
|
||||||
|
return {"error": "You have passed empty data!"}, 400
|
||||||
|
|
||||||
|
student_query = Student.query.filter_by(index=index)
|
||||||
|
student = student_query.first()
|
||||||
|
if student is None:
|
||||||
|
return {"error": "Not Found student!"}, 404
|
||||||
|
|
||||||
|
student_query.update(data)
|
||||||
|
db.session.commit()
|
||||||
|
except ValidationError as e:
|
||||||
|
return {"error": e.messages}, 422
|
||||||
|
return {"message": "Student was updated!"}, 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/", methods=["POST"])
|
||||||
|
def create_student() -> Tuple[dict, int] or Response:
|
||||||
|
request_data = request.get_json()
|
||||||
|
student_schema = StudentCreateSchema()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = student_schema.load(request_data)
|
||||||
|
|
||||||
|
index = data['index']
|
||||||
|
student = Student.query.filter_by(index=index).first()
|
||||||
|
if student is not None:
|
||||||
|
return {"error": "Student has already exists!"}, 400
|
||||||
|
|
||||||
|
dummy_email = f'student{randint(1, 300_000)}@gmail.com'
|
||||||
|
student = Student(**data, email=dummy_email)
|
||||||
|
db.session.add(student)
|
||||||
|
db.session.commit()
|
||||||
|
except ValidationError as e:
|
||||||
|
return {'error': e.messages}, 422
|
||||||
|
return {"message": "Student was created!"}, 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/upload/", methods=["POST"])
|
||||||
|
def upload_students() -> Tuple[dict, int]:
|
||||||
|
"""Maybe in the future move to celery workers"""
|
||||||
|
if (uploaded_file := request.files.get('file')) is None:
|
||||||
|
return {"error": "You didn't attach a csv file!"}, 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
students = parse_csv(uploaded_file)
|
||||||
|
except InvalidNameOrTypeHeaderException:
|
||||||
|
return {"error": "Invalid format of csv file!"}, 400
|
||||||
|
|
||||||
|
for student in students:
|
||||||
|
db.session.add(student)
|
||||||
|
db.session.commit()
|
||||||
|
return {"message": "Students was created by uploading csv file!"}, 200
|
37
backend/app/coordinator/schemas.py
Normal file
37
backend/app/coordinator/schemas.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from ..dependencies import ma
|
||||||
|
from ..students.models import Student, Group
|
||||||
|
from marshmallow import fields, validate, ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
def validate_index(index):
|
||||||
|
if len(str(index)) > 6:
|
||||||
|
raise ValidationError("Length of index is too long!")
|
||||||
|
elif len(str(index)) < 6:
|
||||||
|
raise ValidationError("Length of index is too short!")
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
include_relationships = False
|
||||||
|
|
||||||
|
|
||||||
|
class StudentSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
group = fields.Nested(GroupSchema)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
|
||||||
|
|
||||||
|
class StudentCreateSchema(ma.Schema):
|
||||||
|
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
index = fields.Integer(validate=validate_index, required=True)
|
||||||
|
mode = fields.Boolean(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class StudentEditSchema(ma.Schema):
|
||||||
|
first_name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||||
|
last_name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||||
|
index = fields.Integer(validate=validate_index)
|
||||||
|
mode = fields.Boolean()
|
37
backend/app/coordinator/utils.py
Normal file
37
backend/app/coordinator/utils.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from typing import List
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from .exceptions import InvalidNameOrTypeHeaderException
|
||||||
|
from ..students.models import Student
|
||||||
|
|
||||||
|
|
||||||
|
def check_columns(df: pd.DataFrame, columns: List[str]) -> bool:
|
||||||
|
flag = True
|
||||||
|
col_types = ['object', 'object', 'int', 'int']
|
||||||
|
print(df.dtypes['first_name'])
|
||||||
|
for name, col_type in zip(columns, col_types):
|
||||||
|
if name not in df and df.dtypes[name].startswith(col_type):
|
||||||
|
flag = False
|
||||||
|
break
|
||||||
|
|
||||||
|
return flag
|
||||||
|
|
||||||
|
|
||||||
|
def parse_csv(file) -> List[Student]:
|
||||||
|
df = pd.read_csv(file)
|
||||||
|
columns = ['first_name', 'last_name', 'index', 'mode']
|
||||||
|
|
||||||
|
if not check_columns(df, columns):
|
||||||
|
raise InvalidNameOrTypeHeaderException
|
||||||
|
|
||||||
|
students = []
|
||||||
|
for _, item in df.iterrows():
|
||||||
|
data = {}
|
||||||
|
for c in columns:
|
||||||
|
data[c] = item[c]
|
||||||
|
data['email'] = f'student{randint(1, 300_000)}@gmail.com'
|
||||||
|
students.append(Student(**data))
|
||||||
|
|
||||||
|
return students
|
0
backend/app/students/__init__.py
Normal file
0
backend/app/students/__init__.py
Normal file
28
backend/app/students/models.py
Normal file
28
backend/app/students/models.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from ..dependencies import db
|
||||||
|
from ..base.models import Person, Base
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisor(Base, Person):
|
||||||
|
__tablename__ = "project_supervisors"
|
||||||
|
|
||||||
|
limit_group = db.Column(db.Integer, default=1, nullable=False)
|
||||||
|
mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary
|
||||||
|
|
||||||
|
|
||||||
|
class Group(Base):
|
||||||
|
__tablename__ = "groups"
|
||||||
|
|
||||||
|
name = db.Column(db.String(60), nullable=False)
|
||||||
|
project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'))
|
||||||
|
project_supervisor = db.relationship('ProjectSupervisor', backref='groups', lazy=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Student(Person):
|
||||||
|
__tablename__ = "students"
|
||||||
|
|
||||||
|
index = db.Column(db.Integer, primary_key=True)
|
||||||
|
first_term = db.Column(db.Integer, default=0, nullable=False)
|
||||||
|
second_term = db.Column(db.Integer, default=0, nullable=False)
|
||||||
|
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
|
||||||
|
group = db.relationship('Group', backref='students', lazy=True)
|
||||||
|
mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary
|
0
backend/app/students/routes.py
Normal file
0
backend/app/students/routes.py
Normal file
0
backend/app/students/schemas.py
Normal file
0
backend/app/students/schemas.py
Normal file
59
backend/migrations/versions/45be50e56689_.py
Normal file
59
backend/migrations/versions/45be50e56689_.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 45be50e56689
|
||||||
|
Revises:
|
||||||
|
Create Date: 2022-05-17 19:36:35.976642
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '45be50e56689'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('project_supervisors',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('first_name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('last_name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('limit_group', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('mode', sa.Boolean(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('email')
|
||||||
|
)
|
||||||
|
op.create_table('groups',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=60), nullable=False),
|
||||||
|
sa.Column('project_supervisor_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['project_supervisor_id'], ['project_supervisors.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('students',
|
||||||
|
sa.Column('first_name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('last_name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('index', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('first_term', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('second_term', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('group_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('mode', sa.Boolean(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('index'),
|
||||||
|
sa.UniqueConstraint('email')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('students')
|
||||||
|
op.drop_table('groups')
|
||||||
|
op.drop_table('project_supervisors')
|
||||||
|
# ### end Alembic commands ###
|
@ -17,7 +17,9 @@ MarkupSafe==2.1.1
|
|||||||
marshmallow==3.15.0
|
marshmallow==3.15.0
|
||||||
marshmallow-sqlalchemy==0.28.0
|
marshmallow-sqlalchemy==0.28.0
|
||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
|
numpy==1.22.3
|
||||||
packaging==21.3
|
packaging==21.3
|
||||||
|
pandas==1.4.2
|
||||||
pluggy==1.0.0
|
pluggy==1.0.0
|
||||||
py==1.11.0
|
py==1.11.0
|
||||||
pycodestyle==2.8.0
|
pycodestyle==2.8.0
|
||||||
@ -25,7 +27,9 @@ pyflakes==2.4.0
|
|||||||
pyparsing==3.0.9
|
pyparsing==3.0.9
|
||||||
pytest==7.1.2
|
pytest==7.1.2
|
||||||
pytest-flask==1.2.0
|
pytest-flask==1.2.0
|
||||||
|
python-dateutil==2.8.2
|
||||||
python-dotenv==0.20.0
|
python-dotenv==0.20.0
|
||||||
|
pytz==2022.1
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
SQLAlchemy==1.4.36
|
SQLAlchemy==1.4.36
|
||||||
tomli==2.0.1
|
tomli==2.0.1
|
||||||
|
9
data/students.csv
Normal file
9
data/students.csv
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
first_name,last_name,index,mode
|
||||||
|
Patryk,Drzewiński,452790,1
|
||||||
|
Adam,Skowronek,452791,1
|
||||||
|
Mariia,Kuzmenko,452792,1
|
||||||
|
Dominik,Cupał,452793,1
|
||||||
|
Natalia,Wasik,452794,0
|
||||||
|
Michalina,Gaj,452795,0
|
||||||
|
Jan,Kowalski,452796,0
|
||||||
|
Adrian,Kowalski,452797,1
|
|
Loading…
Reference in New Issue
Block a user