Compare commits
1 Commits
developmen
...
master
Author | SHA1 | Date | |
---|---|---|---|
bcd06d85bd |
201
LICENSE
201
LICENSE
@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2023 Projekt inżynierski System PRI
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
@ -4,7 +4,7 @@ System organizacji PRI
|
|||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
First create `.env` files in `./backend` and `./frontend` and fill with similar data like `.env.example`.
|
First create `.env` file and fill with similar data like `./backend/.env.example`.
|
||||||
|
|
||||||
Run application and init database
|
Run application and init database
|
||||||
```bash
|
```bash
|
||||||
|
@ -13,3 +13,4 @@ COPY requirements.txt .
|
|||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
FROM nginx:1.23-alpine
|
|
||||||
|
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
|
||||||
COPY ./nginx.conf /etc/nginx/conf.d
|
|
@ -1,19 +0,0 @@
|
|||||||
FROM python:3.8-slim-buster
|
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apt update && \
|
|
||||||
pip install --upgrade pip
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN flask db stamp head
|
|
||||||
RUN flask db migrate
|
|
||||||
RUN flask db upgrade
|
|
@ -25,24 +25,8 @@ Run tests
|
|||||||
```bash
|
```bash
|
||||||
pytest
|
pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
Run all tests in specific python module
|
|
||||||
```bash
|
|
||||||
pytest ./tests/unit_tests/test_file.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Run one test inside of python module
|
|
||||||
```bash
|
|
||||||
pytest ./tests/unit_tests/test_file.py::test_function_name
|
|
||||||
```
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### Format code:
|
|
||||||
```bash
|
|
||||||
black path/to/file.py
|
|
||||||
isort path/to/file.py
|
|
||||||
flake8
|
|
||||||
```
|
|
||||||
### Useful commands:
|
### Useful commands:
|
||||||
Add new package
|
Add new package
|
||||||
```bash
|
```bash
|
||||||
@ -61,19 +45,15 @@ flask startapp NAME_OF_APP
|
|||||||
```
|
```
|
||||||
Above command create package structure:
|
Above command create package structure:
|
||||||
\
|
\
|
||||||
Create serializer in `schemas.py` file:
|
Create serializer in `__schemas__.py` file:
|
||||||
```python3
|
```python3
|
||||||
from marshmallow import Schema, fields
|
class ExampleSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
class ExampleSchema(Schema):
|
model = MODEL_NAME
|
||||||
id = fields.Integer()
|
|
||||||
name = fields.Str()
|
|
||||||
```
|
```
|
||||||
\
|
\
|
||||||
Create models in `models.py` file:
|
Create models in `__models__.py` file:
|
||||||
```python3
|
```python3
|
||||||
from ..dependencies import db
|
|
||||||
|
|
||||||
class Example(db.Model):
|
class Example(db.Model):
|
||||||
__tablename__ = "examples"
|
__tablename__ = "examples"
|
||||||
|
|
||||||
@ -81,7 +61,7 @@ class Example(db.Model):
|
|||||||
title = db.Column(db.String(255), unique=True)
|
title = db.Column(db.String(255), unique=True)
|
||||||
```
|
```
|
||||||
\
|
\
|
||||||
Create api routes in ```routes.py``` file. You only have to register blueprint in `app/__init__.py`.
|
Create api routes in ```__routes__.py``` file. You only have to register blueprint in `app/__init__.py`.
|
||||||
```python3
|
```python3
|
||||||
from flask import Blueprint, make_response, jsonify, Response
|
from flask import Blueprint, make_response, jsonify, Response
|
||||||
|
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from apiflask import APIFlask
|
from apiflask import APIFlask
|
||||||
from flask_cors import CORS
|
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
from .api import api_bp
|
|
||||||
from .commands.clear_db import clear_db
|
|
||||||
from .commands.startapp import startapp
|
|
||||||
from .config import config
|
from .config import config
|
||||||
from .dependencies import db, ma
|
from .dependencies import db, ma
|
||||||
from .errors import register_error_handlers
|
from .commands.startapp import startapp
|
||||||
|
from .commands.init_db import init_db
|
||||||
|
from .commands.clear_db import clear_db
|
||||||
from .utils import import_models
|
from .utils import import_models
|
||||||
|
from .api import api_bp
|
||||||
|
from .errors import request_entity_too_large, register_error_handlers
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_name: str = "") -> APIFlask:
|
def create_app(config_name: str = '') -> APIFlask:
|
||||||
if config_name is None:
|
if config_name is None:
|
||||||
config_name = os.environ.get("FLASK_ENV")
|
config_name = os.environ.get("FLASK_ENV")
|
||||||
|
|
||||||
app = APIFlask(__name__, docs_path="/")
|
app = APIFlask(__name__, docs_path='/')
|
||||||
app.config.from_object(config.get(config_name) or config.get("development"))
|
app.config.from_object(config.get(config_name) or config.get("development"))
|
||||||
|
|
||||||
if app.config["ENABLE_CORS"]:
|
if app.config['ENABLE_CORS']:
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
@ -36,9 +37,11 @@ def create_app(config_name: str = "") -> APIFlask:
|
|||||||
|
|
||||||
# register commands
|
# register commands
|
||||||
app.cli.add_command(startapp)
|
app.cli.add_command(startapp)
|
||||||
|
app.cli.add_command(init_db)
|
||||||
app.cli.add_command(clear_db)
|
app.cli.add_command(clear_db)
|
||||||
|
|
||||||
# register errors
|
# register errors
|
||||||
register_error_handlers(app)
|
register_error_handlers(app)
|
||||||
|
# app.register_error_handler(413, request_entity_too_large)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from .coordinator.routes import bp as coordinator_bp
|
from .coordinator.routes import bp as coordinator_bp
|
||||||
from .examination_schedule.routes import bp as examination_schedules_bp
|
|
||||||
from .project_supervisor.routes import bp as project_supervisor_bp
|
from .project_supervisor.routes import bp as project_supervisor_bp
|
||||||
from .students.routes import bp as students_bp
|
from .students.routes import bp as students_bp
|
||||||
|
from .examination_schedule.routes import bp as examination_schedules_bp
|
||||||
|
|
||||||
api_bp = Blueprint("api", __name__, url_prefix="/api")
|
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
||||||
|
|
||||||
# register blueprints here
|
# register blueprints here
|
||||||
api_bp.register_blueprint(coordinator_bp)
|
api_bp.register_blueprint(coordinator_bp)
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class ModeGroups(str, Enum):
|
|
||||||
STATIONARY = "s"
|
|
||||||
NON_STATIONARY = "n"
|
|
||||||
ENGLISH_SPEAKING_STATIONARY = "e"
|
|
||||||
|
|
||||||
|
|
||||||
class EnrollmentsMode(str, Enum):
|
|
||||||
"""
|
|
||||||
What means specific values?:
|
|
||||||
INIT - project supervisor set your availability time for this examination
|
|
||||||
schedule, students cannot use examination schedule
|
|
||||||
OPEN - students can assign to term of defence, project supervisor cannot
|
|
||||||
use examination schedule
|
|
||||||
CLOSE - students and project supervisor cannot use examination schedule
|
|
||||||
"""
|
|
||||||
|
|
||||||
INIT = "i"
|
|
||||||
OPEN = "o"
|
|
||||||
CLOSE = "c"
|
|
@ -12,4 +12,4 @@ class Person(db.Model):
|
|||||||
|
|
||||||
first_name = db.Column(db.String(255), index=True, nullable=False)
|
first_name = db.Column(db.String(255), index=True, nullable=False)
|
||||||
last_name = db.Column(db.String(255), index=True, nullable=False)
|
last_name = db.Column(db.String(255), index=True, nullable=False)
|
||||||
email = db.Column(db.String(120), index=True, nullable=False)
|
email = db.Column(db.String(120), unique=True)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
from marshmallow import Schema, fields
|
|
||||||
|
|
||||||
|
|
||||||
class MessageSchema(Schema):
|
|
||||||
message = fields.Str()
|
|
@ -10,13 +10,11 @@ class PaginationResponse(TypedDict):
|
|||||||
max_pages: int
|
max_pages: int
|
||||||
|
|
||||||
|
|
||||||
def order_by_column_name(
|
def order_by_column_name(query: BaseQuery, model_field: str, order_by_col_name: Union[str, None]) -> BaseQuery:
|
||||||
query: BaseQuery, model_field: str, order_by_col_name: Union[str, None]
|
|
||||||
) -> BaseQuery:
|
|
||||||
if order_by_col_name is not None:
|
if order_by_col_name is not None:
|
||||||
if order_by_col_name == "asc":
|
if order_by_col_name == 'asc':
|
||||||
query = query.order_by(model_field)
|
query = query.order_by(model_field)
|
||||||
elif order_by_col_name == "desc":
|
elif order_by_col_name == 'desc':
|
||||||
query = query.order_by(desc(model_field))
|
query = query.order_by(desc(model_field))
|
||||||
|
|
||||||
return query
|
return query
|
||||||
@ -29,12 +27,12 @@ def paginate_models(page: int, query: BaseQuery, per_page=10) -> PaginationRespo
|
|||||||
else:
|
else:
|
||||||
query = query.paginate(page=default_page, per_page=per_page, error_out=False)
|
query = query.paginate(page=default_page, per_page=per_page, error_out=False)
|
||||||
|
|
||||||
return {"items": query.items, "max_pages": query.pages}
|
return {
|
||||||
|
'items': query.items,
|
||||||
|
'max_pages': query.pages
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_allowed_extensions(filename: str):
|
def is_allowed_extensions(filename: str):
|
||||||
return (
|
return '.' in filename and \
|
||||||
"." in filename
|
filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']
|
||||||
and filename.rsplit(".", 1)[1].lower()
|
|
||||||
in current_app.config["ALLOWED_EXTENSIONS"]
|
|
||||||
)
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from click import command
|
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
from click import command
|
||||||
|
|
||||||
from ..dependencies import db
|
from ..dependencies import db
|
||||||
|
|
||||||
|
|
||||||
@command("clear_db")
|
@command('clear_db')
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def clear_db() -> None:
|
def clear_db() -> None:
|
||||||
"""Clear database"""
|
"""Clear database"""
|
||||||
|
37
backend/app/commands/init_db.py
Normal file
37
backend/app/commands/init_db.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from flask.cli import with_appcontext
|
||||||
|
from click import command
|
||||||
|
|
||||||
|
from ..dependencies import db
|
||||||
|
from ..factory import ProjectSupervisorFactory, GroupFactory, StudentFactory
|
||||||
|
|
||||||
|
|
||||||
|
@command('init_db')
|
||||||
|
@with_appcontext
|
||||||
|
def init_db() -> None:
|
||||||
|
"""Fill database with some data"""
|
||||||
|
db.drop_all()
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
num_of_supervisors = 5
|
||||||
|
|
||||||
|
projects_supervisors = [ProjectSupervisorFactory() for _ in range(num_of_supervisors)]
|
||||||
|
db.session.add_all(projects_supervisors)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
groups = [GroupFactory(project_supervisor=projects_supervisors[i]) for i in range(num_of_supervisors)]
|
||||||
|
db.session.add_all(groups)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
num_of_students = num_of_supervisors * 3
|
||||||
|
students = [StudentFactory(group=groups[i % num_of_supervisors]) for i in range(num_of_students)]
|
||||||
|
|
||||||
|
max_count = 10
|
||||||
|
max_length = len(students)
|
||||||
|
start_count = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if start_count > max_length:
|
||||||
|
break
|
||||||
|
db.session.add_all(students[start_count:max_count])
|
||||||
|
db.session.commit()
|
||||||
|
start_count += max_count
|
@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from click import argument, command
|
|
||||||
from click.exceptions import ClickException
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from click import command, argument
|
||||||
|
from click.exceptions import ClickException
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ def startapp(name: str) -> None:
|
|||||||
"""Create the application structure"""
|
"""Create the application structure"""
|
||||||
|
|
||||||
if not re.match("^[a-zA-Z].*$", name):
|
if not re.match("^[a-zA-Z].*$", name):
|
||||||
raise ClickException("The name argument must be type of string!")
|
raise ClickException(f"The name argument must be type of string!")
|
||||||
|
|
||||||
app_dir = current_app.config["SRC_DIR"] / name
|
app_dir = current_app.config["SRC_DIR"] / name
|
||||||
if os.path.exists(app_dir):
|
if os.path.exists(app_dir):
|
||||||
@ -26,14 +26,12 @@ def startapp(name: str) -> None:
|
|||||||
Below you write a schema of model
|
Below you write a schema of model
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
from marshmallow import Schema
|
|
||||||
|
|
||||||
class ExampleSchema(ma.SQLAlchemyAutoSchema):
|
class ExampleSchema(ma.SQLAlchemyAutoSchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MODEL_NAME
|
model = MODEL_NAME
|
||||||
\'\'\'
|
\'\'\'
|
||||||
|
|
||||||
from marshmallow import Schema
|
from ..dependencies import ma
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_content = """\'\'\'
|
model_content = """\'\'\'
|
||||||
@ -45,6 +43,7 @@ class Example(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
title = db.Column(db.String(255), unique=True)
|
title = db.Column(db.String(255), unique=True)
|
||||||
|
|
||||||
\'\'\'
|
\'\'\'
|
||||||
|
|
||||||
from ..dependencies import db
|
from ..dependencies import db
|
||||||
|
@ -8,30 +8,22 @@ class Config:
|
|||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
SRC_DIR = BASE_DIR / "app"
|
SRC_DIR = BASE_DIR / "app"
|
||||||
EXCLUDED_DIRS = ["__pycache__", "commands"]
|
EXCLUDED_DIRS = ["__pycache__", "commands"]
|
||||||
TIMEZONE = "Europe/Warsaw"
|
TIMEZONE = 'Europe/Warsaw'
|
||||||
|
|
||||||
ENABLE_CORS = os.environ.get("ENABLE_CORS") or False
|
ENABLE_CORS = os.environ.get('ENABLE_CORS') or False
|
||||||
|
|
||||||
ALLOWED_EXTENSIONS = {"csv"}
|
ALLOWED_EXTENSIONS = {'csv'}
|
||||||
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10 MB
|
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10 MB
|
||||||
|
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
SQLALCHEMY_DATABASE_URI = f'sqlite:///{BASE_DIR / "db.sqlite"}'
|
SQLALCHEMY_DATABASE_URI = f'sqlite:///{BASE_DIR / "db.sqlite"}'
|
||||||
LIMIT_STUDENTS_PER_GROUP = 5
|
LIMIT_STUDENTS_PER_GROUP = 5
|
||||||
LIMIT_MEMBERS_PER_COMMITTEE = 3
|
LIMIT_PERSONS_PER_COMMITTEE = 4
|
||||||
|
|
||||||
DESCRIPTION = "System PRI"
|
DESCRIPTION = 'System PRI'
|
||||||
OPENAPI_VERSION = "3.0.2"
|
OPENAPI_VERSION = '3.0.2'
|
||||||
|
|
||||||
# Weights for project grade sheet
|
PROJECT_PRESENTATION_TIME = 30 # in minutes
|
||||||
PRESENTATION_WEIGHT_FIRST_TERM = 1.5
|
|
||||||
PRESENTATION_WEIGHT_SECOND_TERM = 1.5
|
|
||||||
DOCUMENTATION_WEIGHT_FIRST_TERM = 2
|
|
||||||
DOCUMENTATION_WEIGHT_SECOND_TERM = 1
|
|
||||||
GROUP_WORK_WEIGHT_FIRST_TERM = 3
|
|
||||||
GROUP_WORK_WEIGHT_SECOND_TERM = 3
|
|
||||||
PRODUCT_PROJECT_WEIGHT_FIRST_TERM = 3.5
|
|
||||||
PRODUCT_PROJECT_WEIGHT_SECOND_TERM = 4.5
|
|
||||||
|
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
class ProductionConfig(Config):
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
class CSVException(Exception):
|
class CSVException(Exception):
|
||||||
"""Main csv exception"""
|
"""Main csv exception"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidNameOrTypeHeaderException(CSVException):
|
class InvalidNameOrTypeHeaderException(CSVException):
|
||||||
"""Throw if csv file has invalid name or type of header"""
|
"""Throw if csv file has invalid name or type of header"""
|
||||||
|
pass
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
# from ..base.models import Base
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Coordinator(Base):
|
||||||
|
# __tablename__ = 'coordinators'
|
@ -1,143 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from flask import abort
|
|
||||||
from sqlalchemy import and_, or_
|
|
||||||
|
|
||||||
from ...base.mode import EnrollmentsMode
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...examination_schedule.models import ExaminationSchedule, TermOfDefence
|
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
|
||||||
from ...students.models import Group
|
|
||||||
|
|
||||||
|
|
||||||
def get_term_of_defence_by_id_and_examination_schedule_id(
|
|
||||||
examination_schedule_id: int, term_of_defence_id: int
|
|
||||||
) -> ExaminationSchedule:
|
|
||||||
td = TermOfDefence.query.filter(
|
|
||||||
TermOfDefence.id == term_of_defence_id,
|
|
||||||
TermOfDefence.examination_schedule_id == examination_schedule_id,
|
|
||||||
).first()
|
|
||||||
if td is None:
|
|
||||||
abort(404, "Not found examination schedule or term of defence!")
|
|
||||||
return td
|
|
||||||
|
|
||||||
|
|
||||||
def get_group_by_id(group_id: int) -> Group:
|
|
||||||
group = Group.query.filter(Group.id == group_id).first()
|
|
||||||
if group is None:
|
|
||||||
abort(404, "Not found group!")
|
|
||||||
return group
|
|
||||||
|
|
||||||
|
|
||||||
def check_the_group_has_assigned_to_term_of_defence(group_id: int) -> TermOfDefence:
|
|
||||||
td = TermOfDefence.query.filter(TermOfDefence.group_id == group_id).first()
|
|
||||||
if td is not None:
|
|
||||||
abort(400, "Group has already assigned to term of defence!")
|
|
||||||
return td
|
|
||||||
|
|
||||||
|
|
||||||
def set_new_group_to_term_of_defence(
|
|
||||||
examination_schedule_id: int, term_of_defence_id: int, group_id: int
|
|
||||||
) -> TermOfDefence:
|
|
||||||
td = get_term_of_defence_by_id_and_examination_schedule_id(
|
|
||||||
examination_schedule_id, term_of_defence_id
|
|
||||||
)
|
|
||||||
get_group_by_id(group_id)
|
|
||||||
check_the_group_has_assigned_to_term_of_defence(group_id)
|
|
||||||
td.group_id = group_id
|
|
||||||
return td
|
|
||||||
|
|
||||||
|
|
||||||
def get_examination_schedule_by_id(examination_schedule_id: int) -> ExaminationSchedule:
|
|
||||||
ex = ExaminationSchedule.query.filter(
|
|
||||||
ExaminationSchedule.id == examination_schedule_id
|
|
||||||
).first()
|
|
||||||
if ex is None:
|
|
||||||
abort(404, "Not found examination schedule!")
|
|
||||||
return ex
|
|
||||||
|
|
||||||
|
|
||||||
def get_and_check_the_project_supervisors_exists_in_db(
|
|
||||||
year_group_id: int, project_supervisors_ids: List[int]
|
|
||||||
) -> List[ProjectSupervisor]:
|
|
||||||
project_supervisors = (
|
|
||||||
ProjectSupervisor.query.filter(
|
|
||||||
or_(*[ProjectSupervisor.id == i for i in project_supervisors_ids])
|
|
||||||
)
|
|
||||||
.filter(ProjectSupervisor.year_group_id == year_group_id)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(project_supervisors) != len(project_supervisors_ids):
|
|
||||||
abort(404, "Not found project supervisors!")
|
|
||||||
|
|
||||||
return project_supervisors
|
|
||||||
|
|
||||||
|
|
||||||
def validate_enrollments_date(
|
|
||||||
ex_start_date: datetime, ex_end_date: datetime, duration_time: int, data: dict
|
|
||||||
) -> None:
|
|
||||||
start_date = data.get("start_date")
|
|
||||||
end_date = data.get("end_date")
|
|
||||||
|
|
||||||
if not (
|
|
||||||
ex_start_date.timestamp() <= start_date.timestamp()
|
|
||||||
and ex_end_date.timestamp() >= end_date.timestamp()
|
|
||||||
):
|
|
||||||
abort(400, "Invalid date range!")
|
|
||||||
|
|
||||||
if end_date <= start_date:
|
|
||||||
abort(400, "End date must be greater than start date!")
|
|
||||||
|
|
||||||
delta_time = end_date - start_date
|
|
||||||
delta_time_in_minutes = delta_time.total_seconds() / 60
|
|
||||||
if delta_time_in_minutes % duration_time != 0:
|
|
||||||
abort(400, "Invalid duration time!")
|
|
||||||
|
|
||||||
|
|
||||||
def check_the_term_of_defence_not_exists_in_chosen_date_range(
|
|
||||||
examination_schedule_id: int, data: dict
|
|
||||||
) -> None:
|
|
||||||
start_date = data.get("start_date")
|
|
||||||
end_date = data.get("end_date")
|
|
||||||
td = (
|
|
||||||
TermOfDefence.query.filter(
|
|
||||||
TermOfDefence.examination_schedule_id == examination_schedule_id
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
or_(
|
|
||||||
and_(
|
|
||||||
TermOfDefence.start_date >= start_date,
|
|
||||||
TermOfDefence.start_date < end_date,
|
|
||||||
TermOfDefence.end_date >= end_date,
|
|
||||||
),
|
|
||||||
and_(
|
|
||||||
TermOfDefence.start_date <= start_date,
|
|
||||||
TermOfDefence.end_date > start_date,
|
|
||||||
TermOfDefence.end_date <= end_date,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if td is not None:
|
|
||||||
abort(400, "This term of defence is taken! You choose other date!")
|
|
||||||
|
|
||||||
|
|
||||||
def set_enrollments_mode(
|
|
||||||
examination_schedule_id: int, mode: EnrollmentsMode, action_name: str
|
|
||||||
) -> dict:
|
|
||||||
examination_schedule = (
|
|
||||||
db.session.query(ExaminationSchedule)
|
|
||||||
.filter(ExaminationSchedule.id == examination_schedule_id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if examination_schedule is None:
|
|
||||||
abort(404, "Not found examination schedule!")
|
|
||||||
|
|
||||||
examination_schedule.open_enrollments = mode.value
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": f"You {action_name} enrollments for this examination schedule!"}
|
|
@ -1,19 +1,17 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from .enrollments import bp as enrollments_bp
|
|
||||||
from .examination_schedule import bp as examination_schedule_bp
|
from .examination_schedule import bp as examination_schedule_bp
|
||||||
|
from .enrollments import bp as enrollments_bp
|
||||||
from .groups import bp as groups_bp
|
from .groups import bp as groups_bp
|
||||||
from .project_supervisor import bp as project_supervisor_bp
|
from .project_supervisor import bp as project_supervisor_bp
|
||||||
from .students import bp as students_bp
|
from .students import bp as students_bp
|
||||||
from .workloads import bp as workloads_bp
|
from .workloads import bp as workloads_bp
|
||||||
from .year_group import bp as year_group_bp
|
|
||||||
|
|
||||||
bp = Blueprint("coordinator", __name__, url_prefix="/coordinator")
|
bp = Blueprint("coordinator", __name__, url_prefix="/coordinator")
|
||||||
|
|
||||||
bp.register_blueprint(students_bp)
|
bp.register_blueprint(students_bp)
|
||||||
bp.register_blueprint(project_supervisor_bp)
|
bp.register_blueprint(project_supervisor_bp)
|
||||||
bp.register_blueprint(groups_bp)
|
bp.register_blueprint(groups_bp)
|
||||||
bp.register_blueprint(year_group_bp)
|
|
||||||
bp.register_blueprint(examination_schedule_bp)
|
bp.register_blueprint(examination_schedule_bp)
|
||||||
bp.register_blueprint(enrollments_bp)
|
bp.register_blueprint(enrollments_bp)
|
||||||
bp.register_blueprint(workloads_bp)
|
bp.register_blueprint(workloads_bp)
|
||||||
|
@ -1,235 +1,69 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
from flask import abort
|
from flask import abort, current_app
|
||||||
|
|
||||||
from ...base.schemas import MessageSchema
|
from ..schemas import MessageSchema, EnrollmentCreateSchema
|
||||||
|
from ...examination_schedule.models import Enrollment, Committee, ExaminationSchedule
|
||||||
from ...dependencies import db
|
from ...dependencies import db
|
||||||
from ...examination_schedule.models import TemporaryAvailability, TermOfDefence
|
|
||||||
from ..query.enrollments import (
|
|
||||||
check_the_term_of_defence_not_exists_in_chosen_date_range,
|
|
||||||
get_and_check_the_project_supervisors_exists_in_db,
|
|
||||||
get_examination_schedule_by_id,
|
|
||||||
get_term_of_defence_by_id_and_examination_schedule_id,
|
|
||||||
set_new_group_to_term_of_defence,
|
|
||||||
validate_enrollments_date,
|
|
||||||
)
|
|
||||||
from ..schemas.enrollments import (
|
|
||||||
AssignedGroupToTermOfDefenceListSchema,
|
|
||||||
TemporaryAvailabilityListSchema,
|
|
||||||
TermOfDefenceSchema,
|
|
||||||
)
|
|
||||||
from ..schemas.groups import GroupIdSchema
|
|
||||||
from ..utils import generate_range_dates
|
|
||||||
|
|
||||||
bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments")
|
bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments")
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:examination_schedule_id>/add")
|
@bp.post('/<int:examination_schedule_id>/')
|
||||||
@bp.input(TermOfDefenceSchema)
|
@bp.input(EnrollmentCreateSchema, location='json')
|
||||||
@bp.output(MessageSchema, status_code=201)
|
|
||||||
def create_term_of_defence(examination_schedule_id: int, data: dict) -> dict:
|
|
||||||
chairman_of_committee_id = data.pop("chairman_of_committee")
|
|
||||||
project_supervisors_ids = data.pop("project_supervisors")
|
|
||||||
if chairman_of_committee_id not in project_supervisors_ids:
|
|
||||||
abort(400, "Invalid id of chairman committee!")
|
|
||||||
|
|
||||||
ex = get_examination_schedule_by_id(examination_schedule_id)
|
|
||||||
|
|
||||||
project_supervisors = get_and_check_the_project_supervisors_exists_in_db(
|
|
||||||
ex.year_group_id, project_supervisors_ids
|
|
||||||
)
|
|
||||||
validate_enrollments_date(ex.start_date, ex.end_date, ex.duration_time, data)
|
|
||||||
check_the_term_of_defence_not_exists_in_chosen_date_range(
|
|
||||||
examination_schedule_id, data
|
|
||||||
)
|
|
||||||
td = TermOfDefence(
|
|
||||||
**data,
|
|
||||||
examination_schedule_id=examination_schedule_id,
|
|
||||||
chairman_of_committee=chairman_of_committee_id
|
|
||||||
)
|
|
||||||
db.session.add(td)
|
|
||||||
db.session.commit()
|
|
||||||
for p in project_supervisors:
|
|
||||||
td.members_of_committee.append(p)
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Term of defence was created!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:examination_schedule_id>/add-term-of-defences/")
|
|
||||||
@bp.input(TermOfDefenceSchema)
|
|
||||||
@bp.output(MessageSchema, status_code=201)
|
|
||||||
def create_many_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
|
|
||||||
chairman_of_committee_id = data.pop("chairman_of_committee")
|
|
||||||
project_supervisors_ids = data.pop("project_supervisors")
|
|
||||||
if chairman_of_committee_id not in project_supervisors_ids:
|
|
||||||
abort(400, "Invalid id of chairman committee!")
|
|
||||||
|
|
||||||
ex = get_examination_schedule_by_id(examination_schedule_id)
|
|
||||||
|
|
||||||
project_supervisors = get_and_check_the_project_supervisors_exists_in_db(
|
|
||||||
ex.year_group_id, project_supervisors_ids
|
|
||||||
)
|
|
||||||
validate_enrollments_date(ex.start_date, ex.end_date, ex.duration_time, data)
|
|
||||||
check_the_term_of_defence_not_exists_in_chosen_date_range(
|
|
||||||
examination_schedule_id, data
|
|
||||||
)
|
|
||||||
|
|
||||||
# create many here
|
|
||||||
start_date = data.get("start_date")
|
|
||||||
end_date = data.get("end_date")
|
|
||||||
dates = generate_range_dates(start_date, end_date, ex.duration_time)
|
|
||||||
for start_date in dates:
|
|
||||||
end_date = start_date + datetime.timedelta(minutes=ex.duration_time)
|
|
||||||
|
|
||||||
td = TermOfDefence(
|
|
||||||
start_date=start_date,
|
|
||||||
end_date=end_date,
|
|
||||||
examination_schedule_id=examination_schedule_id,
|
|
||||||
chairman_of_committee=chairman_of_committee_id,
|
|
||||||
)
|
|
||||||
td.members_of_committee = project_supervisors
|
|
||||||
db.session.add(td)
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Term of defences was created!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:examination_schedule_id>/update/<int:term_of_defence_id>/")
|
|
||||||
@bp.input(TermOfDefenceSchema)
|
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def update_term_of_defence(
|
def create_enrollments(examination_schedule_id: int, data: dict) -> dict:
|
||||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
prt = current_app.config["PROJECT_PRESENTATION_TIME"]
|
||||||
) -> dict:
|
|
||||||
chairman_of_committee_id = data.pop("chairman_of_committee")
|
|
||||||
project_supervisors_ids = data.pop("project_supervisors")
|
|
||||||
if chairman_of_committee_id not in project_supervisors_ids:
|
|
||||||
abort(400, "Invalid id of chairman committee!")
|
|
||||||
|
|
||||||
td_query = TermOfDefence.query.filter(
|
examination_schedule = db.session.query(ExaminationSchedule).filter(
|
||||||
TermOfDefence.id == term_of_defence_id,
|
ExaminationSchedule.id == examination_schedule_id).first()
|
||||||
TermOfDefence.examination_schedule_id == examination_schedule_id,
|
if examination_schedule is None:
|
||||||
)
|
abort(404, "Examination schedule doesn't exist!")
|
||||||
td = td_query.first()
|
|
||||||
if td is None:
|
|
||||||
abort(404, "Not found term of defence!")
|
|
||||||
|
|
||||||
ex = get_examination_schedule_by_id(examination_schedule_id)
|
start_date = data['start_date']
|
||||||
|
end_date = data['end_date']
|
||||||
|
if start_date > end_date:
|
||||||
|
abort(400, "Invalid dates! End date must be greater than start date!")
|
||||||
|
if start_date.date() != end_date.date():
|
||||||
|
abort(400, "Invalid dates! Only hours can be different!")
|
||||||
|
|
||||||
project_supervisors = get_and_check_the_project_supervisors_exists_in_db(
|
enrollment = Enrollment.query.filter(Enrollment.start_date >= start_date,
|
||||||
ex.year_group_id, project_supervisors_ids
|
Enrollment.start_date < end_date).first()
|
||||||
)
|
if enrollment is not None:
|
||||||
validate_enrollments_date(ex.start_date, ex.end_date, ex.duration_time, data)
|
abort(400, "You have just created enrollments for this range date!")
|
||||||
check_the_term_of_defence_not_exists_in_chosen_date_range(
|
|
||||||
examination_schedule_id, data
|
|
||||||
)
|
|
||||||
|
|
||||||
td_query.update(data)
|
enrollment = Enrollment.query.filter(Enrollment.end_date > start_date, Enrollment.end_date <= end_date).first()
|
||||||
td.members_of_committee = []
|
if enrollment is not None:
|
||||||
td.chairman_of_committee = chairman_of_committee_id
|
abort(400, "You have just created enrollments for this range date! `1")
|
||||||
db.session.commit()
|
|
||||||
for p in project_supervisors:
|
delta = end_date - start_date
|
||||||
td.members_of_committee.append(p)
|
delta_in_minutes = delta.total_seconds() / 60
|
||||||
|
if delta_in_minutes % prt != 0:
|
||||||
|
abort(400, "Invalid format dates!")
|
||||||
|
|
||||||
|
amount = int(delta_in_minutes // prt)
|
||||||
|
enrollments = []
|
||||||
|
for i in range(amount):
|
||||||
|
sd = start_date + datetime.timedelta(minutes=i * prt)
|
||||||
|
ed = start_date + datetime.timedelta(minutes=(i + 1) * prt)
|
||||||
|
enrollment = Enrollment(start_date=sd, end_date=ed, examination_schedule_id=examination_schedule_id)
|
||||||
|
enrollments.append(enrollment)
|
||||||
|
db.session.add_all(enrollments)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"message": "Term of defence was updated!"}
|
committees = [Committee(enrollment_id=e.id) for e in enrollments]
|
||||||
|
db.session.add_all(committees)
|
||||||
|
db.session.commit()
|
||||||
|
return {"message": "Enrollments was created!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:examination_schedule_id>/delete/<int:term_of_defence_id>/")
|
@bp.delete('/<int:enrollment_id>/')
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def delete_term_of_defence(
|
def delete_enrollment(enrollment_id: int) -> dict:
|
||||||
examination_schedule_id: int, term_of_defence_id: int
|
enrollment = db.session.query(Enrollment).filter(Enrollment.id == enrollment_id).first()
|
||||||
) -> dict:
|
if enrollment is None:
|
||||||
td = get_term_of_defence_by_id_and_examination_schedule_id(
|
abort(404, "Enrollment doesn't exist!")
|
||||||
examination_schedule_id, term_of_defence_id
|
db.session.delete(enrollment)
|
||||||
)
|
|
||||||
db.session.delete(td)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Term of defence was deleted!"}
|
return {"message": "Enrollment was deleted!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:examination_schedule_id>/term-of-defences/")
|
|
||||||
@bp.output(AssignedGroupToTermOfDefenceListSchema)
|
|
||||||
def list_of_term_of_defences(examination_schedule_id: int) -> dict:
|
|
||||||
get_examination_schedule_by_id(examination_schedule_id)
|
|
||||||
|
|
||||||
td = (
|
|
||||||
TermOfDefence.query.join(TermOfDefence.members_of_committee, isouter=True)
|
|
||||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return {"term_of_defences": td}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:examination_schedule_id>/temporary-availabilities/")
|
|
||||||
@bp.output(TemporaryAvailabilityListSchema)
|
|
||||||
def list_of_temporary_availability(examination_schedule_id: int) -> dict:
|
|
||||||
get_examination_schedule_by_id(examination_schedule_id)
|
|
||||||
|
|
||||||
td = (
|
|
||||||
TemporaryAvailability.query.filter(
|
|
||||||
TemporaryAvailability.examination_schedule_id == examination_schedule_id
|
|
||||||
)
|
|
||||||
.join(TemporaryAvailability.project_supervisor)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return {"temporary_availabilities": td}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:examination_schedule_id>/assigned-group-to-term-of-defences/")
|
|
||||||
@bp.output(AssignedGroupToTermOfDefenceListSchema)
|
|
||||||
def list_of_assigned_group_to_term_of_defences(examination_schedule_id: int) -> dict:
|
|
||||||
get_examination_schedule_by_id(examination_schedule_id)
|
|
||||||
|
|
||||||
td = (
|
|
||||||
TermOfDefence.query.join(TermOfDefence.members_of_committee, isouter=True)
|
|
||||||
.join(TermOfDefence.group)
|
|
||||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
|
||||||
.filter(TermOfDefence.group_id.isnot(None))
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return {"term_of_defences": td}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post(
|
|
||||||
"/<int:examination_schedule_id>/term-of-defence/<int:term_of_defence_id>/group/"
|
|
||||||
)
|
|
||||||
@bp.input(GroupIdSchema)
|
|
||||||
@bp.output(MessageSchema, status_code=201)
|
|
||||||
def add_group_to_term_of_defence(
|
|
||||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
set_new_group_to_term_of_defence(
|
|
||||||
examination_schedule_id, term_of_defence_id, data.get("group_id")
|
|
||||||
)
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Group was added to term of defence!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.delete(
|
|
||||||
"/<int:examination_schedule_id>/term-of-defence/<int:term_of_defence_id>/group/"
|
|
||||||
)
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def delete_group_to_term_of_defence(
|
|
||||||
examination_schedule_id: int, term_of_defence_id: int
|
|
||||||
) -> dict:
|
|
||||||
td = get_term_of_defence_by_id_and_examination_schedule_id(
|
|
||||||
examination_schedule_id, term_of_defence_id
|
|
||||||
)
|
|
||||||
td.group_id = None
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Group was deleted from this term of defence!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.put(
|
|
||||||
"/<int:examination_schedule_id>/term-of-defence/<int:term_of_defence_id>/group/"
|
|
||||||
)
|
|
||||||
@bp.input(GroupIdSchema)
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def update_group_for_term_of_defence(
|
|
||||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
set_new_group_to_term_of_defence(
|
|
||||||
examination_schedule_id, term_of_defence_id, data.get("group_id")
|
|
||||||
)
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Group for term of defence was updated!"}
|
|
||||||
|
@ -1,69 +1,45 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
from flask import Response, abort, current_app, make_response
|
from flask import abort, Response, make_response
|
||||||
|
|
||||||
from ...base.mode import EnrollmentsMode
|
|
||||||
from ...base.schemas import MessageSchema
|
|
||||||
from ...base.utils import paginate_models
|
from ...base.utils import paginate_models
|
||||||
from ...dependencies import db
|
from ...dependencies import db
|
||||||
from ...examination_schedule.models import ExaminationSchedule, TermOfDefence
|
from ...examination_schedule.models import ExaminationSchedule, Enrollment
|
||||||
|
from ...students.models import Group
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ...students.models import Group, YearGroup
|
from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \
|
||||||
from ..query.enrollments import set_enrollments_mode
|
ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema
|
||||||
from ..schemas.examination_schedule import (
|
from ..utils import generate_examination_schedule_pdf_file
|
||||||
ExaminationScheduleSchema,
|
|
||||||
ExaminationSchedulesPaginationSchema,
|
|
||||||
ExaminationSchedulesQuerySchema,
|
|
||||||
)
|
|
||||||
from ..utils import generate_examination_schedule_pdf_file, get_duration_time
|
|
||||||
|
|
||||||
bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule")
|
bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:year_group_id>/")
|
@bp.get('/')
|
||||||
@bp.input(ExaminationSchedulesQuerySchema, location="query")
|
@bp.input(ExaminationSchedulesQuerySchema, location='query')
|
||||||
@bp.output(ExaminationSchedulesPaginationSchema)
|
@bp.output(ExaminationSchedulesPaginationSchema)
|
||||||
def list_examination_schedule(year_group_id: int, query: dict) -> dict:
|
def list_examination_schedule(query: dict) -> dict:
|
||||||
page = query.get("page")
|
page = query.get('page')
|
||||||
per_page = query.get("per_page")
|
per_page = query.get('per_page')
|
||||||
es_query = ExaminationSchedule.query.filter(
|
data = paginate_models(page, ExaminationSchedule.query, per_page)
|
||||||
ExaminationSchedule.year_group_id == year_group_id
|
return {'examination_schedules': data['items'], 'max_pages': data['max_pages']}
|
||||||
)
|
|
||||||
data = paginate_models(page, es_query, per_page)
|
|
||||||
return {"examination_schedules": data["items"], "max_pages": data["max_pages"]}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:year_group_id>/")
|
@bp.post('/')
|
||||||
@bp.input(ExaminationScheduleSchema)
|
@bp.input(ExaminationScheduleSchema)
|
||||||
@bp.output(MessageSchema, status_code=201)
|
@bp.output(MessageSchema)
|
||||||
def create_examination_schedule(year_group_id: int, data: dict) -> dict:
|
def create_examination_schedule(data: dict) -> dict:
|
||||||
yg = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
examination_schedule = ExaminationSchedule(**data)
|
||||||
if yg is None:
|
|
||||||
abort(404, "Year group doesn't exist!")
|
|
||||||
|
|
||||||
if data.get("start_date") > data.get("end_date"):
|
|
||||||
abort(400, "Invalid data! End date must be greater than start date!")
|
|
||||||
|
|
||||||
duration_time = get_duration_time(yg.mode)
|
|
||||||
if duration_time is None:
|
|
||||||
abort(400, "Invalid duration time!")
|
|
||||||
|
|
||||||
examination_schedule = ExaminationSchedule(
|
|
||||||
**data, year_group_id=year_group_id, duration_time=duration_time
|
|
||||||
)
|
|
||||||
db.session.add(examination_schedule)
|
db.session.add(examination_schedule)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Examination schedule was created!"}
|
return {"message": "Examination schedule was created!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:examination_schedule_id>/")
|
@bp.put('/<int:id>/')
|
||||||
@bp.input(ExaminationScheduleSchema)
|
@bp.input(ExaminationScheduleSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def update_examination_schedule(examination_schedule_id: int, data: dict) -> dict:
|
def update_title_examination_schedule(id: int, data: dict) -> dict:
|
||||||
examination_schedule_query = db.session.query(ExaminationSchedule).filter(
|
examination_schedule_query = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id)
|
||||||
ExaminationSchedule.id == examination_schedule_id
|
|
||||||
)
|
|
||||||
examination_schedule = examination_schedule_query.first()
|
examination_schedule = examination_schedule_query.first()
|
||||||
|
|
||||||
if examination_schedule is None:
|
if examination_schedule is None:
|
||||||
@ -73,14 +49,10 @@ def update_examination_schedule(examination_schedule_id: int, data: dict) -> dic
|
|||||||
return {"message": "Examination schedule was updated!"}
|
return {"message": "Examination schedule was updated!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:examination_schedule_id>/")
|
@bp.delete('/<int:id>/')
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def delete_examination_schedule(examination_schedule_id: int) -> dict:
|
def delete_examination_schedule(id: int) -> dict:
|
||||||
examination_schedule = (
|
examination_schedule = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id).first()
|
||||||
db.session.query(ExaminationSchedule)
|
|
||||||
.filter(ExaminationSchedule.id == examination_schedule_id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if examination_schedule is None:
|
if examination_schedule is None:
|
||||||
abort(404, "Examination schedule doesn't exist!")
|
abort(404, "Examination schedule doesn't exist!")
|
||||||
db.session.delete(examination_schedule)
|
db.session.delete(examination_schedule)
|
||||||
@ -88,56 +60,49 @@ def delete_examination_schedule(examination_schedule_id: int) -> dict:
|
|||||||
return {"message": "Examination schedule was deleted!"}
|
return {"message": "Examination schedule was deleted!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:examination_schedule_id>/open-enrollments/")
|
@bp.put('/<int:id>/date')
|
||||||
|
@bp.input(ExaminationScheduleUpdateSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def open_enrollments(examination_schedule_id: int) -> dict:
|
def set_date_of_examination_schedule(id: int, data: dict) -> dict:
|
||||||
return set_enrollments_mode(examination_schedule_id, EnrollmentsMode.OPEN, "open")
|
examination_schedule_query = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id)
|
||||||
|
examination_schedule = examination_schedule_query.first()
|
||||||
|
|
||||||
@bp.put("/<int:examination_schedule_id>/close-enrollments/")
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def close_enrollments(examination_schedule_id: int) -> dict:
|
|
||||||
return set_enrollments_mode(examination_schedule_id, EnrollmentsMode.CLOSE, "close")
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:examination_schedule_id>/download/")
|
|
||||||
def download_examination_schedule(examination_schedule_id: int) -> Response:
|
|
||||||
examination_schedule = (
|
|
||||||
db.session.query(ExaminationSchedule)
|
|
||||||
.filter(ExaminationSchedule.id == examination_schedule_id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if examination_schedule is None:
|
if examination_schedule is None:
|
||||||
abort(404, "Examination schedule doesn't exist!")
|
abort(404, "Examination schedule doesn't exist!")
|
||||||
|
|
||||||
distinct_dates = (
|
if data['start_date'] > data['end_date']:
|
||||||
db.session.query(db.func.Date(TermOfDefence.start_date)).distinct().all()
|
abort(400, "Invalid data! End date must be greater than start date!")
|
||||||
)
|
|
||||||
|
|
||||||
nested_term_of_defences = []
|
examination_schedule_query.update(data)
|
||||||
|
db.session.commit()
|
||||||
|
return {"message": "You set date of examination schedule!"}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post('/<int:examination_schedule_id>/download/')
|
||||||
|
def download_examination_schedule(examination_schedule_id: int) -> Response:
|
||||||
|
examination_schedule = db.session.query(ExaminationSchedule). \
|
||||||
|
filter(ExaminationSchedule.id == examination_schedule_id).first()
|
||||||
|
|
||||||
|
if examination_schedule is None:
|
||||||
|
abort(404, "Examination schedule doesn't exist!")
|
||||||
|
|
||||||
|
distinct_dates = db.session.query(db.func.Date(Enrollment.start_date)).distinct().all()
|
||||||
|
# print(distinct_dates)
|
||||||
|
nested_enrollments = []
|
||||||
for d in distinct_dates:
|
for d in distinct_dates:
|
||||||
date_tmp = datetime.datetime.strptime(d[0], "%Y-%m-%d").date()
|
date_tmp = datetime.datetime.strptime(d[0], "%Y-%m-%d").date()
|
||||||
term_of_defences = (
|
enrollment = db.session.query(Enrollment).join(ExaminationSchedule, isouter=True).\
|
||||||
db.session.query(TermOfDefence)
|
join(Group, isouter=True).join(ProjectSupervisor, isouter=True). \
|
||||||
.join(Group, isouter=True)
|
filter(ExaminationSchedule.id == examination_schedule_id).filter(
|
||||||
.join(ProjectSupervisor, isouter=True)
|
db.func.Date(Enrollment.start_date) == date_tmp).all()
|
||||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
nested_enrollments.append(enrollment)
|
||||||
.filter(TermOfDefence.group_id.isnot(None))
|
|
||||||
.filter(db.func.Date(TermOfDefence.start_date) == date_tmp)
|
# print(nested_enrollments)
|
||||||
.all()
|
pdf = generate_examination_schedule_pdf_file(examination_schedule.title, nested_enrollments)
|
||||||
)
|
|
||||||
if len(term_of_defences) > 0:
|
|
||||||
nested_term_of_defences.append(term_of_defences)
|
|
||||||
# print(nested_term_of_defences)
|
|
||||||
base_dir = current_app.config.get("BASE_DIR")
|
|
||||||
pdf = generate_examination_schedule_pdf_file(
|
|
||||||
examination_schedule.title, nested_term_of_defences, base_dir
|
|
||||||
)
|
|
||||||
title = examination_schedule.title.replace("-", "_").split()
|
title = examination_schedule.title.replace("-", "_").split()
|
||||||
filename = "_".join(title)
|
filename = "_".join(title)
|
||||||
|
|
||||||
response = make_response(pdf)
|
response = make_response(pdf)
|
||||||
response.headers["Content-Type"] = "application/pdf"
|
response.headers['Content-Type'] = 'application/pdf'
|
||||||
response.headers["Content-Disposition"] = "attachment; filename=%s.pdf" % filename
|
response.headers['Content-Disposition'] = 'attachment; filename=%s.pdf' % filename
|
||||||
return response
|
return response
|
||||||
|
@ -1,166 +1,120 @@
|
|||||||
|
from flask import abort, current_app
|
||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
from flask import abort
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
from ...base.schemas import MessageSchema
|
from ...students.models import Group, Student
|
||||||
from ...base.utils import paginate_models
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ...students.models import Group, ProjectGradeSheet, Student, YearGroup
|
from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \
|
||||||
from ..schemas.groups import (
|
GroupCreateSchema, MessageSchema, GroupQuerySchema
|
||||||
GroupCreateSchema,
|
from ...dependencies import db
|
||||||
GroupEditSchema,
|
from ...base.utils import paginate_models
|
||||||
GroupQuerySchema,
|
|
||||||
GroupSetGradeSchema,
|
|
||||||
GroupsPaginationSchema,
|
|
||||||
)
|
|
||||||
from ..schemas.students import DetailGroupSchema
|
|
||||||
from ..utils import attach_grade_to_group_models
|
|
||||||
|
|
||||||
bp = APIBlueprint("groups", __name__, url_prefix="/groups")
|
bp = APIBlueprint("groups", __name__, url_prefix="/groups")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:year_group_id>/")
|
@bp.route("/", methods=["GET"])
|
||||||
@bp.input(GroupQuerySchema, location="query")
|
@bp.input(GroupQuerySchema, location='query')
|
||||||
@bp.output(GroupsPaginationSchema)
|
@bp.output(GroupsPaginationSchema)
|
||||||
def list_groups(year_group_id: int, query: dict) -> dict:
|
def list_groups(query: dict) -> dict:
|
||||||
search_name = query.get("name")
|
search_name = query.get('name')
|
||||||
page = query.get("page")
|
page = query.get('page')
|
||||||
per_page = query.get("per_page")
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
|
groups_query = Group.search_by_name(search_name)
|
||||||
|
|
||||||
groups_query = Group.search_by_name(year_group_id, search_name)
|
|
||||||
data = paginate_models(page, groups_query, per_page)
|
data = paginate_models(page, groups_query, per_page)
|
||||||
items = data["items"]
|
|
||||||
attach_grade_to_group_models(items)
|
return {
|
||||||
return {"groups": items, "max_pages": data["max_pages"]}
|
"groups": data['items'],
|
||||||
|
"max_pages": data['max_pages']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:year_group_id>/")
|
@bp.route("/", methods=["POST"])
|
||||||
@bp.input(GroupCreateSchema)
|
@bp.input(GroupCreateSchema)
|
||||||
@bp.output(MessageSchema, status_code=201)
|
@bp.output(MessageSchema)
|
||||||
def create_group(year_group_id: int, data: dict) -> dict:
|
def create_group(data: dict) -> dict:
|
||||||
name = data["name"]
|
name = data['name']
|
||||||
students_ids = data["students"]
|
students_indexes = data['students']
|
||||||
project_supervisor_id = data["project_supervisor_id"]
|
project_supervisor_id = data['project_supervisor_id']
|
||||||
|
|
||||||
yg = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first()
|
||||||
if yg is None:
|
|
||||||
abort(404, "Not found year group!")
|
|
||||||
|
|
||||||
project_supervisor = ProjectSupervisor.query.filter_by(
|
|
||||||
id=project_supervisor_id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
|
limit_student_per_group = current_app.config.get('LIMIT_STUDENTS_PER_GROUP')
|
||||||
if project_supervisor is None:
|
if project_supervisor is None:
|
||||||
abort(404, "Not found project supervisor!")
|
abort(400, f"Project Supervisor with id {project_supervisor_id} doesnt exist")
|
||||||
|
elif limit_student_per_group is not None and limit_student_per_group < len(students_indexes):
|
||||||
|
abort(400, f"Too much students you want add to group, "
|
||||||
|
f"The group can have only {limit_student_per_group} students")
|
||||||
|
|
||||||
group = Group(
|
limit = db.session.query(ProjectSupervisor.limit_group - db.func.count(ProjectSupervisor.id)).join(Group).filter(
|
||||||
name=name,
|
ProjectSupervisor.id == project_supervisor_id).group_by(ProjectSupervisor.id).scalar()
|
||||||
project_supervisor_id=project_supervisor_id,
|
|
||||||
year_group_id=year_group_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
students_without_groups = (
|
if limit is not None and limit <= 0:
|
||||||
db.session.query(Student, Group)
|
abort(400, "Can't create new group, project supervisor achieved a limit of groups")
|
||||||
.join(Group, Student.groups)
|
|
||||||
.filter(Group.year_group_id == year_group_id)
|
|
||||||
.filter(db.or_(*[Student.id == st_id for st_id in students_ids]))
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(students_without_groups) > 0:
|
group = Group(name=name, project_supervisor_id=project_supervisor_id)
|
||||||
|
|
||||||
|
students_without_groups = db.session.query(Student).join(Group, isouter=True) \
|
||||||
|
.filter(Group.id.is_(None)).filter(Student.index.in_(students_indexes)).count()
|
||||||
|
|
||||||
|
if students_without_groups != len(students_indexes):
|
||||||
abort(400, "One or more students have already belonged to group!")
|
abort(400, "One or more students have already belonged to group!")
|
||||||
|
|
||||||
students = db.session.query(Student).filter(Student.id.in_(students_ids)).all()
|
|
||||||
if len(students) != len(students_ids):
|
|
||||||
abort(404, "Not found students!")
|
|
||||||
|
|
||||||
db.session.add(group)
|
db.session.add(group)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all()
|
||||||
|
project_supervisor.count_groups += 1
|
||||||
for student in students:
|
for student in students:
|
||||||
group.students.append(student)
|
student.group_id = group.id
|
||||||
pgs = ProjectGradeSheet(group_id=group.id)
|
|
||||||
db.session.add(pgs)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"message": "Group was created!"}
|
return {"message": "Group was created!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:group_id>/detail/")
|
@bp.route("/<int:id>/", methods=["GET"])
|
||||||
@bp.output(DetailGroupSchema)
|
@bp.output(GroupSchema)
|
||||||
def detail_group(group_id: int) -> Group:
|
def detail_group(id: int) -> Group:
|
||||||
group = Group.query.filter_by(id=group_id).first()
|
group = Group.query.filter_by(id=id).first()
|
||||||
if group is None:
|
if group is None:
|
||||||
abort(404, "Not found group!")
|
abort(400, f"Group with id {id} doesn't exist!")
|
||||||
attach_grade_to_group_models([group])
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:group_id>/")
|
@bp.route("/<int:id>/", methods=["DELETE"])
|
||||||
@bp.output(MessageSchema, status_code=202)
|
@bp.output(MessageSchema, status_code=202)
|
||||||
def delete_group(group_id: int) -> dict:
|
def delete_group(id: int) -> dict:
|
||||||
group = Group.query.filter_by(id=group_id).first()
|
group = Group.query.filter_by(id=id).first()
|
||||||
if group is None:
|
if group is None:
|
||||||
abort(404, "Not found group!")
|
abort(400, f"Group with id {id} doesn't exist!")
|
||||||
|
|
||||||
|
project_supervisor = ProjectSupervisor.query.filter_by(id=group.project_supervisor_id).first()
|
||||||
|
project_supervisor.count_groups -= 1
|
||||||
|
|
||||||
|
students = db.session.query(Student).filter_by(group_id=id).all()
|
||||||
|
for student in students:
|
||||||
|
student.group_id = None
|
||||||
|
|
||||||
group.students = []
|
|
||||||
db.session.delete(group)
|
db.session.delete(group)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Group was deleted!"}
|
return {"message": "Group was deleted!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:group_id>/")
|
@bp.route("/<int:id>", methods=["PUT"])
|
||||||
@bp.input(GroupEditSchema)
|
@bp.input(GroupEditSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def edit_group(group_id: int, data: dict) -> dict:
|
def edit_group(id: int, data: dict) -> dict:
|
||||||
if not data:
|
if not data:
|
||||||
abort(400, "You have passed empty data!")
|
abort(400, 'You have passed empty data!')
|
||||||
|
|
||||||
group_query = Group.query.filter_by(id=group_id)
|
group_query = Group.query.filter_by(id=id)
|
||||||
group = group_query.first()
|
group = group_query.first()
|
||||||
|
|
||||||
if group is None:
|
if group is None:
|
||||||
abort(404, "Not found group!")
|
abort(400, f"Group with id {id} doesn't exist!")
|
||||||
|
|
||||||
students_ids = data.get("students")
|
|
||||||
name = data.get("name")
|
|
||||||
project_supervisor_id = data.get("project_supervisor_id")
|
|
||||||
|
|
||||||
if students_ids is not None:
|
|
||||||
students = db.session.query(Student).filter(Student.id.in_(students_ids)).all()
|
|
||||||
if len(students_ids) != len(students):
|
|
||||||
abort(404, "Not found students!")
|
|
||||||
group.students = students
|
|
||||||
|
|
||||||
if name is not None:
|
|
||||||
group.name = name
|
|
||||||
|
|
||||||
if project_supervisor_id is not None:
|
|
||||||
ps = ProjectSupervisor.query.filter(
|
|
||||||
ProjectSupervisor.id == project_supervisor_id
|
|
||||||
).first()
|
|
||||||
if ps is None:
|
|
||||||
abort(404, "Not found project supervisor!")
|
|
||||||
group.project_supervisor_id = project_supervisor_id
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Group was updated!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:group_id>/set-grades/")
|
|
||||||
@bp.input(GroupSetGradeSchema)
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def set_grade_for_group(group_id: int, data: dict) -> dict:
|
|
||||||
if not data:
|
|
||||||
abort(400, "You have passed empty data!")
|
|
||||||
|
|
||||||
group_query = Group.query.filter_by(id=group_id)
|
|
||||||
group = group_query.first()
|
|
||||||
|
|
||||||
if group is None:
|
|
||||||
abort(404, "Not found group!")
|
|
||||||
|
|
||||||
group_query.update(data)
|
group_query.update(data)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
return {"message": "Group was updated!"}
|
||||||
return {"message": "Grade was updated!"}
|
|
||||||
|
@ -1,152 +1,97 @@
|
|||||||
from apiflask import APIBlueprint
|
|
||||||
from flask import abort
|
from flask import abort
|
||||||
|
from apiflask import APIBlueprint
|
||||||
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
from ...base.schemas import MessageSchema
|
|
||||||
from ...base.utils import paginate_models
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ...students.models import Group, YearGroup
|
from ...students.models import Group
|
||||||
from ..schemas.project_supervisor import (
|
from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \
|
||||||
ProjectSupervisorCreateSchema,
|
ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema
|
||||||
ProjectSupervisorEditSchema,
|
from ...dependencies import db
|
||||||
ProjectSupervisorQuerySchema,
|
from ...base.utils import paginate_models
|
||||||
ProjectSupervisorSchema,
|
|
||||||
ProjectSupervisorsPaginationSchema,
|
|
||||||
)
|
|
||||||
from ..schemas.students import MessageWithIdSchema
|
|
||||||
|
|
||||||
bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
|
bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:year_group_id>/")
|
@bp.route("/", methods=["GET"])
|
||||||
@bp.input(ProjectSupervisorQuerySchema, location="query")
|
@bp.input(ProjectSupervisorQuerySchema, location='query')
|
||||||
@bp.output(ProjectSupervisorsPaginationSchema)
|
@bp.output(ProjectSupervisorsPaginationSchema)
|
||||||
def list_project_supervisors(year_group_id: int, query: dict) -> dict:
|
def list_project_supervisors(query: dict) -> dict:
|
||||||
fullname = query.get("fullname")
|
fullname = query.get('fullname')
|
||||||
order_by_first_name = query.get("order_by_first_name")
|
order_by_first_name = query.get('order_by_first_name')
|
||||||
order_by_last_name = query.get("order_by_last_name")
|
order_by_last_name = query.get('order_by_last_name')
|
||||||
page = query.get("page")
|
mode = query.get('mode')
|
||||||
per_page = query.get("per_page")
|
page = query.get('page')
|
||||||
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
project_supervisor_query = ProjectSupervisor.search_by_fullname(
|
project_supervisor_query = ProjectSupervisor.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
||||||
year_group_id, fullname, order_by_first_name, order_by_last_name
|
fullname, mode, order_by_first_name, order_by_last_name)
|
||||||
)
|
|
||||||
|
|
||||||
data = paginate_models(page, project_supervisor_query, per_page)
|
data = paginate_models(page, project_supervisor_query, per_page)
|
||||||
# print(get_debug_queries()[0])
|
# print(get_debug_queries()[0])
|
||||||
return {"project_supervisors": data["items"], "max_pages": data["max_pages"]}
|
return {
|
||||||
|
"project_supervisors": data['items'],
|
||||||
|
"max_pages": data['max_pages']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:year_group_id>/")
|
@bp.route("/", methods=["POST"])
|
||||||
@bp.input(ProjectSupervisorCreateSchema)
|
@bp.input(ProjectSupervisorCreateSchema)
|
||||||
@bp.output(MessageWithIdSchema, status_code=201)
|
@bp.output(MessageSchema)
|
||||||
def create_project_supervisor(year_group_id: int, data: dict) -> dict:
|
def create_project_supervisor(data: dict) -> dict:
|
||||||
year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
first_name = data['first_name']
|
||||||
if year_group is None:
|
last_name = data['last_name']
|
||||||
abort(404, "Not found year group!")
|
project_supervisor = ProjectSupervisor.query.filter_by(first_name=first_name).filter_by(last_name=last_name).first()
|
||||||
|
|
||||||
email = data["email"]
|
|
||||||
project_supervisor = ProjectSupervisor.query.filter(
|
|
||||||
ProjectSupervisor.email == email,
|
|
||||||
ProjectSupervisor.year_group_id == year_group_id,
|
|
||||||
).first()
|
|
||||||
if project_supervisor is not None:
|
if project_supervisor is not None:
|
||||||
abort(400, "Project Supervisor has already exists!")
|
abort(400, "Project Supervisor has already exists!")
|
||||||
|
|
||||||
project_supervisor = ProjectSupervisor(**data, year_group_id=year_group_id)
|
project_supervisor = ProjectSupervisor(**data)
|
||||||
|
|
||||||
db.session.add(project_supervisor)
|
db.session.add(project_supervisor)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"message": "Project Supervisor was created!", "id": project_supervisor.id}
|
return {"message": "Project Supervisor was created!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:project_supervisor_id>/detail/")
|
@bp.route("/<int:id>/", methods=["GET"])
|
||||||
@bp.output(ProjectSupervisorSchema)
|
@bp.output(ProjectSupervisorSchema)
|
||||||
def detail_project_supervisor(project_supervisor_id: int) -> ProjectSupervisor:
|
def detail_project_supervisor(id: int) -> ProjectSupervisor:
|
||||||
project_supervisor = ProjectSupervisor.query.filter_by(
|
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
|
||||||
id=project_supervisor_id
|
|
||||||
).first()
|
|
||||||
if project_supervisor is None:
|
if project_supervisor is None:
|
||||||
abort(404, "Not found project supervisor!")
|
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||||
return project_supervisor
|
return project_supervisor
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:project_supervisor_id>/")
|
@bp.route("/<int:id>/", methods=["DELETE"])
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema, status_code=202)
|
||||||
def delete_project_supervisor(project_supervisor_id: int) -> dict:
|
def delete_project_supervisor(id: int) -> dict:
|
||||||
project_supervisor = ProjectSupervisor.query.filter_by(
|
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
|
||||||
id=project_supervisor_id
|
|
||||||
).first()
|
|
||||||
if project_supervisor is None:
|
if project_supervisor is None:
|
||||||
abort(404, "Not found project supervisor!")
|
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||||
|
|
||||||
count_groups = len(
|
count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group).\
|
||||||
Group.query.filter(Group.project_supervisor_id == project_supervisor.id).all()
|
filter(ProjectSupervisor.id == id).group_by(ProjectSupervisor.id)
|
||||||
)
|
|
||||||
if count_groups > 0:
|
if count_groups is not None:
|
||||||
abort(400, "Project Supervisor has at least one group!")
|
abort(400, f"Project Supervisor with id {id} has gropus!")
|
||||||
|
|
||||||
db.session.delete(project_supervisor)
|
db.session.delete(project_supervisor)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Project Supervisor was deleted!"}
|
return {"message": "Project Supervisor was deleted!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:project_supervisor_id>/")
|
@bp.route("/<int:id>", methods=["PUT"])
|
||||||
@bp.input(ProjectSupervisorEditSchema)
|
@bp.input(ProjectSupervisorEditSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def edit_project_supervisor(project_supervisor_id: int, data: dict) -> dict:
|
def edit_project_supervisor(id: int, data: dict) -> dict:
|
||||||
if not data:
|
if not data:
|
||||||
abort(400, "You have passed empty data!")
|
abort(400, 'You have passed empty data!')
|
||||||
|
|
||||||
project_supervisor_query = ProjectSupervisor.query.filter_by(
|
project_supervisor_query = ProjectSupervisor.query.filter_by(id=id)
|
||||||
id=project_supervisor_id
|
|
||||||
)
|
|
||||||
project_supervisor = project_supervisor_query.first()
|
project_supervisor = project_supervisor_query.first()
|
||||||
|
|
||||||
if project_supervisor is None:
|
if project_supervisor is None:
|
||||||
abort(404, "Not found project supervisor!")
|
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||||
|
|
||||||
project_supervisor_query.update(data)
|
project_supervisor_query.update(data)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Project Supervisor was updated!"}
|
return {"message": "Project Supervisor was updated!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/copy-project-supervisors-from-last-year-group/<int:year_group_id>/")
|
|
||||||
@bp.output(MessageSchema, status_code=201)
|
|
||||||
def copy_project_supervisors_from_last_year_group(year_group_id: int) -> dict:
|
|
||||||
year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
|
||||||
if year_group is None:
|
|
||||||
abort(404, "Not found year group!")
|
|
||||||
|
|
||||||
last_year_group = (
|
|
||||||
YearGroup.query.filter(YearGroup.mode == year_group.mode)
|
|
||||||
.filter(YearGroup.id != year_group_id)
|
|
||||||
.filter(YearGroup.created_at < year_group.created_at)
|
|
||||||
.order_by(db.desc(YearGroup.created_at))
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if last_year_group is None:
|
|
||||||
abort(400, "The latest year group doesn't exist!")
|
|
||||||
|
|
||||||
project_supervisors = ProjectSupervisor.query.filter(
|
|
||||||
ProjectSupervisor.year_group_id == last_year_group.id
|
|
||||||
).all()
|
|
||||||
current_project_supervisors_email_in_new_year_group = [
|
|
||||||
ps.email
|
|
||||||
for ps in ProjectSupervisor.query.filter(
|
|
||||||
ProjectSupervisor.year_group_id == year_group_id
|
|
||||||
).all()
|
|
||||||
]
|
|
||||||
for ps in project_supervisors:
|
|
||||||
if ps.email not in current_project_supervisors_email_in_new_year_group:
|
|
||||||
new_ps = ProjectSupervisor(
|
|
||||||
first_name=ps.first_name,
|
|
||||||
last_name=ps.last_name,
|
|
||||||
email=ps.email,
|
|
||||||
limit_group=ps.limit_group,
|
|
||||||
year_group_id=year_group_id,
|
|
||||||
)
|
|
||||||
db.session.add(new_ps)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return {"message": "Project Supervisors was added!"}
|
|
||||||
|
@ -1,85 +1,78 @@
|
|||||||
from itertools import islice
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
from itertools import islice
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from apiflask import APIBlueprint
|
|
||||||
from flask import Response, abort
|
from flask import Response, abort
|
||||||
from sqlalchemy import or_
|
from apiflask import APIBlueprint
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
from ...base.schemas import MessageSchema
|
from ...students.models import Student, Group
|
||||||
from ...base.utils import is_allowed_extensions, paginate_models
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ...students.models import Group, Student, YearGroup
|
from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, \
|
||||||
|
StudentCreateSchema, MessageSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema
|
||||||
|
from ...dependencies import db
|
||||||
|
from ..utils import parse_csv, generate_csv
|
||||||
from ..exceptions import InvalidNameOrTypeHeaderException
|
from ..exceptions import InvalidNameOrTypeHeaderException
|
||||||
from ..schemas.students import (
|
from ...base.utils import paginate_models, is_allowed_extensions
|
||||||
FileSchema,
|
|
||||||
StudentCreateSchema,
|
|
||||||
StudentEditSchema,
|
|
||||||
StudentListFileDownloaderSchema,
|
|
||||||
StudentQuerySchema,
|
|
||||||
StudentSchema,
|
|
||||||
StudentsPaginationSchema,
|
|
||||||
YearGroupInfoQuery,
|
|
||||||
)
|
|
||||||
from ..utils import generate_csv, parse_csv
|
|
||||||
|
|
||||||
bp = APIBlueprint("students", __name__, url_prefix="/students")
|
bp = APIBlueprint("students", __name__, url_prefix="/students")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:year_group_id>/")
|
@bp.route("/", methods=["GET"])
|
||||||
@bp.input(StudentQuerySchema, location="query")
|
@bp.input(StudentQuerySchema, location='query')
|
||||||
@bp.output(StudentsPaginationSchema)
|
@bp.output(StudentsPaginationSchema)
|
||||||
def list_students(year_group_id: int, query: dict) -> dict:
|
def list_students(query: dict) -> dict:
|
||||||
# add filter by year group
|
fullname = query.get('fullname')
|
||||||
fullname = query.get("fullname")
|
order_by_first_name = query.get('order_by_first_name')
|
||||||
order_by_first_name = query.get("order_by_first_name")
|
order_by_last_name = query.get('order_by_last_name')
|
||||||
order_by_last_name = query.get("order_by_last_name")
|
mode = query.get('mode')
|
||||||
page = query.get("page")
|
page = query.get('page')
|
||||||
per_page = query.get("per_page")
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
student_query = (
|
student_query = Student.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
||||||
Student.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
fullname, mode, order_by_first_name, order_by_last_name)
|
||||||
year_group_id, fullname, order_by_first_name, order_by_last_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
data = paginate_models(page, student_query, per_page)
|
data = paginate_models(page, student_query, per_page)
|
||||||
# print(get_debug_queries()[0])
|
# print(get_debug_queries()[0])
|
||||||
return {"students": data["items"], "max_pages": data["max_pages"]}
|
return {
|
||||||
|
"students": data['items'],
|
||||||
|
"max_pages": data['max_pages']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:student_id>/detail/")
|
@bp.route("/<int:index>/", methods=["GET"])
|
||||||
@bp.output(StudentSchema)
|
@bp.output(StudentSchema)
|
||||||
def detail_student(student_id: int) -> Student:
|
def detail_student(index: int) -> Student:
|
||||||
student = Student.query.filter_by(id=student_id).first()
|
student = Student.query.filter_by(index=index).first()
|
||||||
if student is None:
|
if student is None:
|
||||||
abort(404, "Not found student!")
|
abort(404, f"Student with {index} index doesn't exist!")
|
||||||
return student
|
return student
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:student_id>/")
|
@bp.route("/<int:index>/", methods=["DELETE"])
|
||||||
@bp.output(MessageSchema, status_code=202)
|
@bp.output(MessageSchema, status_code=202)
|
||||||
def delete_student(student_id: int) -> dict:
|
def delete_student(index: int) -> dict:
|
||||||
student = Student.query.filter_by(id=student_id).first()
|
student = Student.query.filter_by(index=index).first()
|
||||||
if student is None:
|
if student is None:
|
||||||
abort(404, "Not found student!")
|
abort(404, f"Student with {index} index doesn't exist!")
|
||||||
db.session.delete(student)
|
db.session.delete(student)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Student was deleted!"}
|
return {"message": "Student was deleted!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:student_id>/")
|
@bp.route("/<int:index>/", methods=["PUT"])
|
||||||
@bp.input(StudentEditSchema)
|
@bp.input(StudentEditSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def edit_student(student_id: int, data: dict) -> dict:
|
def edit_student(index: int, data: dict) -> dict:
|
||||||
if not data:
|
if not data:
|
||||||
abort(400, "You have passed empty data!")
|
abort(400, 'You have passed empty data!')
|
||||||
|
|
||||||
student_query = Student.query.filter(Student.id == student_id)
|
student_query = Student.query.filter_by(index=index)
|
||||||
student = student_query.first()
|
student = student_query.first()
|
||||||
|
|
||||||
if student is None:
|
if student is None:
|
||||||
abort(404, "Not found student!")
|
abort(404, 'Not found student!')
|
||||||
|
|
||||||
student_query.update(data)
|
student_query.update(data)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -87,97 +80,62 @@ def edit_student(student_id: int, data: dict) -> dict:
|
|||||||
return {"message": "Student was updated!"}
|
return {"message": "Student was updated!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/")
|
@bp.route("/", methods=["POST"])
|
||||||
@bp.input(StudentCreateSchema)
|
@bp.input(StudentCreateSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def create_student(data: dict) -> dict:
|
def create_student(data: dict) -> dict:
|
||||||
index = data["index"]
|
index = data['index']
|
||||||
yg_id = data["year_group_id"]
|
student = Student.query.filter_by(index=index).first()
|
||||||
del data["year_group_id"]
|
|
||||||
|
|
||||||
student = Student.query.filter(
|
|
||||||
Student.index == index, Student.year_group_id == yg_id
|
|
||||||
).first()
|
|
||||||
if student is not None:
|
if student is not None:
|
||||||
abort(400, "Student has already assigned to this year group!")
|
abort(400, "Student has already exists!")
|
||||||
|
|
||||||
year_group = YearGroup.query.filter(YearGroup.id == yg_id).first()
|
dummy_email = f'student{randint(1, 300_000)}@gmail.com'
|
||||||
if year_group is None:
|
student = Student(**data, email=dummy_email)
|
||||||
abort(404, "Not found year group!")
|
|
||||||
|
|
||||||
dummy_email = f"student{randint(1, 300_000)}@gmail.com"
|
|
||||||
student = Student(**data, email=dummy_email, year_group_id=yg_id)
|
|
||||||
db.session.add(student)
|
db.session.add(student)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"message": "Student was created!"}
|
return {"message": "Student was created!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/upload/")
|
@bp.route("/upload/", methods=["POST"])
|
||||||
@bp.input(YearGroupInfoQuery, location="query")
|
@bp.input(FileSchema, location='form_and_files')
|
||||||
@bp.input(FileSchema, location="form_and_files")
|
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def upload_students(query: dict, file: dict) -> dict:
|
def upload_students(file: dict) -> dict:
|
||||||
"""Add only Students to chosen year group if students exist in db and
|
uploaded_file = file.get('file')
|
||||||
assigned to correct year group, they will be omitted"""
|
|
||||||
year_group_id = query.get("year_group_id")
|
|
||||||
yg = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
|
||||||
if yg is None:
|
|
||||||
abort(404, "Not found year group!")
|
|
||||||
|
|
||||||
uploaded_file = file.get("file")
|
|
||||||
if uploaded_file and is_allowed_extensions(uploaded_file.filename):
|
if uploaded_file and is_allowed_extensions(uploaded_file.filename):
|
||||||
try:
|
try:
|
||||||
students = parse_csv(uploaded_file, year_group_id)
|
students = parse_csv(uploaded_file, mode=True)
|
||||||
while True:
|
while True:
|
||||||
sliced_students = islice(students, 5)
|
sliced_students = islice(students, 5)
|
||||||
list_of_students = list(sliced_students)
|
list_of_students = list(sliced_students)
|
||||||
|
|
||||||
if len(list_of_students) == 0:
|
if len(list_of_students) == 0:
|
||||||
break
|
break
|
||||||
|
db.session.add_all(list_of_students)
|
||||||
students_in_db = (
|
|
||||||
Student.query.filter(
|
|
||||||
or_(Student.index == s.index for s in list_of_students)
|
|
||||||
)
|
|
||||||
.filter(Student.year_group_id == year_group_id)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
student_index_in_db = [s.index for s in students_in_db]
|
|
||||||
students_not_exists_in_db = list(
|
|
||||||
filter(
|
|
||||||
lambda s: s.index not in student_index_in_db, list_of_students
|
|
||||||
)
|
|
||||||
)
|
|
||||||
db.session.add_all(students_not_exists_in_db)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
except InvalidNameOrTypeHeaderException:
|
except InvalidNameOrTypeHeaderException:
|
||||||
abort(400, "Invalid format of csv file!")
|
abort(400, "Invalid format of csv file!")
|
||||||
|
except IntegrityError as e:
|
||||||
|
# in the future create sql query checks index and add only these students, which didn't exist in db
|
||||||
|
abort(400, "These students have already exist!")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
abort(400, "Invalid extension of file")
|
abort(400, "Invalid extension of file")
|
||||||
|
|
||||||
return {"message": "Students was created by uploading csv file!"}
|
return {"message": "Students was created by uploading csv file!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/download/")
|
@bp.route("/download/", methods=["POST"])
|
||||||
@bp.input(StudentListFileDownloaderSchema, location="query")
|
@bp.input(StudentListFileDownloaderSchema, location='query')
|
||||||
def download_students(query: dict) -> Response:
|
def download_students(query: dict) -> Response:
|
||||||
year_group_id = query.get("year_group_id")
|
mode = query.get('mode')
|
||||||
students_and_groups = (
|
mode = mode if mode is not None else True
|
||||||
db.session.query(Student, Group)
|
students = db.session.query(Student).join(Group). \
|
||||||
.join(Group, Student.groups)
|
join(ProjectSupervisor).filter(Student.mode == mode).all()
|
||||||
.filter(Group.year_group_id == year_group_id)
|
|
||||||
.join(ProjectSupervisor)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(students_and_groups) == 0:
|
if len(students) == 0:
|
||||||
abort(404, "Not found students!")
|
abort(404, "Not found students, which are assigned to group!")
|
||||||
|
csv_file = generate_csv(students)
|
||||||
csv_file = generate_csv(students_and_groups)
|
response = Response(csv_file, mimetype='text/csv')
|
||||||
response = Response(csv_file, mimetype="text/csv")
|
response.headers.set("Content-Disposition", "attachment", filename="students_list.csv")
|
||||||
response.headers.set(
|
|
||||||
"Content-Disposition", "attachment", filename="students_list.csv"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
|
@ -1,38 +1,33 @@
|
|||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
from flask import abort
|
from flask import abort
|
||||||
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
from ...dependencies import db
|
from ...dependencies import db
|
||||||
from ...examination_schedule.models import ExaminationSchedule, TermOfDefence
|
from ...examination_schedule.models import ExaminationSchedule, Enrollment, Committee
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ..schemas.examination_schedule import WorkloadSchema
|
from ..schemas import WorkloadSchema
|
||||||
|
|
||||||
bp = APIBlueprint("workloads", __name__, url_prefix="/")
|
bp = APIBlueprint("workloads", __name__, url_prefix="/")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/examination_schedule/<int:examination_schedule_id>/workloads/")
|
@bp.get('/examination_schedule/<int:examination_schedule_id>/workloads/')
|
||||||
@bp.output(WorkloadSchema)
|
@bp.output(WorkloadSchema)
|
||||||
def workloads_statistics(examination_schedule_id: int) -> dict:
|
def workloads_statistics(examination_schedule_id: int) -> dict:
|
||||||
es = ExaminationSchedule.query.filter_by(id=examination_schedule_id).first()
|
es = ExaminationSchedule.query.filter_by(id=examination_schedule_id).first()
|
||||||
if es is None:
|
if es is None:
|
||||||
abort(404, "Not found examination schedule!")
|
abort(404, "Not found examination schedule!")
|
||||||
|
|
||||||
statistics = (
|
statistics = db.session.query(
|
||||||
db.session.query(
|
|
||||||
ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name,
|
ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name,
|
||||||
db.func.count(TermOfDefence.group_id),
|
db.func.count(Enrollment.group_id),
|
||||||
db.func.count(TermOfDefence.id),
|
db.func.count(Committee.id),
|
||||||
)
|
).join(Committee.members). \
|
||||||
.join(TermOfDefence.members_of_committee)
|
join(Enrollment, isouter=True). \
|
||||||
.group_by(ProjectSupervisor.id)
|
group_by(ProjectSupervisor.id).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
workloads = (
|
# print(statistics)
|
||||||
{
|
# print(len(statistics))
|
||||||
"full_name": s[0],
|
# print(get_debug_queries())
|
||||||
"groups_assigned_to_his_committee": s[1],
|
workloads = ({"full_name": s[0], "groups_assigned_to_his_committee": s[1], "assigned_to_committee": s[2]} for s in
|
||||||
"assigned_to_committee": s[2],
|
statistics)
|
||||||
}
|
return {'workloads': workloads}
|
||||||
for s in statistics
|
|
||||||
)
|
|
||||||
return {"workloads": workloads}
|
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
from apiflask import APIBlueprint
|
|
||||||
from flask import abort
|
|
||||||
|
|
||||||
from ...base.schemas import MessageSchema
|
|
||||||
from ...base.utils import paginate_models
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...students.models import YearGroup
|
|
||||||
from ..schemas.year_group import (
|
|
||||||
YearGroupPaginationSchema,
|
|
||||||
YearGroupQuerySchema,
|
|
||||||
YearGroupSchema,
|
|
||||||
)
|
|
||||||
|
|
||||||
bp = APIBlueprint("year_group", __name__, url_prefix="/year-group")
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/")
|
|
||||||
@bp.input(YearGroupSchema)
|
|
||||||
@bp.output(MessageSchema, status_code=201)
|
|
||||||
def create_year_group(data: dict) -> dict:
|
|
||||||
name = data["name"]
|
|
||||||
mode = data["mode"]
|
|
||||||
year_group = YearGroup.query.filter(
|
|
||||||
YearGroup.name == name, YearGroup.mode == mode
|
|
||||||
).first()
|
|
||||||
if year_group is not None:
|
|
||||||
abort(400, "Year group has already exists!")
|
|
||||||
|
|
||||||
yg = YearGroup(**data)
|
|
||||||
db.session.add(yg)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return {"message": "Year group was created!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/")
|
|
||||||
@bp.input(YearGroupQuerySchema, location="query")
|
|
||||||
@bp.output(YearGroupPaginationSchema)
|
|
||||||
def list_of_year_groups(query: dict) -> dict:
|
|
||||||
page = query.get("page")
|
|
||||||
per_page = query.get("per_page")
|
|
||||||
|
|
||||||
year_group_query = YearGroup.query.order_by(db.desc(YearGroup.created_at))
|
|
||||||
data = paginate_models(page, year_group_query, per_page)
|
|
||||||
|
|
||||||
return {"year_groups": data["items"], "max_pages": data["max_pages"]}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/<int:id>/")
|
|
||||||
@bp.input(YearGroupSchema)
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def update_year_of_group(id: int, data: dict) -> dict:
|
|
||||||
if not data:
|
|
||||||
abort(400, "You have passed empty data!")
|
|
||||||
|
|
||||||
year_group_query = YearGroup.query.filter(YearGroup.id == id)
|
|
||||||
year_group = year_group_query.first()
|
|
||||||
|
|
||||||
if year_group is None:
|
|
||||||
abort(404, "Not found year group!")
|
|
||||||
|
|
||||||
name = data["name"]
|
|
||||||
mode = data["mode"]
|
|
||||||
year_group = YearGroup.query.filter(
|
|
||||||
YearGroup.name == name, YearGroup.mode == mode, YearGroup.id != id
|
|
||||||
).first()
|
|
||||||
if year_group is not None:
|
|
||||||
abort(400, "Year group has already exists!")
|
|
||||||
|
|
||||||
year_group_query.update(data)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return {"message": "Year group was updated!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:id>/")
|
|
||||||
@bp.output(MessageSchema, status_code=202)
|
|
||||||
def delete_year_of_group(id: int) -> dict:
|
|
||||||
year_group = YearGroup.query.filter_by(id=id).first()
|
|
||||||
if year_group is None:
|
|
||||||
abort(404, "Year group doesn't exist!")
|
|
||||||
db.session.delete(year_group)
|
|
||||||
db.session.commit()
|
|
||||||
return {"message": "Year group was deleted!"}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
from .enrollments import EnrollmentCreateSchema
|
||||||
|
from .examination_schedule import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, \
|
||||||
|
ExaminationSchedulesPaginationSchema, ExaminationSchedulesQuerySchema, WorkloadSchema
|
||||||
|
from .groups import GroupQuerySchema, GroupsPaginationSchema, GroupCreateSchema, GroupEditSchema
|
||||||
|
from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisorsPaginationSchema, \
|
||||||
|
ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema
|
||||||
|
from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \
|
||||||
|
StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \
|
||||||
|
StudentQuerySchema
|
@ -1,69 +1,8 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import Schema, fields
|
||||||
|
|
||||||
|
from ..validators import validate_datetime_greater_than_now
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefenceSchema(Schema):
|
class EnrollmentCreateSchema(Schema):
|
||||||
start_date = fields.DateTime(required=True)
|
start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||||
end_date = fields.DateTime(required=True)
|
end_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||||
project_supervisors = fields.List(
|
|
||||||
fields.Integer(required=True), validate=validate.Length(3, 3)
|
|
||||||
)
|
|
||||||
chairman_of_committee = fields.Integer(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorForTermOfDefenceSchema(Schema):
|
|
||||||
id = fields.Str()
|
|
||||||
first_name = fields.Str()
|
|
||||||
last_name = fields.Str()
|
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefenceItemSchema(Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
start_date = fields.DateTime()
|
|
||||||
end_date = fields.DateTime()
|
|
||||||
members_of_committee = fields.List(
|
|
||||||
fields.Nested(ProjectSupervisorForTermOfDefenceSchema)
|
|
||||||
)
|
|
||||||
chairman_of_committee = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefenceListSchema(Schema):
|
|
||||||
term_of_defences = fields.List(fields.Nested(TermOfDefenceItemSchema))
|
|
||||||
|
|
||||||
|
|
||||||
class StudentDataItemAssignedGroupSchema(Schema):
|
|
||||||
index = fields.Integer()
|
|
||||||
first_name = fields.Str()
|
|
||||||
last_name = fields.Str()
|
|
||||||
|
|
||||||
|
|
||||||
class GroupDataItemAssignedGroupSchema(Schema):
|
|
||||||
name = fields.Str()
|
|
||||||
students = fields.List(fields.Nested(StudentDataItemAssignedGroupSchema))
|
|
||||||
|
|
||||||
|
|
||||||
class AssignedGroupToTermOfDefenceDataItemSchema(TermOfDefenceItemSchema):
|
|
||||||
group = fields.Nested(GroupDataItemAssignedGroupSchema)
|
|
||||||
|
|
||||||
|
|
||||||
class AssignedGroupToTermOfDefenceListSchema(Schema):
|
|
||||||
term_of_defences = fields.List(
|
|
||||||
fields.Nested(AssignedGroupToTermOfDefenceDataItemSchema)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorForTemporaryAvailabilitySchema(Schema):
|
|
||||||
id = fields.Str()
|
|
||||||
first_name = fields.Str()
|
|
||||||
last_name = fields.Str()
|
|
||||||
|
|
||||||
|
|
||||||
class TemporaryAvailabilityItemSchema(Schema):
|
|
||||||
start_date = fields.DateTime()
|
|
||||||
end_date = fields.DateTime()
|
|
||||||
project_supervisor = fields.Nested(ProjectSupervisorForTemporaryAvailabilitySchema)
|
|
||||||
|
|
||||||
|
|
||||||
class TemporaryAvailabilityListSchema(Schema):
|
|
||||||
temporary_availabilities = fields.List(
|
|
||||||
fields.Nested(TemporaryAvailabilityItemSchema)
|
|
||||||
)
|
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import fields, validate, Schema
|
||||||
|
|
||||||
from ..validators import validate_datetime_greater_than_now
|
from ..validators import validate_datetime_greater_than_now
|
||||||
|
|
||||||
|
|
||||||
class ExaminationScheduleSchema(Schema):
|
class ExaminationScheduleSchema(Schema):
|
||||||
title = fields.Str(validate=validate.Length(min=1, max=100), required=True)
|
title = fields.Str(validate=validate.Length(min=1, max=100), required=True)
|
||||||
start_date = fields.DateTime(
|
mode = fields.Boolean(required=True)
|
||||||
validate=validate_datetime_greater_than_now, required=True
|
|
||||||
)
|
|
||||||
end_date = fields.DateTime(
|
class ExaminationScheduleUpdateSchema(Schema):
|
||||||
validate=validate_datetime_greater_than_now, required=True
|
start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||||
)
|
end_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||||
|
|
||||||
|
|
||||||
class ExaminationScheduleListItemSchema(Schema):
|
class ExaminationScheduleListItemSchema(Schema):
|
||||||
id = fields.Integer()
|
id = fields.Integer(required=True)
|
||||||
title = fields.Str()
|
title = fields.Str(validate=validate.Length(min=1, max=100), required=True)
|
||||||
open_enrollments = fields.String()
|
mode = fields.Boolean(required=True)
|
||||||
start_date = fields.DateTime()
|
start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||||
end_date = fields.DateTime()
|
end_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||||
|
|
||||||
|
|
||||||
class ExaminationSchedulesPaginationSchema(Schema):
|
class ExaminationSchedulesPaginationSchema(Schema):
|
||||||
examination_schedules = fields.List(
|
examination_schedules = fields.List(fields.Nested(ExaminationScheduleListItemSchema))
|
||||||
fields.Nested(ExaminationScheduleListItemSchema)
|
|
||||||
)
|
|
||||||
max_pages = fields.Integer()
|
max_pages = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,35 +1,28 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import fields, validate
|
||||||
|
|
||||||
from .students import GroupSchema
|
from ...dependencies import ma
|
||||||
|
from ..validators import validate_index
|
||||||
|
from .students import GroupSchema, StudentSchema
|
||||||
|
|
||||||
|
|
||||||
class GroupQuerySchema(Schema):
|
class GroupQuerySchema(ma.Schema):
|
||||||
name = fields.Str()
|
name = fields.Str()
|
||||||
page = fields.Integer()
|
page = fields.Integer()
|
||||||
per_page = fields.Integer()
|
per_page = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class GroupsPaginationSchema(Schema):
|
class GroupsPaginationSchema(ma.Schema):
|
||||||
groups = fields.List(fields.Nested(GroupSchema))
|
groups = fields.List(fields.Nested(GroupSchema))
|
||||||
max_pages = fields.Integer()
|
max_pages = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class GroupCreateSchema(Schema):
|
class GroupCreateSchema(ma.Schema):
|
||||||
name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
project_supervisor_id = fields.Integer(required=True)
|
project_supervisor_id = fields.Integer(required=True)
|
||||||
students = fields.List(fields.Integer(required=True))
|
students = fields.List(fields.Integer(validate=validate_index, required=True))
|
||||||
|
|
||||||
|
|
||||||
class GroupEditSchema(Schema):
|
class GroupEditSchema(ma.Schema):
|
||||||
name = fields.Str(validate=validate.Length(min=1, max=255))
|
name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||||
project_supervisor_id = fields.Integer()
|
project_supervisor_id = fields.Integer(validate=validate_index)
|
||||||
students = fields.List(fields.Integer())
|
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
|
||||||
|
|
||||||
|
|
||||||
class GroupIdSchema(Schema):
|
|
||||||
group_id = fields.Integer(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class GroupSetGradeSchema(Schema):
|
|
||||||
grade_for_first_term = fields.Float()
|
|
||||||
grade_for_second_term = fields.Float()
|
|
||||||
|
@ -1,38 +1,36 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import fields, validate
|
||||||
|
|
||||||
|
from ...dependencies import ma
|
||||||
|
from ..validators import validate_index
|
||||||
|
from .students import ProjectSupervisorSchema
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorSchema(Schema):
|
class ProjectSupervisorQuerySchema(ma.Schema):
|
||||||
id = fields.Integer()
|
|
||||||
first_name = fields.Str()
|
|
||||||
last_name = fields.Str()
|
|
||||||
email = fields.Str()
|
|
||||||
limit_group = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorQuerySchema(Schema):
|
|
||||||
fullname = fields.Str()
|
fullname = fields.Str()
|
||||||
order_by_first_name = fields.Str()
|
order_by_first_name = fields.Str()
|
||||||
order_by_last_name = fields.Str()
|
order_by_last_name = fields.Str()
|
||||||
page = fields.Integer()
|
page = fields.Integer()
|
||||||
per_page = fields.Integer()
|
per_page = fields.Integer()
|
||||||
|
mode = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorsPaginationSchema(Schema):
|
class ProjectSupervisorsPaginationSchema(ma.Schema):
|
||||||
project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema))
|
project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema))
|
||||||
max_pages = fields.Integer()
|
max_pages = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorCreateSchema(Schema):
|
class ProjectSupervisorCreateSchema(ma.Schema):
|
||||||
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
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)
|
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
email = fields.Str(
|
email = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
validate=[validate.Length(min=1, max=255), validate.Email()], required=True
|
|
||||||
)
|
|
||||||
limit_group = fields.Integer(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorEditSchema(Schema):
|
|
||||||
first_name = fields.Str(validate=validate.Length(min=1, max=255))
|
|
||||||
last_name = fields.Str(validate=validate.Length(min=1, max=255))
|
|
||||||
email = fields.Str(validate=[validate.Length(min=0, max=255), validate.Email()])
|
|
||||||
limit_group = fields.Integer()
|
limit_group = fields.Integer()
|
||||||
|
mode = fields.Integer(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorEditSchema(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)
|
||||||
|
email = fields.Str(validate=validate.Length(min=0, max=11), required=True)
|
||||||
|
limit_group = fields.Integer(validate=validate_index)
|
||||||
|
count_groups = fields.Integer(validate=validate_index)
|
||||||
|
mode = fields.Integer(required=True)
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import fields, validate
|
||||||
|
|
||||||
from ...dependencies import ma
|
from ...dependencies import ma
|
||||||
from ...students.models import Group, Student
|
from ...students.models import Student, Group
|
||||||
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ..validators import validate_index
|
from ..validators import validate_index
|
||||||
from .project_supervisor import ProjectSupervisorSchema
|
|
||||||
|
|
||||||
|
class ProjectSupervisorSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = ProjectSupervisor
|
||||||
|
include_relationships = False
|
||||||
|
|
||||||
|
|
||||||
class GroupSchema(ma.SQLAlchemyAutoSchema):
|
class GroupSchema(ma.SQLAlchemyAutoSchema):
|
||||||
@ -14,7 +20,7 @@ class GroupSchema(ma.SQLAlchemyAutoSchema):
|
|||||||
|
|
||||||
|
|
||||||
class StudentSchema(ma.SQLAlchemyAutoSchema):
|
class StudentSchema(ma.SQLAlchemyAutoSchema):
|
||||||
groups = fields.List(fields.Nested(GroupSchema))
|
group = fields.Nested(GroupSchema)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Student
|
model = Student
|
||||||
@ -26,29 +32,31 @@ class StudentsPaginationSchema(ma.Schema):
|
|||||||
|
|
||||||
|
|
||||||
class StudentListFileDownloaderSchema(ma.Schema):
|
class StudentListFileDownloaderSchema(ma.Schema):
|
||||||
year_group_id = fields.Integer(required=True)
|
mode = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class StudentCreateSchema(ma.Schema):
|
class StudentCreateSchema(ma.Schema):
|
||||||
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
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)
|
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
pesel = fields.Str(validate=validate.Length(min=0, max=11), required=True)
|
||||||
index = fields.Integer(validate=validate_index, required=True)
|
index = fields.Integer(validate=validate_index, required=True)
|
||||||
year_group_id = fields.Integer()
|
mode = fields.Boolean(required=True)
|
||||||
|
|
||||||
|
|
||||||
class StudentEditSchema(ma.Schema):
|
class StudentEditSchema(ma.Schema):
|
||||||
first_name = fields.Str(validate=validate.Length(min=1, max=255))
|
first_name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||||
last_name = fields.Str(validate=validate.Length(min=1, max=255))
|
last_name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||||
|
pesel = fields.Str(validate=validate.Length(min=0, max=11))
|
||||||
index = fields.Integer(validate=validate_index)
|
index = fields.Integer(validate=validate_index)
|
||||||
|
mode = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
class MessageWithIdSchema(ma.Schema):
|
class MessageSchema(ma.Schema):
|
||||||
message = fields.Str(required=True)
|
message = fields.Str(required=True)
|
||||||
id = fields.Str(required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class FileSchema(ma.Schema):
|
class FileSchema(ma.Schema):
|
||||||
file = fields.Raw(metadata={"type": "file"}, required=True)
|
file = fields.Raw(type='file', required=True)
|
||||||
|
|
||||||
|
|
||||||
class StudentQuerySchema(ma.Schema):
|
class StudentQuerySchema(ma.Schema):
|
||||||
@ -57,17 +65,4 @@ class StudentQuerySchema(ma.Schema):
|
|||||||
order_by_last_name = fields.Str()
|
order_by_last_name = fields.Str()
|
||||||
page = fields.Integer()
|
page = fields.Integer()
|
||||||
per_page = fields.Integer()
|
per_page = fields.Integer()
|
||||||
|
mode = fields.Boolean()
|
||||||
|
|
||||||
class YearGroupInfoQuery(Schema):
|
|
||||||
year_group_id = fields.Integer(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class DetailGroupSchema(ma.SQLAlchemyAutoSchema):
|
|
||||||
project_supervisor = fields.Nested(ProjectSupervisorSchema)
|
|
||||||
students = fields.List(
|
|
||||||
fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255)
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Group
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
from marshmallow import Schema, ValidationError, fields, validate
|
|
||||||
|
|
||||||
from ...base.mode import ModeGroups
|
|
||||||
|
|
||||||
|
|
||||||
def validate_mode(value: str) -> str:
|
|
||||||
if value not in [m.value for m in ModeGroups]:
|
|
||||||
raise ValidationError("Invalid mode!")
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class YearGroupSchema(Schema):
|
|
||||||
name = fields.Str(validate=validate.Regexp(r"^\d{4}\/\d{4}$"), required=True)
|
|
||||||
mode = fields.Str(validate=validate_mode, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class YearGroupItemSchema(Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
name = fields.Str()
|
|
||||||
mode = fields.Str()
|
|
||||||
|
|
||||||
|
|
||||||
class YearGroupPaginationSchema(Schema):
|
|
||||||
year_groups = fields.List(fields.Nested(YearGroupItemSchema))
|
|
||||||
max_pages = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
class YearGroupQuerySchema(Schema):
|
|
||||||
page = fields.Integer()
|
|
||||||
per_page = fields.Integer()
|
|
@ -1,100 +1,62 @@
|
|||||||
import copy
|
import datetime
|
||||||
import json
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from itertools import chain
|
||||||
from typing import Any, Generator, List, TextIO, Tuple, Union
|
from typing import Generator, Any, List
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from flask import current_app
|
|
||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
from reportlab.lib.enums import TA_CENTER
|
from reportlab.lib.enums import TA_CENTER
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
from reportlab.lib.units import inch, mm
|
from reportlab.lib.units import mm, inch
|
||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Table
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
|
||||||
from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Table
|
|
||||||
from werkzeug.datastructures import FileStorage
|
|
||||||
|
|
||||||
from ..base.mode import ModeGroups
|
|
||||||
from ..examination_schedule.models import TermOfDefence
|
|
||||||
from ..students.models import Group, ProjectGradeSheet, Student
|
|
||||||
from .exceptions import InvalidNameOrTypeHeaderException
|
from .exceptions import InvalidNameOrTypeHeaderException
|
||||||
|
from ..students.models import Student
|
||||||
|
from ..examination_schedule.models import Enrollment
|
||||||
|
|
||||||
|
|
||||||
def check_columns(df: pd.DataFrame) -> bool:
|
def check_columns(df: pd.DataFrame) -> bool:
|
||||||
headers = set(df.keys().values)
|
headers = set(df.keys().values)
|
||||||
column_names = ["NAZWISKO", "IMIE", "INDEKS", "EMAIL"]
|
columns = ['NAZWISKO', 'IMIE', 'INDEKS', 'PESEL', 'EMAIL']
|
||||||
column_types = ["object", "object", "int", "object"]
|
|
||||||
return all((column_name in headers for column_name in column_names)) and all(
|
if len(headers - set(columns)) != 0:
|
||||||
(
|
return False
|
||||||
str(df.dtypes[column_name]).startswith(column_type)
|
|
||||||
for column_name, column_type in zip(column_names, column_types)
|
flag = True
|
||||||
)
|
col_types = ['object', 'object', 'int', 'float64', 'object']
|
||||||
)
|
|
||||||
|
for name, col_type in zip(columns, col_types):
|
||||||
|
if not str(df.dtypes[name]).startswith(col_type):
|
||||||
|
flag = False
|
||||||
|
break
|
||||||
|
|
||||||
|
return flag
|
||||||
|
|
||||||
|
|
||||||
def parse_csv(
|
def parse_csv(file, mode) -> Generator[Student, Any, None]:
|
||||||
file: Union[FileStorage, TextIO], year_group_id: int
|
|
||||||
) -> Generator[Student, Any, None]:
|
|
||||||
df = pd.read_csv(file)
|
df = pd.read_csv(file)
|
||||||
|
|
||||||
if not check_columns(df):
|
if not check_columns(df):
|
||||||
raise InvalidNameOrTypeHeaderException
|
raise InvalidNameOrTypeHeaderException
|
||||||
students = (
|
|
||||||
Student(
|
students = (Student(last_name=dict(item.items())['NAZWISKO'],
|
||||||
last_name=dict(item.items())["NAZWISKO"],
|
first_name=dict(item.items())['IMIE'],
|
||||||
first_name=dict(item.items())["IMIE"],
|
index=dict(item.items())['INDEKS'],
|
||||||
index=dict(item.items())["INDEKS"],
|
pesel=str(int(dict(item.items())['PESEL'])) if not pd.isna(
|
||||||
email=dict(item.items())["EMAIL"],
|
dict(item.items())['PESEL']) else None,
|
||||||
year_group_id=year_group_id,
|
email=dict(item.items())['EMAIL'],
|
||||||
)
|
mode=mode)
|
||||||
for _, item in df.iterrows()
|
for _, item in df.iterrows())
|
||||||
)
|
|
||||||
|
|
||||||
return students
|
return students
|
||||||
|
|
||||||
|
|
||||||
def map_project_supervisors(groups: List[Group]) -> dict:
|
def generate_csv(students: List[Student]) -> str:
|
||||||
i = 1
|
headers = ['PESEL', 'INDEKS', 'IMIE', 'NAZWISKO', 'EMAIL', 'CDYD_KOD', 'PRZ_KOD', 'TZAJ_KOD', 'GR_NR', 'PRG_KOD']
|
||||||
mapped_project_supervisors = {}
|
data = [(student.pesel, student.index, student.first_name, student.last_name, student.email,
|
||||||
for group in groups:
|
student.group.cdyd_kod, student.group.prz_kod, student.group.tzaj_kod, student.group.project_supervisor_id,
|
||||||
if group.project_supervisor_id not in mapped_project_supervisors.keys():
|
None) for student in students]
|
||||||
mapped_project_supervisors[group.project_supervisor_id] = i
|
|
||||||
i += 1
|
|
||||||
return mapped_project_supervisors
|
|
||||||
|
|
||||||
|
|
||||||
def generate_csv(students_and_groups: List[Tuple[Student, Group]]) -> str:
|
|
||||||
headers = [
|
|
||||||
"INDEKS",
|
|
||||||
"IMIE",
|
|
||||||
"NAZWISKO",
|
|
||||||
"EMAIL",
|
|
||||||
"CDYD_KOD",
|
|
||||||
"PRZ_KOD",
|
|
||||||
"TZAJ_KOD",
|
|
||||||
"GR_NR",
|
|
||||||
"PRG_KOD",
|
|
||||||
]
|
|
||||||
mapped_project_supervisors_id = map_project_supervisors(
|
|
||||||
[group for _, group in students_and_groups]
|
|
||||||
)
|
|
||||||
data = [
|
|
||||||
(
|
|
||||||
student.index,
|
|
||||||
student.first_name,
|
|
||||||
student.last_name,
|
|
||||||
student.email,
|
|
||||||
group.cdyd_kod,
|
|
||||||
group.prz_kod,
|
|
||||||
group.tzaj_kod,
|
|
||||||
mapped_project_supervisors_id[group.project_supervisor_id],
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
for student, group in students_and_groups
|
|
||||||
]
|
|
||||||
dataframe = defaultdict(list)
|
dataframe = defaultdict(list)
|
||||||
for row in data:
|
for row in data:
|
||||||
for idx, item in enumerate(row):
|
for idx, item in enumerate(row):
|
||||||
@ -104,32 +66,9 @@ def generate_csv(students_and_groups: List[Tuple[Student, Group]]) -> str:
|
|||||||
return df.to_csv(index=False)
|
return df.to_csv(index=False)
|
||||||
|
|
||||||
|
|
||||||
def generate_range_dates(
|
def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[List[Enrollment]]) -> bytes:
|
||||||
start_date: datetime, end_date: datetime, step_in_minutes: int
|
|
||||||
) -> Generator[Union[datetime, timedelta], Any, None]:
|
|
||||||
current_date = copy.copy(start_date)
|
|
||||||
while True:
|
|
||||||
next_date = current_date + timedelta(minutes=step_in_minutes)
|
|
||||||
|
|
||||||
if next_date > end_date:
|
|
||||||
break
|
|
||||||
yield current_date
|
|
||||||
current_date = copy.copy(next_date)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_examination_schedule_pdf_file(
|
|
||||||
title: str, nested_term_of_defences: List[List[TermOfDefence]], base_dir: Path
|
|
||||||
) -> bytes:
|
|
||||||
pagesize = (297 * mm, 210 * mm)
|
pagesize = (297 * mm, 210 * mm)
|
||||||
headers = [
|
headers = ["lp.", "Godzina", "Nazwa projektu", "Opiekun", "Zespol", "Komisja"]
|
||||||
"lp.",
|
|
||||||
"Godzina",
|
|
||||||
"Nazwa projektu",
|
|
||||||
"Opiekun",
|
|
||||||
"Zespol",
|
|
||||||
"Komisja",
|
|
||||||
"Uwagi",
|
|
||||||
]
|
|
||||||
pdf_buffer = BytesIO()
|
pdf_buffer = BytesIO()
|
||||||
my_doc = SimpleDocTemplate(
|
my_doc = SimpleDocTemplate(
|
||||||
pdf_buffer,
|
pdf_buffer,
|
||||||
@ -138,74 +77,64 @@ def generate_examination_schedule_pdf_file(
|
|||||||
leftMargin=1 * inch,
|
leftMargin=1 * inch,
|
||||||
rightMargin=1 * inch,
|
rightMargin=1 * inch,
|
||||||
bottomMargin=1 * inch,
|
bottomMargin=1 * inch,
|
||||||
title=title,
|
title=title
|
||||||
)
|
)
|
||||||
|
|
||||||
pdfmetrics.registerFont(TTFont("Lato", base_dir / "fonts" / "Lato.ttf"))
|
|
||||||
style = getSampleStyleSheet()
|
style = getSampleStyleSheet()
|
||||||
bodyText = style["BodyText"]
|
bodyText = style['BodyText']
|
||||||
bodyText.fontName = "Lato"
|
bodyText.fontName = 'Helvetica'
|
||||||
normal = style["Heading1"]
|
normal = style["Heading1"]
|
||||||
normal.alignment = TA_CENTER
|
normal.alignment = TA_CENTER
|
||||||
flowables = []
|
flowables = []
|
||||||
|
|
||||||
# print(nested_enrollments)
|
# print(nested_enrollments)
|
||||||
for term_of_defences in nested_term_of_defences:
|
for enrollments in nested_enrollments:
|
||||||
if len(term_of_defences) == 0:
|
if len(enrollments) == 0:
|
||||||
continue
|
continue
|
||||||
date = datetime.strftime(term_of_defences[0].start_date.date(), "%d.%m.%Y")
|
date = datetime.datetime.strftime(enrollments[0].start_date, '%d/%m/%Y')
|
||||||
paragraph_1 = Paragraph(f"{title} ~ {date}", normal)
|
paragraph_1 = Paragraph(f"{title} ~ {date}", normal)
|
||||||
flowables.append(paragraph_1)
|
flowables.append(paragraph_1)
|
||||||
data = [headers]
|
data = [headers]
|
||||||
|
|
||||||
for idx, td in enumerate(term_of_defences, start=1):
|
for idx, e in enumerate(enrollments, start=1):
|
||||||
new_date = td.start_date + timedelta(hours=2)
|
new_date = e.start_date + datetime.timedelta(hours=2)
|
||||||
group_name = td.group.name if td.group is not None else ""
|
group_name = e.group.name if e.group is not None else ""
|
||||||
if group_name != "":
|
if group_name != '':
|
||||||
ps = td.group.project_supervisor
|
ps = e.group.project_supervisor
|
||||||
project_supervisor_fullname = f"{ps.first_name[0]}. {ps.last_name}"
|
project_supervisor_fullname = f"{ps.first_name[0]}. {ps.last_name}"
|
||||||
students = td.group.students
|
students = e.group.students
|
||||||
|
# print(students)
|
||||||
team = ", ".join([f"{s.first_name} {s.last_name}" for s in students])
|
team = ", ".join([f"{s.first_name} {s.last_name}" for s in students])
|
||||||
else:
|
else:
|
||||||
project_supervisor_fullname = ""
|
project_supervisor_fullname = ""
|
||||||
team = ""
|
team = ""
|
||||||
|
|
||||||
members = td.members_of_committee
|
members = e.committee.members
|
||||||
|
# print(members)
|
||||||
if len(members) == 0:
|
if len(members) == 0:
|
||||||
committee = ""
|
committee = ''
|
||||||
else:
|
else:
|
||||||
members_iter = (f"{m.first_name[0]}. {m.last_name}" for m in members)
|
members_iter = (f"{m.first_name[0]} {m.last_name}" for m in members)
|
||||||
|
if project_supervisor_fullname != '':
|
||||||
|
members_iter = chain(members_iter, [project_supervisor_fullname])
|
||||||
|
|
||||||
committee = ", ".join(members_iter)
|
committee = ", ".join(members_iter)
|
||||||
|
|
||||||
data.append(
|
data.append([str(idx), new_date.strftime("%H:%M"),
|
||||||
[
|
|
||||||
str(idx),
|
|
||||||
new_date.strftime("%H:%M"),
|
|
||||||
Paragraph(group_name, bodyText),
|
Paragraph(group_name, bodyText),
|
||||||
Paragraph(project_supervisor_fullname, bodyText),
|
Paragraph(project_supervisor_fullname, bodyText),
|
||||||
Paragraph(team, bodyText),
|
Paragraph(team, bodyText),
|
||||||
Paragraph(committee, bodyText),
|
Paragraph(committee, bodyText),
|
||||||
]
|
])
|
||||||
)
|
|
||||||
# print(data)
|
# print(data)
|
||||||
|
|
||||||
table = Table(
|
table = Table(data=data,
|
||||||
data=data,
|
|
||||||
style=[
|
style=[
|
||||||
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
|
||||||
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#A6F1A6")),
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#A6F1A6")),
|
||||||
("BACKGROUND", (0, 0), (1, -1), colors.HexColor("#A6F1A6")),
|
('BACKGROUND', (0, 0), (1, -1), colors.HexColor("#A6F1A6"))
|
||||||
],
|
|
||||||
colWidths=[
|
|
||||||
0.25 * inch,
|
|
||||||
0.7 * inch,
|
|
||||||
1.6 * inch,
|
|
||||||
1.5 * inch,
|
|
||||||
2.5 * inch,
|
|
||||||
2.2 * inch,
|
|
||||||
2 * inch,
|
|
||||||
],
|
],
|
||||||
|
colWidths=[0.25 * inch, 0.7 * inch, 1.6 * inch, 1.5 * inch, 4 * inch, 3 * inch]
|
||||||
)
|
)
|
||||||
flowables.append(table)
|
flowables.append(table)
|
||||||
flowables.append(PageBreak())
|
flowables.append(PageBreak())
|
||||||
@ -214,121 +143,3 @@ def generate_examination_schedule_pdf_file(
|
|||||||
pdf_value = pdf_buffer.getvalue()
|
pdf_value = pdf_buffer.getvalue()
|
||||||
pdf_buffer.close()
|
pdf_buffer.close()
|
||||||
return pdf_value
|
return pdf_value
|
||||||
|
|
||||||
|
|
||||||
def get_duration_time(mode: str) -> int:
|
|
||||||
duration_time = None
|
|
||||||
if mode == ModeGroups.NON_STATIONARY.value:
|
|
||||||
duration_time = 20
|
|
||||||
elif mode in [
|
|
||||||
ModeGroups.STATIONARY.value,
|
|
||||||
ModeGroups.ENGLISH_SPEAKING_STATIONARY.value,
|
|
||||||
]:
|
|
||||||
duration_time = 30
|
|
||||||
return duration_time
|
|
||||||
|
|
||||||
|
|
||||||
def load_weight_for_project_grade_sheet() -> Union[dict, None]:
|
|
||||||
base_dir = current_app.config.get("BASE_DIR")
|
|
||||||
config_dir = base_dir / "config"
|
|
||||||
|
|
||||||
with open(config_dir / "weights_project_grade_sheet.json") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def get_criterion_by_weight_key(weight_key: str) -> str:
|
|
||||||
if weight_key.startswith("presentation"):
|
|
||||||
return "presentation"
|
|
||||||
if weight_key.startswith("documentation"):
|
|
||||||
return "documentation"
|
|
||||||
if weight_key.startswith("group_work"):
|
|
||||||
return "group_work"
|
|
||||||
return "product_project"
|
|
||||||
|
|
||||||
|
|
||||||
def grade_in_percentage(term_key: str, term_points: dict) -> str:
|
|
||||||
try:
|
|
||||||
criterions = {
|
|
||||||
"presentation": current_app.config.get(f"PRESENTATION_WEIGHT_{term_key}"),
|
|
||||||
"group_work": current_app.config.get(f"GROUP_WORK_WEIGHT_{term_key}"),
|
|
||||||
"documentation": current_app.config.get(f"DOCUMENTATION_WEIGHT_{term_key}"),
|
|
||||||
"product_project": current_app.config.get(
|
|
||||||
f"PRODUCT_PROJECT_WEIGHT_{term_key}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
result = 0
|
|
||||||
for criterion_key, criterion_weight in criterions.items():
|
|
||||||
result += (
|
|
||||||
term_points[criterion_key]["gained_points"]
|
|
||||||
/ term_points[criterion_key]["all_points"]
|
|
||||||
* criterion_weight
|
|
||||||
)
|
|
||||||
result /= sum(criterions.values())
|
|
||||||
except ZeroDivisionError:
|
|
||||||
result = 0
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_points_for_both_terms(
|
|
||||||
weights: dict, project_grade_sheet: ProjectGradeSheet
|
|
||||||
) -> Tuple[float, float]:
|
|
||||||
if project_grade_sheet is None:
|
|
||||||
return 0.0, 0.0
|
|
||||||
first_term_points = {
|
|
||||||
"presentation": {"gained_points": 0, "all_points": 0},
|
|
||||||
"documentation": {"gained_points": 0, "all_points": 0},
|
|
||||||
"group_work": {"gained_points": 0, "all_points": 0},
|
|
||||||
"product_project": {"gained_points": 0, "all_points": 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
second_term_points = copy.deepcopy(first_term_points)
|
|
||||||
|
|
||||||
for weight_key, weight_value in weights.items():
|
|
||||||
points = first_term_points if weight_key.endswith("1") else second_term_points
|
|
||||||
criterion = get_criterion_by_weight_key(weight_key)
|
|
||||||
try:
|
|
||||||
attribute_value = getattr(project_grade_sheet, weight_key)
|
|
||||||
except AttributeError:
|
|
||||||
attribute_value = 0
|
|
||||||
points[criterion]["gained_points"] += attribute_value / 4 * weight_value
|
|
||||||
points[criterion]["all_points"] += weight_value
|
|
||||||
|
|
||||||
points_1 = round(grade_in_percentage("FIRST_TERM", first_term_points) * 100, 1)
|
|
||||||
points_2 = round(grade_in_percentage("SECOND_TERM", second_term_points) * 100, 1)
|
|
||||||
return points_1, points_2
|
|
||||||
|
|
||||||
|
|
||||||
def attach_points_for_first_and_second_term_to_group(group: Group) -> None:
|
|
||||||
weights = load_weight_for_project_grade_sheet()
|
|
||||||
pgs = group.project_grade_sheet
|
|
||||||
if len(pgs) == 0:
|
|
||||||
pgs = None
|
|
||||||
else:
|
|
||||||
pgs = pgs[0]
|
|
||||||
points = calculate_points_for_both_terms(weights, pgs)
|
|
||||||
group.points_for_first_term = points[0]
|
|
||||||
group.points_for_second_term = points[1]
|
|
||||||
|
|
||||||
|
|
||||||
def get_term_grade(point: float) -> float:
|
|
||||||
if point >= 91.0:
|
|
||||||
return 5
|
|
||||||
if point >= 81.0:
|
|
||||||
return 4.5
|
|
||||||
if point >= 71.0:
|
|
||||||
return 4
|
|
||||||
if point >= 61.0:
|
|
||||||
return 3.5
|
|
||||||
if point >= 51.0:
|
|
||||||
return 3
|
|
||||||
return 2
|
|
||||||
|
|
||||||
|
|
||||||
def attach_grade_to_group_models(groups: List[Group]) -> None:
|
|
||||||
for group in groups:
|
|
||||||
if group.grade_for_first_term == 0:
|
|
||||||
group.grade_for_first_term = get_term_grade(group.points_for_first_term)
|
|
||||||
if group.grade_for_second_term == 0:
|
|
||||||
group.grade_for_second_term = get_term_grade(group.points_for_second_term)
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_marshmallow import Marshmallow
|
||||||
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
ma = Marshmallow()
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import json
|
import json
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
from apiflask import APIFlask
|
from apiflask import APIFlask
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import RequestEntityTooLarge, HTTPException
|
||||||
|
|
||||||
|
|
||||||
|
def request_entity_too_large(error: RequestEntityTooLarge) -> Tuple[dict, int]:
|
||||||
|
return {'error': 'File too large!'}, 413
|
||||||
|
|
||||||
|
|
||||||
def register_error_handlers(app: APIFlask):
|
def register_error_handlers(app: APIFlask):
|
||||||
@app.errorhandler(HTTPException)
|
@app.errorhandler(HTTPException)
|
||||||
def handle_http_exception(e):
|
def handle_http_exception(e):
|
||||||
response = e.get_response()
|
response = e.get_response()
|
||||||
response.data = json.dumps({"error": e.description})
|
response.data = json.dumps({'error': e.description})
|
||||||
response.content_type = "application/json"
|
response.content_type = 'application/json'
|
||||||
return response
|
return response
|
||||||
|
@ -1,68 +1,38 @@
|
|||||||
from ..base.mode import EnrollmentsMode
|
|
||||||
from ..base.models import Base
|
|
||||||
from ..dependencies import db
|
from ..dependencies import db
|
||||||
|
from ..base.models import Base
|
||||||
|
|
||||||
|
|
||||||
class ExaminationSchedule(Base):
|
class ExaminationSchedule(Base):
|
||||||
__tablename__ = "examination_schedules"
|
__tablename__ = 'examination_schedules'
|
||||||
|
|
||||||
title = db.Column(db.String(100), unique=True, nullable=False)
|
title = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
duration_time = db.Column(db.Integer, nullable=False) # in minutes
|
mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary
|
||||||
open_enrollments = db.Column(
|
start_date = db.Column(db.DateTime)
|
||||||
db.String(1), default=EnrollmentsMode.INIT.value, nullable=False
|
end_date = db.Column(db.DateTime)
|
||||||
)
|
|
||||||
start_date = db.Column(db.DateTime, nullable=False)
|
|
||||||
end_date = db.Column(db.DateTime, nullable=False)
|
|
||||||
year_group_id = db.Column(
|
|
||||||
db.Integer, db.ForeignKey("year_groups.id"), nullable=False
|
|
||||||
)
|
|
||||||
year_group = db.relationship("YearGroup", backref="examination_schedules")
|
|
||||||
|
|
||||||
|
|
||||||
committee = db.Table(
|
class Enrollment(Base):
|
||||||
"committees",
|
__tablename__ = 'enrollments'
|
||||||
db.Column("term_of_defence_id", db.ForeignKey("term_of_defences.id")),
|
|
||||||
db.Column("project_supervisor_id", db.ForeignKey("project_supervisors.id")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefence(Base):
|
|
||||||
__tablename__ = "term_of_defences"
|
|
||||||
|
|
||||||
start_date = db.Column(db.DateTime, nullable=False)
|
start_date = db.Column(db.DateTime, nullable=False)
|
||||||
end_date = db.Column(db.DateTime, nullable=False)
|
end_date = db.Column(db.DateTime, nullable=False)
|
||||||
examination_schedule_id = db.Column(
|
examination_schedule_id = db.Column(db.Integer, db.ForeignKey('examination_schedules.id'))
|
||||||
db.Integer, db.ForeignKey("examination_schedules.id")
|
examination_schedule = db.relationship('ExaminationSchedule', backref='enrollments')
|
||||||
)
|
committee = db.relationship("Committee", uselist=False, backref=db.backref('enrollment', passive_deletes=True))
|
||||||
examination_schedule = db.relationship(
|
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
|
||||||
"ExaminationSchedule", backref="term_of_defences"
|
group = db.relationship("Group", uselist=False, backref='enrollment')
|
||||||
)
|
|
||||||
group_id = db.Column(db.Integer, db.ForeignKey("groups.id"))
|
|
||||||
group = db.relationship(
|
|
||||||
"Group", uselist=False, backref="term_of_defence", lazy="joined"
|
|
||||||
)
|
|
||||||
members_of_committee = db.relationship(
|
|
||||||
"ProjectSupervisor", secondary=committee, lazy="joined"
|
|
||||||
)
|
|
||||||
chairman_of_committee = db.Column(
|
|
||||||
db.Integer, db.ForeignKey("project_supervisors.id")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TemporaryAvailability(Base):
|
class Committee(Base):
|
||||||
__tablename__ = "temporary_availabilities"
|
__tablename__ = 'committees'
|
||||||
|
|
||||||
start_date = db.Column(db.DateTime, nullable=False)
|
enrollment_id = db.Column(db.Integer, db.ForeignKey('enrollments.id', ondelete='CASCADE'))
|
||||||
end_date = db.Column(db.DateTime, nullable=False)
|
members = db.relationship('ProjectSupervisor', secondary='committees_projects_supervisors', backref='committees')
|
||||||
examination_schedule_id = db.Column(
|
|
||||||
db.Integer, db.ForeignKey("examination_schedules.id"), nullable=False
|
|
||||||
)
|
class CommitteeProjectSupervisor(Base):
|
||||||
examination_schedule = db.relationship(
|
__tablename__ = 'committees_projects_supervisors'
|
||||||
"ExaminationSchedule", backref="temporary_availabilities"
|
|
||||||
)
|
chairman = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
project_supervisor_id = db.Column(
|
committee_id = db.Column(db.Integer, db.ForeignKey('committees.id'))
|
||||||
db.Integer, db.ForeignKey("project_supervisors.id"), nullable=False
|
member_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'))
|
||||||
)
|
|
||||||
project_supervisor = db.relationship(
|
|
||||||
"ProjectSupervisor", backref="temporary_availabilities"
|
|
||||||
)
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
|
from .enrollments import bp as enrollments_bp
|
||||||
|
from .examination_schedule import bp as examination_schedule_bp
|
||||||
|
|
||||||
bp = Blueprint("examination_schedule", __name__, url_prefix="/examination_schedule")
|
bp = Blueprint("examination_schedule", __name__, url_prefix="/examination_schedule")
|
||||||
|
|
||||||
|
bp.register_blueprint(enrollments_bp)
|
||||||
|
bp.register_blueprint(examination_schedule_bp)
|
||||||
|
61
backend/app/examination_schedule/routes/enrollments.py
Normal file
61
backend/app/examination_schedule/routes/enrollments.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from apiflask import APIBlueprint
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from ..schemas import EnrollmentPaginationSchema, EnrollmentQuerySchema
|
||||||
|
from ..utils import check_examination_schedule_is_exist, get_list_of_enrollments_response
|
||||||
|
|
||||||
|
bp = APIBlueprint("enrollments", __name__, url_prefix="/enrollments")
|
||||||
|
|
||||||
|
|
||||||
|
# list of the exam registration for students
|
||||||
|
@bp.get('/<int:examination_schedule_id>/student-view')
|
||||||
|
@bp.input(EnrollmentQuerySchema, location='query')
|
||||||
|
@bp.output(EnrollmentPaginationSchema)
|
||||||
|
def list_enrollments_for_students(examination_schedule_id: int, query: dict) -> dict:
|
||||||
|
page = query.get('page')
|
||||||
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
|
examination_schedule = check_examination_schedule_is_exist(examination_schedule_id)
|
||||||
|
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
if examination_schedule.start_date is None or examination_schedule.end_date is None:
|
||||||
|
abort(403, "Forbidden! The examination schedule is not available yet")
|
||||||
|
|
||||||
|
if examination_schedule.start_date.timestamp() > now.timestamp():
|
||||||
|
abort(403, "Forbidden! Enrollments haven't just started!")
|
||||||
|
|
||||||
|
if examination_schedule.end_date.timestamp() < now.timestamp():
|
||||||
|
abort(400, "The exam registration has just finished!")
|
||||||
|
|
||||||
|
return get_list_of_enrollments_response(examination_schedule_id, page, per_page)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/<int:examination_schedule_id>/coordinator-view/')
|
||||||
|
@bp.input(EnrollmentQuerySchema, location='query')
|
||||||
|
@bp.output(EnrollmentPaginationSchema)
|
||||||
|
def list_enrollments_for_coordinator(examination_schedule_id: int, query: dict) -> dict:
|
||||||
|
page = query.get('page')
|
||||||
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
|
check_examination_schedule_is_exist(examination_schedule_id)
|
||||||
|
|
||||||
|
return get_list_of_enrollments_response(examination_schedule_id, page, per_page)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/<int:examination_schedule_id>/project-supervisor-view/')
|
||||||
|
@bp.input(EnrollmentQuerySchema, location='query')
|
||||||
|
@bp.output(EnrollmentPaginationSchema)
|
||||||
|
def list_enrollments_for_project_supervisor(examination_schedule_id: int, query: dict) -> dict:
|
||||||
|
page = query.get('page')
|
||||||
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
|
examination_schedule = check_examination_schedule_is_exist(examination_schedule_id)
|
||||||
|
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
if examination_schedule.start_date.timestamp() < now.timestamp():
|
||||||
|
abort(403, "Forbidden! Enrollment has just started! You cannot assign to the exam committees!")
|
||||||
|
|
||||||
|
return get_list_of_enrollments_response(examination_schedule_id, page, per_page)
|
@ -0,0 +1,32 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from apiflask import APIBlueprint
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from ...dependencies import db
|
||||||
|
from ..models import ExaminationSchedule
|
||||||
|
from ..schemas import ExaminationScheduleListSchema
|
||||||
|
|
||||||
|
bp = APIBlueprint("list_of_examination_schedule", __name__, url_prefix="/")
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/project-supervisor-view/')
|
||||||
|
@bp.output(ExaminationScheduleListSchema)
|
||||||
|
def list_examination_schedule_for_project_supervisors() -> dict:
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
examination_schedules = db.session.query(ExaminationSchedule). \
|
||||||
|
filter(ExaminationSchedule.start_date > now). \
|
||||||
|
all()
|
||||||
|
return {'examination_schedules': examination_schedules}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/students-view/')
|
||||||
|
@bp.output(ExaminationScheduleListSchema)
|
||||||
|
def list_examination_schedule_for_students() -> dict:
|
||||||
|
# in the future filter after the mode of examination schedule if we will have authorization module
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
examination_schedules = db.session.query(ExaminationSchedule).\
|
||||||
|
filter(ExaminationSchedule.start_date < now).\
|
||||||
|
filter(ExaminationSchedule.end_date > now).\
|
||||||
|
all()
|
||||||
|
return {'examination_schedules': examination_schedules}
|
@ -0,0 +1,41 @@
|
|||||||
|
from marshmallow import fields, validate, Schema
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorSchema(Schema):
|
||||||
|
first_name = fields.Str()
|
||||||
|
last_name = fields.Str()
|
||||||
|
|
||||||
|
|
||||||
|
class CommitteeSchema(Schema):
|
||||||
|
members = fields.List(fields.Nested(ProjectSupervisorSchema))
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSchema(Schema):
|
||||||
|
name = fields.Str()
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollmentSchema(Schema):
|
||||||
|
id = fields.Integer()
|
||||||
|
start_date = fields.DateTime()
|
||||||
|
end_date = fields.DateTime()
|
||||||
|
committee = fields.Nested(CommitteeSchema)
|
||||||
|
group = fields.Nested(GroupSchema)
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollmentPaginationSchema(Schema):
|
||||||
|
enrollments = fields.List(fields.Nested(EnrollmentSchema))
|
||||||
|
max_pages = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollmentQuerySchema(Schema):
|
||||||
|
page = fields.Integer()
|
||||||
|
per_page = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class ExaminationScheduleSchema(Schema):
|
||||||
|
id = fields.Integer()
|
||||||
|
title = fields.Str()
|
||||||
|
|
||||||
|
|
||||||
|
class ExaminationScheduleListSchema(Schema):
|
||||||
|
examination_schedules = fields.List(fields.Nested(ExaminationScheduleSchema))
|
27
backend/app/examination_schedule/utils.py
Normal file
27
backend/app/examination_schedule/utils.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from ..dependencies import db
|
||||||
|
from .models import Enrollment, Committee, ExaminationSchedule
|
||||||
|
from ..students.models import Group
|
||||||
|
from ..base.utils import paginate_models
|
||||||
|
|
||||||
|
|
||||||
|
def check_examination_schedule_is_exist(examination_schedule_id: int) -> ExaminationSchedule:
|
||||||
|
examination_schedule = db.session.query(ExaminationSchedule).filter(
|
||||||
|
ExaminationSchedule.id == examination_schedule_id).first()
|
||||||
|
|
||||||
|
if examination_schedule is None:
|
||||||
|
abort(404, "Examination Schedule doesn't exist!")
|
||||||
|
|
||||||
|
return examination_schedule
|
||||||
|
|
||||||
|
|
||||||
|
def get_list_of_enrollments_response(examination_schedule_id: int, page: int = None, per_page: int = None) -> dict:
|
||||||
|
enrollments_query = db.session.query(Enrollment). \
|
||||||
|
join(Group, isouter=True).join(Committee, isouter=True). \
|
||||||
|
join(ExaminationSchedule, isouter=True). \
|
||||||
|
filter(ExaminationSchedule.id == examination_schedule_id)
|
||||||
|
|
||||||
|
data = paginate_models(page, enrollments_query, per_page)
|
||||||
|
|
||||||
|
return {"enrollments": data["items"], "max_pages": data["max_pages"]}
|
44
backend/app/factory.py
Normal file
44
backend/app/factory.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from factory import alchemy, Sequence
|
||||||
|
from factory.faker import Faker
|
||||||
|
from factory.fuzzy import FuzzyInteger, FuzzyChoice
|
||||||
|
|
||||||
|
from .dependencies import db
|
||||||
|
from .students.models import Student, Group
|
||||||
|
from .project_supervisor.models import ProjectSupervisor
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = ProjectSupervisor
|
||||||
|
sqlalchemy_session = db.session
|
||||||
|
|
||||||
|
first_name = Faker('first_name')
|
||||||
|
last_name = Faker('last_name')
|
||||||
|
email = Faker('email')
|
||||||
|
limit_group = 4 # FuzzyInteger(3, 5)
|
||||||
|
count_groups = 4
|
||||||
|
mode = 0
|
||||||
|
|
||||||
|
|
||||||
|
class GroupFactory(alchemy.SQLAlchemyModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
sqlalchemy_session = db.session
|
||||||
|
|
||||||
|
name = Sequence(lambda n: f'Group-{n}')
|
||||||
|
points_for_first_term = FuzzyInteger(1, 5)
|
||||||
|
points_for_second_term = FuzzyInteger(1, 5)
|
||||||
|
# project_supervisor = RelatedFactory(ProjectSupervisorFactory, 'project_supervisor')
|
||||||
|
|
||||||
|
|
||||||
|
class StudentFactory(alchemy.SQLAlchemyModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
sqlalchemy_session = db.session
|
||||||
|
|
||||||
|
first_name = Faker('first_name')
|
||||||
|
last_name = Faker('last_name')
|
||||||
|
email = Faker('email')
|
||||||
|
index = Sequence(lambda n: 400_000 + n)
|
||||||
|
# group = RelatedFactory(GroupFactory)
|
||||||
|
mode = FuzzyChoice([True, False])
|
@ -1,50 +1,34 @@
|
|||||||
from flask_sqlalchemy import BaseQuery
|
from flask_sqlalchemy import BaseQuery
|
||||||
|
|
||||||
from ..base.models import Base, Person
|
|
||||||
from ..base.utils import order_by_column_name
|
|
||||||
from ..dependencies import db
|
from ..dependencies import db
|
||||||
from ..students.models import YearGroup
|
from ..base.models import Person, Base
|
||||||
|
from ..base.utils import order_by_column_name
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisor(Base, Person):
|
class ProjectSupervisor(Base, Person):
|
||||||
__tablename__ = "project_supervisors"
|
__tablename__ = "project_supervisors"
|
||||||
|
|
||||||
limit_group = db.Column(db.Integer, default=3, nullable=False)
|
limit_group = db.Column(db.Integer, default=3, nullable=False)
|
||||||
is_coordinator = db.Column(db.Boolean, default=False, nullable=False)
|
count_groups = db.Column(db.Integer, default=0, nullable=False)
|
||||||
year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id"))
|
mode = db.Column(db.Integer, default=0, nullable=False) # 0 - stationary, 1 - non-stationary, 2 - both
|
||||||
year_group = db.relationship("YearGroup", backref="project_supervisors")
|
|
||||||
|
|
||||||
__table__args = db.UniqueConstraint(
|
|
||||||
"email", "year_group_id", name="uc_email_year_group_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_by_fullname(
|
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None,
|
||||||
cls,
|
mode: int = None,
|
||||||
year_group_id: int = None,
|
|
||||||
fullname: str = None,
|
|
||||||
order_by_first_name: str = None,
|
order_by_first_name: str = None,
|
||||||
order_by_last_name: str = None,
|
order_by_last_name: str = None) -> BaseQuery:
|
||||||
) -> BaseQuery:
|
|
||||||
project_supervisors_query = cls.query
|
project_supervisors_query = cls.query
|
||||||
|
|
||||||
if year_group_id is not None:
|
if mode is not None:
|
||||||
project_supervisors_query = project_supervisors_query.filter(
|
project_supervisors_query = project_supervisors_query.filter(mode != 1 - mode)
|
||||||
ProjectSupervisor.year_group_id == year_group_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if fullname is not None:
|
if fullname is not None:
|
||||||
project_supervisors_query = project_supervisors_query.filter(
|
project_supervisors_query = project_supervisors_query.filter(
|
||||||
(ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name).like(
|
(ProjectSupervisor.first_name + ' ' + ProjectSupervisor.last_name).like(f'{fullname}%'))
|
||||||
f"{fullname}%"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
project_supervisors_query = order_by_column_name(
|
project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.first_name,
|
||||||
project_supervisors_query, ProjectSupervisor.first_name, order_by_first_name
|
order_by_first_name)
|
||||||
)
|
project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.last_name,
|
||||||
project_supervisors_query = order_by_column_name(
|
order_by_last_name)
|
||||||
project_supervisors_query, ProjectSupervisor.last_name, order_by_last_name
|
|
||||||
)
|
|
||||||
|
|
||||||
return project_supervisors_query
|
return project_supervisors_query
|
||||||
|
33
backend/app/project_supervisor/query.py
Normal file
33
backend/app/project_supervisor/query.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
from ..dependencies import db
|
||||||
|
from ..examination_schedule.models import Enrollment, ExaminationSchedule, Committee
|
||||||
|
|
||||||
|
|
||||||
|
def get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id: int,
|
||||||
|
enrollment_id: int) -> Enrollment:
|
||||||
|
enrollment = db.session.query(Enrollment). \
|
||||||
|
join(ExaminationSchedule, isouter=True).join(Committee, isouter=True). \
|
||||||
|
filter(ExaminationSchedule.id == examination_schedule_id). \
|
||||||
|
filter(Enrollment.id == enrollment_id). \
|
||||||
|
first()
|
||||||
|
|
||||||
|
if enrollment is None:
|
||||||
|
abort(404, "Examination schedule doesn't exist!")
|
||||||
|
|
||||||
|
return enrollment
|
||||||
|
|
||||||
|
|
||||||
|
def check_the_project_supervisor_is_in_committee(enrollment_id: int, project_supervisor_id) -> bool:
|
||||||
|
return db.session.query(
|
||||||
|
Committee.query.join(Committee.members).filter(Committee.enrollment_id == enrollment_id).filter(
|
||||||
|
Committee.members.any(id=project_supervisor_id)).exists()).scalar()
|
||||||
|
|
||||||
|
|
||||||
|
def check_the_enrollments_has_just_started(start_date: datetime.datetime, action: str) -> None:
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
if start_date is not None and start_date.timestamp() < now.timestamp():
|
||||||
|
abort(403, f"Forbidden! Enrollment has just started! You cannot {action} from the exam committees!")
|
@ -1,35 +0,0 @@
|
|||||||
from flask import abort
|
|
||||||
|
|
||||||
from ...coordinator.utils import attach_points_for_first_and_second_term_to_group
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...students.models import Group, ProjectGradeSheet
|
|
||||||
from ..models import ProjectSupervisor
|
|
||||||
|
|
||||||
|
|
||||||
def update_project_grade_sheet(
|
|
||||||
group_id: int, query: dict, data: dict
|
|
||||||
) -> ProjectGradeSheet:
|
|
||||||
project_supervisor_id = query.get("id")
|
|
||||||
project_supervisor = ProjectSupervisor.query.filter(
|
|
||||||
ProjectSupervisor.id == project_supervisor_id
|
|
||||||
).first()
|
|
||||||
if project_supervisor is None:
|
|
||||||
abort(404, "Not found project supervisor!")
|
|
||||||
####################################
|
|
||||||
if len(data) == 0:
|
|
||||||
abort(400, "You passed empty data!")
|
|
||||||
|
|
||||||
group = Group.query.filter(
|
|
||||||
Group.project_supervisor_id == project_supervisor_id, Group.id == group_id
|
|
||||||
).first()
|
|
||||||
if group is None:
|
|
||||||
abort(400, "You cannot update project grade sheet! It's not your group!")
|
|
||||||
|
|
||||||
pgs_query = ProjectGradeSheet.query.filter(ProjectGradeSheet.group_id == group_id)
|
|
||||||
|
|
||||||
if pgs_query.first() is None:
|
|
||||||
abort(404, "Not found project grade sheet!")
|
|
||||||
|
|
||||||
pgs_query.update(data)
|
|
||||||
attach_points_for_first_and_second_term_to_group(group)
|
|
||||||
db.session.commit()
|
|
@ -1,9 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from .enrollments import bp as enrollments_bp
|
from .enrollments import bp as enrollments_bp
|
||||||
from .project_grade_sheet import bp as project_grade_sheet_bp
|
|
||||||
|
|
||||||
bp = Blueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
|
bp = Blueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
|
||||||
|
|
||||||
bp.register_blueprint(enrollments_bp)
|
bp.register_blueprint(enrollments_bp)
|
||||||
bp.register_blueprint(project_grade_sheet_bp)
|
|
||||||
|
@ -1,199 +1,64 @@
|
|||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
from flask import abort
|
from flask import abort, current_app
|
||||||
from sqlalchemy import and_, or_
|
|
||||||
|
|
||||||
from ...base.mode import EnrollmentsMode
|
from ..schemas import MessageSchema, CommitteeCreateSchema, TemporaryProjectSupervisorSchema
|
||||||
from ...base.schemas import MessageSchema
|
from ...examination_schedule.models import Committee
|
||||||
from ...dependencies import db
|
from ...dependencies import db
|
||||||
from ...examination_schedule.models import (
|
|
||||||
ExaminationSchedule,
|
|
||||||
TemporaryAvailability,
|
|
||||||
TermOfDefence,
|
|
||||||
)
|
|
||||||
from ..models import ProjectSupervisor
|
from ..models import ProjectSupervisor
|
||||||
from ..schemas import (
|
from ..query import get_enrollment_by_enrollment_and_examination_schedule_ids, \
|
||||||
ListOfFreeTimesSchema,
|
check_the_project_supervisor_is_in_committee, check_the_enrollments_has_just_started
|
||||||
ListOfTermOfDefenceSchema,
|
|
||||||
TemporaryProjectSupervisorSchema,
|
|
||||||
TimeAvailabilityCreateSchema,
|
|
||||||
)
|
|
||||||
|
|
||||||
bp = APIBlueprint("enrollments", __name__, url_prefix="/")
|
bp = APIBlueprint("enrollments", __name__, url_prefix="/")
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:examination_schedule_id>/enrollments/")
|
@bp.post('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||||
@bp.input(TimeAvailabilityCreateSchema)
|
@bp.input(CommitteeCreateSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def set_your_free_time_to_examination_schedule(
|
def assign_yourself_to_committee(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||||
examination_schedule_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
# this code will be removed
|
# this code will be removed
|
||||||
project_supervisor = ProjectSupervisor.query.filter(
|
project_supervisor = db.session.query(ProjectSupervisor).filter(
|
||||||
ProjectSupervisor.id == data["project_supervisor_id"]
|
ProjectSupervisor.id == data['project_supervisor_id']).first()
|
||||||
).first()
|
|
||||||
if project_supervisor is None:
|
if project_supervisor is None:
|
||||||
abort(404, "ProjectSupervisor doesn't exist!")
|
abort(404, "ProjectSupervisor doesn't exist!")
|
||||||
print(project_supervisor)
|
|
||||||
################
|
################
|
||||||
|
|
||||||
examination_schedule = ExaminationSchedule.query.filter(
|
limit_of_committtee = current_app.config['LIMIT_PERSONS_PER_COMMITTEE']
|
||||||
ExaminationSchedule.id == examination_schedule_id
|
|
||||||
).first()
|
|
||||||
if examination_schedule is None:
|
|
||||||
abort(404, "Examination schedule doesn't exist!")
|
|
||||||
if examination_schedule.year_group_id != project_supervisor.year_group_id:
|
|
||||||
abort(400, "You are not assigned to this year group!")
|
|
||||||
|
|
||||||
if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value:
|
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||||
abort(400, "Enrollments has started or closed! You have been delayed!")
|
check_the_enrollments_has_just_started(enrollment.examination_schedule.start_date, "assign")
|
||||||
|
|
||||||
sd = data.get("start_date")
|
size_of_committee = db.session.query(Committee).join(Committee.members). \
|
||||||
ed = data.get("end_date")
|
filter(Committee.enrollment_id == enrollment.id).count()
|
||||||
if sd > ed:
|
|
||||||
abort(400, "Invalid data! End date must be greater than start date!")
|
|
||||||
|
|
||||||
start_date = examination_schedule.start_date
|
if size_of_committee >= limit_of_committtee:
|
||||||
end_date = examination_schedule.end_date
|
abort(400, "The committee is full!")
|
||||||
if not (
|
|
||||||
start_date.timestamp() <= sd.timestamp()
|
|
||||||
and end_date.timestamp() >= ed.timestamp()
|
|
||||||
):
|
|
||||||
abort(400, "Invalid date range!")
|
|
||||||
|
|
||||||
ta = (
|
if check_the_project_supervisor_is_in_committee(enrollment.id, project_supervisor.id):
|
||||||
TemporaryAvailability.query.filter(
|
abort(400, "You have already in this committee!")
|
||||||
TemporaryAvailability.examination_schedule_id == examination_schedule_id
|
|
||||||
)
|
|
||||||
.filter(TemporaryAvailability.project_supervisor_id == project_supervisor.id)
|
|
||||||
.filter(
|
|
||||||
or_(
|
|
||||||
and_(
|
|
||||||
TemporaryAvailability.start_date >= sd,
|
|
||||||
TemporaryAvailability.start_date < ed,
|
|
||||||
TemporaryAvailability.end_date >= ed,
|
|
||||||
),
|
|
||||||
and_(
|
|
||||||
TemporaryAvailability.start_date <= sd,
|
|
||||||
TemporaryAvailability.end_date > sd,
|
|
||||||
TemporaryAvailability.end_date <= ed,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if ta is not None:
|
|
||||||
abort(
|
|
||||||
400,
|
|
||||||
"Invalid date ranges. You set your free time "
|
|
||||||
"in this date range! Choose another date!",
|
|
||||||
)
|
|
||||||
|
|
||||||
ta = TemporaryAvailability(**data, examination_schedule_id=examination_schedule_id)
|
enrollment.committee.members.append(project_supervisor)
|
||||||
db.session.add(ta)
|
db.session.add(enrollment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"message": "You have just assigned your free time!"}
|
return {"message": "You have just assigned yourself to committee!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.delete(
|
@bp.delete('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||||
"/<int:examination_schedule_id>/enrollments/<int:temporary_availability_id>/"
|
|
||||||
)
|
|
||||||
@bp.input(TemporaryProjectSupervisorSchema)
|
@bp.input(TemporaryProjectSupervisorSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def delete_your_free_time_from_examination_schedule(
|
def delete_yourself_from_committee(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||||
examination_schedule_id: int, temporary_availability_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
# this code will be removed
|
# this code will be removed
|
||||||
project_supervisor = (
|
project_supervisor = db.session.query(ProjectSupervisor).filter(ProjectSupervisor.id == data['id']).first()
|
||||||
db.session.query(ProjectSupervisor)
|
|
||||||
.filter(ProjectSupervisor.id == data["id"])
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if project_supervisor is None:
|
if project_supervisor is None:
|
||||||
abort(404, "ProjectSupervisor doesn't exist!")
|
abort(404, "ProjectSupervisor doesn't exist!")
|
||||||
################
|
################
|
||||||
examination_schedule = ExaminationSchedule.query.filter(
|
|
||||||
ExaminationSchedule.id == examination_schedule_id
|
|
||||||
).first()
|
|
||||||
if examination_schedule is None:
|
|
||||||
abort(404, "Examination schedule doesn't exist!")
|
|
||||||
|
|
||||||
if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value:
|
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||||
abort(400, "Enrollments has started or closed! You have been delayed!")
|
check_the_enrollments_has_just_started(enrollment.examination_schedule.start_date, "delete")
|
||||||
|
|
||||||
ta = TemporaryAvailability.query.filter(
|
if not check_the_project_supervisor_is_in_committee(enrollment.id, project_supervisor.id):
|
||||||
TemporaryAvailability.examination_schedule_id == examination_schedule_id,
|
abort(400, "You are not assigned to this committee!")
|
||||||
TemporaryAvailability.project_supervisor_id == project_supervisor.id,
|
|
||||||
TemporaryAvailability.id == temporary_availability_id,
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if ta is None:
|
enrollment.committee.members.remove(project_supervisor)
|
||||||
abort(404, "Your free time doesn't exist!")
|
|
||||||
|
|
||||||
db.session.delete(ta)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "You have just removed your free time!"}
|
return {"message": "You have just removed from committee!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:examination_schedule_id>/temporary-availabilities/")
|
|
||||||
@bp.input(TemporaryProjectSupervisorSchema, location="query")
|
|
||||||
@bp.output(ListOfFreeTimesSchema)
|
|
||||||
def list_enrollments_for_project_supervisor(
|
|
||||||
examination_schedule_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
# this code will be removed
|
|
||||||
project_supervisor = (
|
|
||||||
db.session.query(ProjectSupervisor)
|
|
||||||
.filter(ProjectSupervisor.id == data["id"])
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if project_supervisor is None:
|
|
||||||
abort(404, "Not found project supervisor!")
|
|
||||||
################
|
|
||||||
examination_schedule = ExaminationSchedule.query.filter(
|
|
||||||
ExaminationSchedule.id == examination_schedule_id
|
|
||||||
).first()
|
|
||||||
if examination_schedule is None:
|
|
||||||
abort(404, "Not found examination schedule!")
|
|
||||||
|
|
||||||
if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value:
|
|
||||||
abort(400, "Enrollments has started or closed! You have been delayed!")
|
|
||||||
|
|
||||||
# list of your term of defences first enrollment
|
|
||||||
ta = TemporaryAvailability.query.filter(
|
|
||||||
TemporaryAvailability.examination_schedule_id == examination_schedule_id,
|
|
||||||
TemporaryAvailability.project_supervisor_id == project_supervisor.id,
|
|
||||||
).all()
|
|
||||||
return {"free_times": ta}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:examination_schedule_id>/term-of-defences/")
|
|
||||||
@bp.input(TemporaryProjectSupervisorSchema, location="query")
|
|
||||||
@bp.output(ListOfTermOfDefenceSchema)
|
|
||||||
def list_created_term_of_defences_by_coordinator_for_project_supervisor(
|
|
||||||
examination_schedule_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
# this code will be removed
|
|
||||||
project_supervisor = (
|
|
||||||
db.session.query(ProjectSupervisor)
|
|
||||||
.filter(ProjectSupervisor.id == data["id"])
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if project_supervisor is None:
|
|
||||||
abort(404, "Not found project supervisor!")
|
|
||||||
################
|
|
||||||
es = ExaminationSchedule.query.filter(
|
|
||||||
ExaminationSchedule.id == examination_schedule_id,
|
|
||||||
ExaminationSchedule.year_group_id,
|
|
||||||
).first()
|
|
||||||
if es is None:
|
|
||||||
abort(404, "Not found examination schedule!")
|
|
||||||
|
|
||||||
# list of your free times first enrollment
|
|
||||||
td = (
|
|
||||||
TermOfDefence.query.join(TermOfDefence.members_of_committee)
|
|
||||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
|
||||||
.filter_by(id=project_supervisor.id)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
return {"term_of_defences": td}
|
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
from apiflask import APIBlueprint
|
|
||||||
from flask import abort
|
|
||||||
|
|
||||||
from ...base.schemas import MessageSchema
|
|
||||||
from ...students.models import Group
|
|
||||||
from ...students.schemas import (
|
|
||||||
ProjectGradeSheetDetailFirstTermSchema,
|
|
||||||
ProjectGradeSheetDetailSecondTermSchema,
|
|
||||||
ProjectGradeSheetEditFirstTermSchema,
|
|
||||||
ProjectGradeSheetEditSecondTermSchema,
|
|
||||||
)
|
|
||||||
from ..models import ProjectSupervisor
|
|
||||||
from ..query.project_grade_sheet import update_project_grade_sheet
|
|
||||||
from ..schemas import ProjectSupervisorTermQuerySchema, TemporaryProjectSupervisorSchema
|
|
||||||
|
|
||||||
bp = APIBlueprint(
|
|
||||||
"project_grade_sheet_for_project_supervisor",
|
|
||||||
__name__,
|
|
||||||
url_prefix="/project-grade-sheet",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/group/<int:group_id>/")
|
|
||||||
@bp.input(ProjectSupervisorTermQuerySchema, location="query")
|
|
||||||
def detail_project_grade_sheet(group_id: int, query: dict) -> dict:
|
|
||||||
project_supervisor_id = query.get("id")
|
|
||||||
project_supervisor = ProjectSupervisor.query.filter(
|
|
||||||
ProjectSupervisor.id == project_supervisor_id
|
|
||||||
).first()
|
|
||||||
if project_supervisor is None:
|
|
||||||
abort(404, "Not found project supervisor!")
|
|
||||||
####################################
|
|
||||||
term = query.get("term")
|
|
||||||
group = Group.query.filter(
|
|
||||||
Group.project_supervisor_id == project_supervisor_id, Group.id == group_id
|
|
||||||
).first()
|
|
||||||
if group is None or len(group.project_grade_sheet) == 0:
|
|
||||||
abort(
|
|
||||||
400, "Group doesn't exist or you are not project supervisor of that group!"
|
|
||||||
)
|
|
||||||
|
|
||||||
pgs = group.project_grade_sheet[0]
|
|
||||||
if term == 1:
|
|
||||||
schema = ProjectGradeSheetDetailFirstTermSchema()
|
|
||||||
else:
|
|
||||||
schema = ProjectGradeSheetDetailSecondTermSchema()
|
|
||||||
|
|
||||||
return schema.dump(pgs)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.patch("/group/<int:group_id>/first-term/")
|
|
||||||
@bp.input(TemporaryProjectSupervisorSchema, location="query")
|
|
||||||
@bp.input(ProjectGradeSheetEditFirstTermSchema, location="json")
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def update_project_grade_sheet_for_first_term(
|
|
||||||
group_id: int, query: dict, data: dict
|
|
||||||
) -> dict:
|
|
||||||
update_project_grade_sheet(group_id, query, data)
|
|
||||||
return {"message": "Your project grade sheet was updated!"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.patch("/group/<int:group_id>/second-term/")
|
|
||||||
@bp.input(TemporaryProjectSupervisorSchema, location="query")
|
|
||||||
@bp.input(ProjectGradeSheetEditSecondTermSchema, location="json")
|
|
||||||
@bp.output(MessageSchema)
|
|
||||||
def update_project_grade_sheet_for_second_term(
|
|
||||||
group_id: int, query: dict, data: dict
|
|
||||||
) -> dict:
|
|
||||||
update_project_grade_sheet(group_id, query, data)
|
|
||||||
return {"message": "Your project grade sheet was updated!"}
|
|
@ -1,33 +1,16 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import fields, validate, Schema
|
||||||
|
|
||||||
|
|
||||||
class FreeTimeSchema(Schema):
|
# MessageSchema, CommitteeCreateSchema
|
||||||
id = fields.Integer()
|
|
||||||
start_date = fields.DateTime(required=True)
|
class MessageSchema(Schema):
|
||||||
end_date = fields.DateTime(required=True)
|
message = fields.Str()
|
||||||
|
|
||||||
|
|
||||||
class ListOfFreeTimesSchema(Schema):
|
class CommitteeCreateSchema(Schema):
|
||||||
free_times = fields.List(fields.Nested(FreeTimeSchema))
|
project_supervisor_id = fields.Integer(required=True) # temporary field it will be removed in the future
|
||||||
|
|
||||||
|
|
||||||
class ListOfTermOfDefenceSchema(Schema):
|
|
||||||
term_of_defences = fields.List(fields.Nested(FreeTimeSchema))
|
|
||||||
|
|
||||||
|
|
||||||
class TimeAvailabilityCreateSchema(Schema):
|
|
||||||
start_date = fields.DateTime(required=True)
|
|
||||||
end_date = fields.DateTime(required=True)
|
|
||||||
project_supervisor_id = fields.Integer(
|
|
||||||
required=True
|
|
||||||
) # temporary field it will be removed in the future
|
|
||||||
|
|
||||||
|
|
||||||
# temporary class it will be removed in the future
|
# temporary class it will be removed in the future
|
||||||
class TemporaryProjectSupervisorSchema(Schema):
|
class TemporaryProjectSupervisorSchema(Schema):
|
||||||
id = fields.Integer(required=True)
|
id = fields.Integer(required=True)
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorTermQuerySchema(Schema):
|
|
||||||
id = fields.Integer(required=True)
|
|
||||||
term = fields.Integer(required=True, validate=validate.OneOf([1, 2]))
|
|
||||||
|
@ -1,166 +1,57 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from flask_sqlalchemy import BaseQuery
|
from flask_sqlalchemy import BaseQuery
|
||||||
|
|
||||||
from ..base.models import Base, Person
|
|
||||||
from ..base.utils import order_by_column_name
|
|
||||||
from ..dependencies import db
|
from ..dependencies import db
|
||||||
from ..examination_schedule.models import TermOfDefence
|
from ..base.models import Person, Base
|
||||||
|
from ..base.utils import order_by_column_name
|
||||||
|
from ..examination_schedule.models import Enrollment
|
||||||
class YearGroup(Base):
|
|
||||||
__tablename__ = "year_groups"
|
|
||||||
|
|
||||||
name = db.Column(db.String(50), nullable=False)
|
|
||||||
mode = db.Column(db.String(1), nullable=False)
|
|
||||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
|
||||||
|
|
||||||
__table__args = db.UniqueConstraint("name", "mode", name="uc_name_mode_year_group")
|
|
||||||
|
|
||||||
|
|
||||||
students_groups = db.Table(
|
|
||||||
"students_groups",
|
|
||||||
db.Column("group_id", db.ForeignKey("groups.id"), nullable=False),
|
|
||||||
db.Column("student_id", db.ForeignKey("students.id"), nullable=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Group(Base):
|
class Group(Base):
|
||||||
__tablename__ = "groups"
|
__tablename__ = "groups"
|
||||||
|
|
||||||
name = db.Column(db.String(60), nullable=False)
|
name = db.Column(db.String(60), nullable=False)
|
||||||
cdyd_kod = db.Column(db.String(60), default="2022/SZ")
|
cdyd_kod = db.Column(db.String(60), default='2022/SZ')
|
||||||
prz_kod = db.Column(db.String(60), default="06-DPRILI0")
|
prz_kod = db.Column(db.String(60), default='06-DPRILI0')
|
||||||
tzaj_kod = db.Column(db.String(60), default="LAB")
|
tzaj_kod = db.Column(db.String(60), default='LAB')
|
||||||
project_supervisor_id = db.Column(
|
project_supervisor_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'))
|
||||||
db.Integer, db.ForeignKey("project_supervisors.id")
|
project_supervisor = db.relationship('ProjectSupervisor', backref='groups', lazy=True)
|
||||||
)
|
points_for_first_term = db.Column(db.Integer, default=0, nullable=False)
|
||||||
project_supervisor = db.relationship("ProjectSupervisor", backref="groups")
|
points_for_second_term = db.Column(db.Integer, default=0, nullable=False)
|
||||||
year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id"))
|
# enrollment = db.relationship('Enrollment', uselist=False, backref='group')
|
||||||
year_group = db.relationship("YearGroup", backref="groups", lazy="joined")
|
|
||||||
points_for_first_term = db.Column(db.Float, default=0, nullable=False)
|
|
||||||
points_for_second_term = db.Column(db.Float, default=0, nullable=False)
|
|
||||||
grade_for_first_term = db.Column(db.Float, default=0, nullable=False)
|
|
||||||
grade_for_second_term = db.Column(db.Float, default=0, nullable=False)
|
|
||||||
students = db.relationship(
|
|
||||||
"Student", secondary=students_groups, back_populates="groups"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_by_name(cls, year_group_id: int, search_name: str = None) -> BaseQuery:
|
def search_by_name(cls, search_name: str = None) -> BaseQuery:
|
||||||
group_query = cls.query.filter(Group.year_group_id == year_group_id)
|
group_query = cls.query
|
||||||
|
|
||||||
if search_name is not None:
|
if search_name is not None:
|
||||||
group_query = group_query.filter(Group.name.like(f"{search_name}%"))
|
group_query = group_query.filter(Group.name.like(f'{search_name}%'))
|
||||||
|
|
||||||
return group_query
|
return group_query
|
||||||
|
|
||||||
|
|
||||||
class ProjectGradeSheet(Base):
|
class Student(Person):
|
||||||
__tablename__ = "project_grade_sheets"
|
|
||||||
|
|
||||||
group_id = db.Column(db.Integer, db.ForeignKey("groups.id"))
|
|
||||||
group = db.relationship("Group", backref="project_grade_sheet", uselist=False)
|
|
||||||
|
|
||||||
presentation_required_content_1 = db.Column(db.Integer, default=0)
|
|
||||||
presentation_required_content_2 = db.Column(db.Integer, default=0)
|
|
||||||
presentation_was_compatible_1 = db.Column(db.Integer, default=0)
|
|
||||||
presentation_was_compatible_2 = db.Column(db.Integer, default=0)
|
|
||||||
presentation_showing_1 = db.Column(db.Integer, default=0)
|
|
||||||
presentation_showing_2 = db.Column(db.Integer, default=0)
|
|
||||||
presentation_answers_to_questions_from_committee_1 = db.Column(
|
|
||||||
db.Integer, default=0
|
|
||||||
)
|
|
||||||
presentation_answers_to_questions_from_committee_2 = db.Column(
|
|
||||||
db.Integer, default=0
|
|
||||||
)
|
|
||||||
|
|
||||||
documentation_project_vision_1 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_project_vision_2 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_requirements_1 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_requirements_2 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_for_clients_1 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_for_clients_2 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_for_developers_1 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_for_developers_2 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_license_1 = db.Column(db.Integer, default=0)
|
|
||||||
documentation_license_2 = db.Column(db.Integer, default=0)
|
|
||||||
|
|
||||||
group_work_regularity_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_regularity_2 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_division_of_work_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_division_of_work_2 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_contact_with_client_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_contact_with_client_2 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_management_of_risk_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_management_of_risk_2 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_work_methodology_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_work_methodology_2 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_management_of_source_code_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_management_of_source_code_2 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_devops_1 = db.Column(db.Integer, default=0)
|
|
||||||
group_work_devops_2 = db.Column(db.Integer, default=0)
|
|
||||||
|
|
||||||
products_project_complexity_of_product_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_complexity_of_product_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_access_to_application_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_access_to_application_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_security_issues_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_security_issues_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_access_to_test_application_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_access_to_test_application_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_acceptance_criteria_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_acceptance_criteria_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_expected_functionality_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_expected_functionality_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_promises_well_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_promises_well_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_has_been_implemented_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_has_been_implemented_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_is_useful_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_is_useful_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_prototype_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_prototype_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_tests_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_tests_2 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_technology_1 = db.Column(db.Integer, default=0)
|
|
||||||
products_project_technology_2 = db.Column(db.Integer, default=0)
|
|
||||||
|
|
||||||
|
|
||||||
class Student(Base, Person):
|
|
||||||
__tablename__ = "students"
|
__tablename__ = "students"
|
||||||
|
|
||||||
index = db.Column(db.Integer, nullable=False)
|
pesel = db.Column(db.String(11), default='')
|
||||||
groups = db.relationship(
|
index = db.Column(db.Integer, primary_key=True)
|
||||||
"Group", secondary=students_groups, back_populates="students"
|
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
|
||||||
)
|
group = db.relationship('Group', backref='students', lazy=True)
|
||||||
year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id"))
|
mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary
|
||||||
year_group = db.relationship("YearGroup", backref="students")
|
|
||||||
|
|
||||||
__table__args = db.UniqueConstraint(
|
|
||||||
"index", "year_group_id", name="uc_index_year_group_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None,
|
||||||
cls,
|
mode: bool = None,
|
||||||
year_group_id: int,
|
|
||||||
fullname: str = None,
|
|
||||||
order_by_first_name: str = None,
|
order_by_first_name: str = None,
|
||||||
order_by_last_name: str = None,
|
order_by_last_name: str = None) -> BaseQuery:
|
||||||
) -> BaseQuery:
|
student_query = cls.query
|
||||||
student_query = cls.query.filter(Student.year_group_id == year_group_id)
|
|
||||||
|
if mode is not None:
|
||||||
|
student_query = student_query.filter_by(mode=mode)
|
||||||
|
|
||||||
if fullname is not None:
|
if fullname is not None:
|
||||||
student_query = student_query.filter(
|
student_query = student_query.filter((Student.first_name + ' ' + Student.last_name).like(f'{fullname}%'))
|
||||||
(Student.first_name + " " + Student.last_name).like(f"{fullname}%")
|
|
||||||
)
|
|
||||||
|
|
||||||
student_query = order_by_column_name(
|
student_query = order_by_column_name(student_query, Student.first_name, order_by_first_name)
|
||||||
student_query, Student.first_name, order_by_first_name
|
student_query = order_by_column_name(student_query, Student.last_name, order_by_last_name)
|
||||||
)
|
|
||||||
student_query = order_by_column_name(
|
|
||||||
student_query, Student.last_name, order_by_last_name
|
|
||||||
)
|
|
||||||
|
|
||||||
return student_query
|
return student_query
|
||||||
|
10
backend/app/students/query.py
Normal file
10
backend/app/students/query.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from flask import abort
|
||||||
|
from ..examination_schedule.models import ExaminationSchedule
|
||||||
|
|
||||||
|
|
||||||
|
def check_the_enrollments_has_just_started(es: ExaminationSchedule) -> None:
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
if es.start_date is None or es.end_date is None or not (es.start_date < now < es.end_date):
|
||||||
|
abort(403, "Forbidden! Enrollment hasn't started yet.")
|
@ -1,11 +1,9 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from .enrollments import bp as enrollments_bp
|
from .enrollments import bp as enrollments_bp
|
||||||
from .project_grade_sheet import bp as project_grade_sheet_bp
|
|
||||||
from .registrations import bp as registrations_bp
|
from .registrations import bp as registrations_bp
|
||||||
|
|
||||||
bp = Blueprint("students", __name__, url_prefix="/students")
|
bp = Blueprint("students", __name__, url_prefix="/students")
|
||||||
|
|
||||||
bp.register_blueprint(enrollments_bp)
|
|
||||||
bp.register_blueprint(project_grade_sheet_bp)
|
|
||||||
bp.register_blueprint(registrations_bp)
|
bp.register_blueprint(registrations_bp)
|
||||||
|
bp.register_blueprint(enrollments_bp)
|
||||||
|
@ -1,170 +1,69 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
from flask import abort
|
from flask import abort
|
||||||
|
|
||||||
from ...base.mode import EnrollmentsMode
|
from ..schemas import MessageSchema, TemporaryStudentSchema
|
||||||
from ...base.schemas import MessageSchema
|
|
||||||
from ...dependencies import db
|
from ...dependencies import db
|
||||||
from ...examination_schedule.models import ExaminationSchedule
|
from ...examination_schedule.models import Enrollment
|
||||||
|
from ..models import Student, Group
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ..models import Group, Student, TermOfDefence
|
from ...project_supervisor.query import get_enrollment_by_enrollment_and_examination_schedule_ids, \
|
||||||
from ..schemas import (
|
check_the_project_supervisor_is_in_committee
|
||||||
ExaminationScheduleListSchema,
|
from ..query import check_the_enrollments_has_just_started
|
||||||
TemporaryStudentSchema,
|
|
||||||
TermOfDefenceStudentListSchema,
|
|
||||||
)
|
|
||||||
|
|
||||||
bp = APIBlueprint("enrollments", __name__, url_prefix="/")
|
bp = APIBlueprint("enrollments", __name__, url_prefix="/")
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<int:examination_schedule_id>/enrollments/<int:term_of_defence_id>/")
|
@bp.post('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||||
@bp.input(TemporaryStudentSchema)
|
@bp.input(TemporaryStudentSchema)
|
||||||
@bp.output(MessageSchema, status_code=201)
|
@bp.output(MessageSchema)
|
||||||
def assign_your_group_to_term_of_defence(
|
def assign_group_for_this_exam_date(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
# this code will be removed
|
# this code will be removed
|
||||||
student = Student.query.filter(Student.id == data.get("student_id")).first()
|
student = Student.query.filter(Student.index == data['student_index']).first()
|
||||||
if student is None:
|
if student is None:
|
||||||
abort(404, "Not found student!")
|
abort(404, "Student doesn't exist!")
|
||||||
################
|
################
|
||||||
term_of_defence = (
|
st = Student.query.join(Group).join(ProjectSupervisor).filter(Student.index == student.index).first()
|
||||||
TermOfDefence.query.filter(
|
|
||||||
TermOfDefence.id == term_of_defence_id,
|
|
||||||
TermOfDefence.examination_schedule_id == examination_schedule_id,
|
|
||||||
)
|
|
||||||
.join(ExaminationSchedule)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if term_of_defence is None or (ex := term_of_defence.examination_schedule) is None:
|
enrollment = db.session.query(Enrollment.id).filter(Enrollment.group_id == st.group.id).first()
|
||||||
abort(404, "Term of defence not found!")
|
if enrollment is not None:
|
||||||
|
|
||||||
if ex.open_enrollments != EnrollmentsMode.OPEN.value:
|
|
||||||
abort(400, "Enrollments is closed! You have been delayed!")
|
|
||||||
|
|
||||||
group = (
|
|
||||||
Group.query.join(ProjectSupervisor)
|
|
||||||
.filter(Group.year_group_id == ex.year_group_id)
|
|
||||||
.join(Group.students)
|
|
||||||
.filter_by(index=student.index)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if group is None or group.project_supervisor is None:
|
|
||||||
abort(
|
|
||||||
400,
|
|
||||||
"You don't have a group or your group doesn't"
|
|
||||||
" have an assigned project supervisor!",
|
|
||||||
)
|
|
||||||
|
|
||||||
defence = TermOfDefence.query.filter(
|
|
||||||
TermOfDefence.group_id == group.id,
|
|
||||||
TermOfDefence.examination_schedule_id == examination_schedule_id,
|
|
||||||
).first()
|
|
||||||
if defence is not None:
|
|
||||||
abort(400, "Your group has already assigned to any exam date!")
|
abort(400, "Your group has already assigned to any exam date!")
|
||||||
|
|
||||||
td = (
|
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||||
TermOfDefence.query.join(TermOfDefence.members_of_committee)
|
check_the_enrollments_has_just_started(enrollment.examination_schedule)
|
||||||
.filter_by(id=group.project_supervisor_id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if td is None:
|
if st is None or st.group.project_supervisor is None:
|
||||||
|
abort(400, "You don't have a group or your group doesn't have an assigned project supervisor!")
|
||||||
|
|
||||||
|
if not check_the_project_supervisor_is_in_committee(enrollment_id, st.group.project_supervisor.id):
|
||||||
abort(400, "Your project supervisor is not in committee!")
|
abort(400, "Your project supervisor is not in committee!")
|
||||||
|
|
||||||
term_of_defence.group_id = group.id
|
enrollment.group_id = st.group.id
|
||||||
db.session.add(term_of_defence)
|
db.session.add(enrollment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "You have just assigned the group for this exam date!"}
|
return {"message": "You have just assigned the group for this exam date!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/<int:examination_schedule_id>/enrollments/<int:term_of_defence_id>/")
|
@bp.delete('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||||
@bp.input(TemporaryStudentSchema)
|
@bp.input(TemporaryStudentSchema)
|
||||||
@bp.output(MessageSchema)
|
@bp.output(MessageSchema)
|
||||||
def delete_your_group_from_term_of_defence(
|
def delete_group_for_this_exam_date(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
|
||||||
) -> dict:
|
|
||||||
# this code will be removed
|
# this code will be removed
|
||||||
student = Student.query.filter(Student.id == data.get("student_id")).first()
|
student = Student.query.filter(Student.index == data['student_index']).first()
|
||||||
if student is None:
|
if student is None:
|
||||||
abort(404, "Not found student!")
|
abort(404, "Student doesn't exist!")
|
||||||
################
|
################
|
||||||
|
|
||||||
term_of_defence = (
|
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||||
TermOfDefence.query.join(ExaminationSchedule)
|
|
||||||
.filter(TermOfDefence.id == term_of_defence_id)
|
|
||||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if term_of_defence is None:
|
if student.group.id != enrollment.group_id:
|
||||||
abort(404, "Not found term of defence!")
|
abort(400, "You are not assigned to this committee!")
|
||||||
ex = term_of_defence.examination_schedule
|
|
||||||
|
|
||||||
group = (
|
check_the_enrollments_has_just_started(enrollment.examination_schedule)
|
||||||
Group.query.filter(Group.year_group_id == ex.year_group_id)
|
|
||||||
.join(Group.students)
|
|
||||||
.filter_by(index=student.index)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if group is None or group.id != term_of_defence.group_id:
|
|
||||||
abort(400, "You are not assigned to this group!")
|
|
||||||
|
|
||||||
term_of_defence.group_id = None
|
enrollment.group = None
|
||||||
db.session.add(term_of_defence)
|
db.session.add(enrollment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"message": "You have just removed the group for this exam date!"}
|
return {"message": "You have just removed the group for this exam date!"}
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/examination-schedule/year-group/<int:year_group_id>/")
|
|
||||||
@bp.input(TemporaryStudentSchema, location="query")
|
|
||||||
@bp.output(ExaminationScheduleListSchema)
|
|
||||||
def list_examination_schedule(year_group_id: int, data: dict) -> dict:
|
|
||||||
# this code will be removed
|
|
||||||
student = Student.query.filter(Student.id == data.get("student_id")).first()
|
|
||||||
if student is None:
|
|
||||||
abort(404, "Not found student!")
|
|
||||||
################
|
|
||||||
|
|
||||||
examination_schedules = ExaminationSchedule.query.filter(
|
|
||||||
ExaminationSchedule.year_group_id == year_group_id
|
|
||||||
).all()
|
|
||||||
return {"examination_schedules": examination_schedules}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/examination-schedule/<int:examination_schedule_id>/enrollments/")
|
|
||||||
@bp.input(TemporaryStudentSchema, location="query")
|
|
||||||
@bp.output(TermOfDefenceStudentListSchema)
|
|
||||||
def list_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
|
|
||||||
# this code will be removed
|
|
||||||
student = Student.query.filter(Student.id == data.get("student_id")).first()
|
|
||||||
if student is None:
|
|
||||||
abort(404, "Not found student!")
|
|
||||||
################
|
|
||||||
show_available = data.get('show_available')
|
|
||||||
|
|
||||||
term_of_defences = (
|
|
||||||
TermOfDefence.query.filter(
|
|
||||||
TermOfDefence.examination_schedule_id == examination_schedule_id
|
|
||||||
)
|
|
||||||
.join(ExaminationSchedule, isouter=True)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
if show_available is True:
|
|
||||||
term_of_defences = list(
|
|
||||||
filter(
|
|
||||||
lambda n: len(
|
|
||||||
[
|
|
||||||
d.id
|
|
||||||
for d in n.members_of_committee
|
|
||||||
if len(student.groups) > 0 and d.id == student.groups[0].project_supervisor.id
|
|
||||||
]
|
|
||||||
)
|
|
||||||
> 0,
|
|
||||||
term_of_defences,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"term_of_defences": term_of_defences}
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
from apiflask import APIBlueprint
|
|
||||||
from flask import abort
|
|
||||||
|
|
||||||
from ..models import ProjectGradeSheet, Student
|
|
||||||
from ..schemas import (
|
|
||||||
ProjectGradeSheetDetailFirstTermSchema,
|
|
||||||
ProjectGradeSheetDetailSecondTermSchema,
|
|
||||||
StudentIndexQueryTempSchema,
|
|
||||||
)
|
|
||||||
|
|
||||||
bp = APIBlueprint("project_grade_sheet", __name__, url_prefix="/project-grade-sheet")
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/year-group/<int:year_group_id>/")
|
|
||||||
@bp.input(StudentIndexQueryTempSchema, location="query")
|
|
||||||
def detail_project_grade_sheet(year_group_id: int, query: dict) -> dict:
|
|
||||||
student_id = query.get("student_id")
|
|
||||||
st = Student.query.filter(Student.id == student_id).first()
|
|
||||||
if st is None:
|
|
||||||
abort(404, "Not found student!")
|
|
||||||
####################################
|
|
||||||
term = int(query.get("term"))
|
|
||||||
|
|
||||||
groups = [g for g in st.groups if g.year_group_id == year_group_id]
|
|
||||||
if len(groups) == 0:
|
|
||||||
abort(404, "Not found group!")
|
|
||||||
group = groups[0]
|
|
||||||
|
|
||||||
pgs = ProjectGradeSheet.query.filter(ProjectGradeSheet.group_id == group.id).first()
|
|
||||||
if pgs is None:
|
|
||||||
abort(404, "Not found project grade sheet!")
|
|
||||||
|
|
||||||
if term == 1:
|
|
||||||
schema = ProjectGradeSheetDetailFirstTermSchema()
|
|
||||||
else:
|
|
||||||
schema = ProjectGradeSheetDetailSecondTermSchema()
|
|
||||||
|
|
||||||
return schema.dump(pgs)
|
|
@ -1,34 +1,38 @@
|
|||||||
from apiflask import APIBlueprint
|
from apiflask import APIBlueprint
|
||||||
|
|
||||||
from ...base.utils import paginate_models
|
|
||||||
from ...dependencies import db
|
|
||||||
from ...project_supervisor.models import ProjectSupervisor
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
from ..models import Group
|
from ..models import Group
|
||||||
from ..schemas import ProjectSupervisorPaginationSchema, ProjectSupervisorQuerySchema
|
from ...dependencies import db
|
||||||
|
from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema
|
||||||
|
from ...base.utils import paginate_models
|
||||||
|
|
||||||
bp = APIBlueprint("registrations", __name__, url_prefix="/registrations")
|
bp = APIBlueprint("registrations", __name__, url_prefix="/registrations")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:year_group_id>/")
|
@bp.get('/')
|
||||||
@bp.input(ProjectSupervisorQuerySchema, location="query")
|
@bp.input(ProjectSupervisorQuerySchema, location='query')
|
||||||
@bp.output(ProjectSupervisorPaginationSchema)
|
@bp.output(ProjectSupervisorPaginationSchema)
|
||||||
def list_available_groups(year_group_id: int, query: dict) -> dict:
|
def list_available_groups(query: dict) -> dict:
|
||||||
page = query.get("page")
|
mode = 0 if query.get('mode') else 1
|
||||||
per_page = query.get("per_page")
|
page = query.get('page')
|
||||||
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
available_groups = ProjectSupervisor.limit_group - db.func.count(Group.id)
|
available_groups = (ProjectSupervisor.limit_group - ProjectSupervisor.count_groups)
|
||||||
ps_query = (
|
ps_query = db.session.query(ProjectSupervisor, available_groups).join(Group, isouter=True)
|
||||||
db.session.query(ProjectSupervisor, available_groups)
|
|
||||||
.join(Group, isouter=True)
|
if mode is not None:
|
||||||
.filter(ProjectSupervisor.year_group_id == year_group_id)
|
ps_query = ps_query.filter(ProjectSupervisor.mode != 1-mode)
|
||||||
.group_by(ProjectSupervisor.id)
|
|
||||||
)
|
ps_query = ps_query.group_by(ProjectSupervisor.id)
|
||||||
|
|
||||||
data = paginate_models(page, ps_query, per_page)
|
data = paginate_models(page, ps_query, per_page)
|
||||||
|
|
||||||
project_supervisors = []
|
project_supervisors = []
|
||||||
for project_supervisor, available_groups in data["items"]:
|
for project_supervisor, available_groups in data['items']:
|
||||||
setattr(project_supervisor, "available_groups", available_groups)
|
setattr(project_supervisor, 'available_groups', available_groups)
|
||||||
project_supervisors.append(project_supervisor)
|
project_supervisors.append(project_supervisor)
|
||||||
|
|
||||||
return {"project_supervisors": project_supervisors, "max_pages": data["max_pages"]}
|
return {
|
||||||
|
"project_supervisors": project_supervisors,
|
||||||
|
"max_pages": data['max_pages']
|
||||||
|
}
|
||||||
|
@ -1,186 +1,28 @@
|
|||||||
from marshmallow import Schema, fields, validate
|
from marshmallow import fields, Schema
|
||||||
|
|
||||||
POINTS = [0, 1, 3, 4]
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorWithAvailableGroupsSchema(Schema):
|
class ProjectSupervisorSchema(Schema):
|
||||||
first_name = fields.Str()
|
first_name = fields.Str()
|
||||||
last_name = fields.Str()
|
last_name = fields.Str()
|
||||||
email = fields.Str()
|
email = fields.Str()
|
||||||
|
mode = fields.Integer()
|
||||||
available_groups = fields.Integer()
|
available_groups = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorPaginationSchema(Schema):
|
class ProjectSupervisorPaginationSchema(Schema):
|
||||||
project_supervisors = fields.List(
|
project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema))
|
||||||
fields.Nested(ProjectSupervisorWithAvailableGroupsSchema)
|
|
||||||
)
|
|
||||||
max_pages = fields.Integer()
|
max_pages = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorQuerySchema(Schema):
|
class ProjectSupervisorQuerySchema(Schema):
|
||||||
page = fields.Integer()
|
page = fields.Integer()
|
||||||
per_page = fields.Integer()
|
per_page = fields.Integer()
|
||||||
|
mode = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
class TemporaryStudentSchema(Schema):
|
class TemporaryStudentSchema(Schema):
|
||||||
student_id = fields.Integer(required=True)
|
student_index = fields.Integer(required=True)
|
||||||
show_available = fields.Boolean(required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class ExaminationScheduleStudentSchema(Schema):
|
class MessageSchema(Schema):
|
||||||
id = fields.Integer()
|
message = fields.Str()
|
||||||
title = fields.Str()
|
|
||||||
start_date = fields.DateTime()
|
|
||||||
end_date = fields.DateTime()
|
|
||||||
open_enrollments = fields.Boolean()
|
|
||||||
|
|
||||||
|
|
||||||
class ExaminationScheduleListSchema(Schema):
|
|
||||||
examination_schedules = fields.List(fields.Nested(ExaminationScheduleStudentSchema))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorCommitteeSchema(Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
first_name = fields.Str()
|
|
||||||
last_name = fields.Str()
|
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefenceStudentItemSchema(Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
start_date = fields.DateTime()
|
|
||||||
end_date = fields.DateTime()
|
|
||||||
members_of_committee = fields.List(fields.Nested(ProjectSupervisorCommitteeSchema))
|
|
||||||
|
|
||||||
|
|
||||||
class StudentDataItemSchema(Schema):
|
|
||||||
index = fields.Integer()
|
|
||||||
first_name = fields.Str()
|
|
||||||
last_name = fields.Str()
|
|
||||||
|
|
||||||
|
|
||||||
class GroupDataItemSchema(Schema):
|
|
||||||
name = fields.Str()
|
|
||||||
students = fields.List(fields.Nested(StudentDataItemSchema))
|
|
||||||
|
|
||||||
|
|
||||||
class AssignedGroupToTermOfDefenceItemSchema(TermOfDefenceStudentItemSchema):
|
|
||||||
group = fields.Nested(GroupDataItemSchema)
|
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefenceStudentListSchema(Schema):
|
|
||||||
term_of_defences = fields.List(
|
|
||||||
fields.Nested(AssignedGroupToTermOfDefenceItemSchema)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class StudentIndexQueryTempSchema(Schema):
|
|
||||||
student_id = fields.Integer(required=True) # it will be removed
|
|
||||||
term = fields.Integer(required=True, validate=validate.OneOf([1, 2]))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectGradeSheetEditFirstTermSchema(Schema):
|
|
||||||
presentation_required_content_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
presentation_was_compatible_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
presentation_showing_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
presentation_answers_to_questions_from_committee_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
|
|
||||||
documentation_project_vision_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_requirements_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_for_clients_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_for_developers_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_license_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
|
|
||||||
group_work_regularity_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_division_of_work_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_contact_with_client_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_management_of_risk_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_work_methodology_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_management_of_source_code_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
group_work_devops_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
|
|
||||||
products_project_complexity_of_product_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_access_to_application_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_security_issues_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_access_to_test_application_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_acceptance_criteria_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_expected_functionality_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_promises_well_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_has_been_implemented_1 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_is_useful_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_prototype_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_tests_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_technology_1 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectGradeSheetDetailFirstTermSchema(ProjectGradeSheetEditFirstTermSchema):
|
|
||||||
id = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectGradeSheetEditSecondTermSchema(Schema):
|
|
||||||
presentation_required_content_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
presentation_was_compatible_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
presentation_showing_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
presentation_answers_to_questions_from_committee_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
|
|
||||||
documentation_project_vision_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_requirements_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_for_clients_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_for_developers_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
documentation_license_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
|
|
||||||
group_work_regularity_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_division_of_work_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_contact_with_client_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_management_of_risk_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_work_methodology_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
group_work_management_of_source_code_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
group_work_devops_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
|
|
||||||
products_project_complexity_of_product_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_access_to_application_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_security_issues_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_access_to_test_application_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_acceptance_criteria_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_expected_functionality_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_promises_well_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_has_been_implemented_2 = fields.Integer(
|
|
||||||
validate=validate.OneOf(POINTS)
|
|
||||||
)
|
|
||||||
products_project_is_useful_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_prototype_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_tests_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
products_project_technology_2 = fields.Integer(validate=validate.OneOf(POINTS))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectGradeSheetDetailSecondTermSchema(ProjectGradeSheetEditSecondTermSchema):
|
|
||||||
id = fields.Integer()
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import importlib
|
|
||||||
import os
|
import os
|
||||||
|
import importlib
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@ -7,8 +7,8 @@ from flask import current_app
|
|||||||
|
|
||||||
def get_app_directories() -> list:
|
def get_app_directories() -> list:
|
||||||
directories = []
|
directories = []
|
||||||
src_dir = current_app.config["SRC_DIR"]
|
src_dir = current_app.config['SRC_DIR']
|
||||||
excluded_dirs = current_app.config["EXCLUDED_DIRS"]
|
excluded_dirs = current_app.config['EXCLUDED_DIRS']
|
||||||
|
|
||||||
for dirname in os.listdir(src_dir):
|
for dirname in os.listdir(src_dir):
|
||||||
path = src_dir / dirname
|
path = src_dir / dirname
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"presentation_required_content_1": 3,
|
|
||||||
"presentation_required_content_2": 1,
|
|
||||||
"presentation_was_compatible_1": 4,
|
|
||||||
"presentation_was_compatible_2": 4,
|
|
||||||
"presentation_showing_1": 4,
|
|
||||||
"presentation_showing_2": 6,
|
|
||||||
"presentation_answers_to_questions_from_committee_1": 2,
|
|
||||||
"presentation_answers_to_questions_from_committee_2": 2,
|
|
||||||
"documentation_project_vision_1": 3,
|
|
||||||
"documentation_project_vision_2": 0,
|
|
||||||
"documentation_requirements_1": 3,
|
|
||||||
"documentation_requirements_2": 2,
|
|
||||||
"documentation_for_clients_1": 0,
|
|
||||||
"documentation_for_clients_2": 2,
|
|
||||||
"documentation_for_developers_1": 1,
|
|
||||||
"documentation_for_developers_2": 2,
|
|
||||||
"documentation_license_1": 1,
|
|
||||||
"documentation_license_2": 1,
|
|
||||||
"group_work_regularity_1": 5,
|
|
||||||
"group_work_regularity_2": 5,
|
|
||||||
"group_work_division_of_work_1": 3,
|
|
||||||
"group_work_division_of_work_2": 3,
|
|
||||||
"group_work_contact_with_client_1": 4,
|
|
||||||
"group_work_contact_with_client_2": 4,
|
|
||||||
"group_work_management_of_risk_1": 2,
|
|
||||||
"group_work_management_of_risk_2": 3,
|
|
||||||
"group_work_work_methodology_1": 3,
|
|
||||||
"group_work_work_methodology_2": 3,
|
|
||||||
"group_work_management_of_source_code_1": 2,
|
|
||||||
"group_work_management_of_source_code_2": 2,
|
|
||||||
"group_work_devops_1": 0,
|
|
||||||
"group_work_devops_2": 3,
|
|
||||||
"products_project_complexity_of_product_1": 3,
|
|
||||||
"products_project_complexity_of_product_2": 5,
|
|
||||||
"products_project_access_to_application_1": 1,
|
|
||||||
"products_project_access_to_application_2": 1,
|
|
||||||
"products_project_security_issues_1": 0,
|
|
||||||
"products_project_security_issues_2": 2,
|
|
||||||
"products_project_access_to_test_application_1": 0,
|
|
||||||
"products_project_access_to_test_application_2": 5,
|
|
||||||
"products_project_acceptance_criteria_1": 5,
|
|
||||||
"products_project_acceptance_criteria_2": 5,
|
|
||||||
"products_project_expected_functionality_1": 2,
|
|
||||||
"products_project_expected_functionality_2": 2,
|
|
||||||
"products_project_promises_well_1": 5,
|
|
||||||
"products_project_promises_well_2": 0,
|
|
||||||
"products_project_has_been_implemented_1": 0,
|
|
||||||
"products_project_has_been_implemented_2": 3,
|
|
||||||
"products_project_is_useful_1": 3,
|
|
||||||
"products_project_is_useful_2": 5,
|
|
||||||
"products_project_prototype_1": 2,
|
|
||||||
"products_project_prototype_2": 0,
|
|
||||||
"products_project_tests_1": 0,
|
|
||||||
"products_project_tests_2": 4,
|
|
||||||
"products_project_technology_1": 1,
|
|
||||||
"products_project_technology_2": 2
|
|
||||||
}
|
|
Binary file not shown.
@ -1,9 +1,7 @@
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from app import create_app
|
from app import create_app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
app.run()
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(host="0.0.0.0")
|
|
||||||
|
@ -3,9 +3,10 @@ from __future__ import with_statement
|
|||||||
import logging
|
import logging
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
config = context.config
|
config = context.config
|
||||||
@ -13,17 +14,17 @@ config = context.config
|
|||||||
# Interpret the config file for Python logging.
|
# Interpret the config file for Python logging.
|
||||||
# This line sets up loggers basically.
|
# This line sets up loggers basically.
|
||||||
fileConfig(config.config_file_name)
|
fileConfig(config.config_file_name)
|
||||||
logger = logging.getLogger("alembic.env")
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
# from myapp import mymodel
|
# from myapp import mymodel
|
||||||
# target_metadata = mymodel.Base.metadata
|
# target_metadata = mymodel.Base.metadata
|
||||||
config.set_main_option(
|
config.set_main_option(
|
||||||
"sqlalchemy.url",
|
'sqlalchemy.url',
|
||||||
str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"),
|
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
||||||
)
|
'%', '%%'))
|
||||||
target_metadata = current_app.extensions["migrate"].db.metadata
|
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
# other values from the config, defined by the needs of env.py,
|
||||||
# can be acquired:
|
# can be acquired:
|
||||||
@ -44,7 +45,9 @@ def run_migrations_offline():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
url = config.get_main_option("sqlalchemy.url")
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
|
context.configure(
|
||||||
|
url=url, target_metadata=target_metadata, literal_binds=True
|
||||||
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
@ -62,20 +65,20 @@ def run_migrations_online():
|
|||||||
# when there are no changes to the schema
|
# when there are no changes to the schema
|
||||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
def process_revision_directives(context, revision, directives):
|
def process_revision_directives(context, revision, directives):
|
||||||
if getattr(config.cmd_opts, "autogenerate", False):
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
script = directives[0]
|
script = directives[0]
|
||||||
if script.upgrade_ops.is_empty():
|
if script.upgrade_ops.is_empty():
|
||||||
directives[:] = []
|
directives[:] = []
|
||||||
logger.info("No changes in schema detected.")
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
connectable = current_app.extensions["migrate"].db.get_engine()
|
connectable = current_app.extensions['migrate'].db.get_engine()
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
context.configure(
|
context.configure(
|
||||||
connection=connection,
|
connection=connection,
|
||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
process_revision_directives=process_revision_directives,
|
process_revision_directives=process_revision_directives,
|
||||||
**current_app.extensions["migrate"].configure_args
|
**current_app.extensions['migrate'].configure_args
|
||||||
)
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
|
68
backend/migrations/versions/151d3b6306ec_.py
Normal file
68
backend/migrations/versions/151d3b6306ec_.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 151d3b6306ec
|
||||||
|
Revises:
|
||||||
|
Create Date: 2022-06-11 07:57:52.389681
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '151d3b6306ec'
|
||||||
|
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('count_groups', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('mode', sa.Boolean(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('email')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_project_supervisors_first_name'), 'project_supervisors', ['first_name'], unique=False)
|
||||||
|
op.create_index(op.f('ix_project_supervisors_last_name'), 'project_supervisors', ['last_name'], unique=False)
|
||||||
|
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.Column('points_for_first_term', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('points_for_second_term', sa.Integer(), nullable=False),
|
||||||
|
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('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')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_students_first_name'), 'students', ['first_name'], unique=False)
|
||||||
|
op.create_index(op.f('ix_students_last_name'), 'students', ['last_name'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_students_last_name'), table_name='students')
|
||||||
|
op.drop_index(op.f('ix_students_first_name'), table_name='students')
|
||||||
|
op.drop_table('students')
|
||||||
|
op.drop_table('groups')
|
||||||
|
op.drop_index(op.f('ix_project_supervisors_last_name'), table_name='project_supervisors')
|
||||||
|
op.drop_index(op.f('ix_project_supervisors_first_name'), table_name='project_supervisors')
|
||||||
|
op.drop_table('project_supervisors')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,315 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 5f2f440d05e2
|
|
||||||
Revises:
|
|
||||||
Create Date: 2023-01-15 23:52:36.927007
|
|
||||||
|
|
||||||
"""
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = "5f2f440d05e2"
|
|
||||||
down_revision = None
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table(
|
|
||||||
"year_groups",
|
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column("name", sa.String(length=50), nullable=False),
|
|
||||||
sa.Column("mode", sa.String(length=1), nullable=False),
|
|
||||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"examination_schedules",
|
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column("title", sa.String(length=100), nullable=False),
|
|
||||||
sa.Column("duration_time", sa.Integer(), nullable=False),
|
|
||||||
sa.Column("open_enrollments", sa.String(length=1), nullable=False),
|
|
||||||
sa.Column("start_date", sa.DateTime(), nullable=False),
|
|
||||||
sa.Column("end_date", sa.DateTime(), nullable=False),
|
|
||||||
sa.Column("year_group_id", sa.Integer(), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["year_group_id"],
|
|
||||||
["year_groups.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
sa.UniqueConstraint("title"),
|
|
||||||
)
|
|
||||||
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=False),
|
|
||||||
sa.Column("limit_group", sa.Integer(), nullable=False),
|
|
||||||
sa.Column("is_coordinator", sa.Boolean(), nullable=False),
|
|
||||||
sa.Column("year_group_id", sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["year_group_id"],
|
|
||||||
["year_groups.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_index(
|
|
||||||
op.f("ix_project_supervisors_email"),
|
|
||||||
"project_supervisors",
|
|
||||||
["email"],
|
|
||||||
unique=False,
|
|
||||||
)
|
|
||||||
op.create_index(
|
|
||||||
op.f("ix_project_supervisors_first_name"),
|
|
||||||
"project_supervisors",
|
|
||||||
["first_name"],
|
|
||||||
unique=False,
|
|
||||||
)
|
|
||||||
op.create_index(
|
|
||||||
op.f("ix_project_supervisors_last_name"),
|
|
||||||
"project_supervisors",
|
|
||||||
["last_name"],
|
|
||||||
unique=False,
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"students",
|
|
||||||
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=False),
|
|
||||||
sa.Column("index", sa.Integer(), nullable=False),
|
|
||||||
sa.Column("year_group_id", sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["year_group_id"],
|
|
||||||
["year_groups.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_index(op.f("ix_students_email"), "students", ["email"], unique=False)
|
|
||||||
op.create_index(
|
|
||||||
op.f("ix_students_first_name"), "students", ["first_name"], unique=False
|
|
||||||
)
|
|
||||||
op.create_index(
|
|
||||||
op.f("ix_students_last_name"), "students", ["last_name"], unique=False
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"groups",
|
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column("name", sa.String(length=60), nullable=False),
|
|
||||||
sa.Column("cdyd_kod", sa.String(length=60), nullable=True),
|
|
||||||
sa.Column("prz_kod", sa.String(length=60), nullable=True),
|
|
||||||
sa.Column("tzaj_kod", sa.String(length=60), nullable=True),
|
|
||||||
sa.Column("project_supervisor_id", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("year_group_id", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("points_for_first_term", sa.Float(), nullable=False),
|
|
||||||
sa.Column("points_for_second_term", sa.Float(), nullable=False),
|
|
||||||
sa.Column("grade_for_first_term", sa.Float(), nullable=False),
|
|
||||||
sa.Column("grade_for_second_term", sa.Float(), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["project_supervisor_id"],
|
|
||||||
["project_supervisors.id"],
|
|
||||||
),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["year_group_id"],
|
|
||||||
["year_groups.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"temporary_availabilities",
|
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column("start_date", sa.DateTime(), nullable=False),
|
|
||||||
sa.Column("end_date", sa.DateTime(), nullable=False),
|
|
||||||
sa.Column("examination_schedule_id", sa.Integer(), nullable=False),
|
|
||||||
sa.Column("project_supervisor_id", sa.Integer(), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["examination_schedule_id"],
|
|
||||||
["examination_schedules.id"],
|
|
||||||
),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["project_supervisor_id"],
|
|
||||||
["project_supervisors.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"project_grade_sheets",
|
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column("group_id", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("presentation_required_content_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("presentation_required_content_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("presentation_was_compatible_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("presentation_was_compatible_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("presentation_showing_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("presentation_showing_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column(
|
|
||||||
"presentation_answers_to_questions_from_committee_1",
|
|
||||||
sa.Integer(),
|
|
||||||
nullable=True,
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"presentation_answers_to_questions_from_committee_2",
|
|
||||||
sa.Integer(),
|
|
||||||
nullable=True,
|
|
||||||
),
|
|
||||||
sa.Column("documentation_project_vision_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_project_vision_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_requirements_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_requirements_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_for_clients_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_for_clients_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_for_developers_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_for_developers_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_license_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("documentation_license_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_regularity_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_regularity_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_division_of_work_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_division_of_work_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_contact_with_client_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_contact_with_client_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_management_of_risk_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_management_of_risk_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_work_methodology_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_work_methodology_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column(
|
|
||||||
"group_work_management_of_source_code_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"group_work_management_of_source_code_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column("group_work_devops_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_work_devops_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_complexity_of_product_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_complexity_of_product_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_access_to_application_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_access_to_application_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column("products_project_security_issues_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_security_issues_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_access_to_test_application_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_access_to_test_application_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_acceptance_criteria_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_acceptance_criteria_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_expected_functionality_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_expected_functionality_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column("products_project_promises_well_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_promises_well_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_has_been_implemented_1", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"products_project_has_been_implemented_2", sa.Integer(), nullable=True
|
|
||||||
),
|
|
||||||
sa.Column("products_project_is_useful_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_is_useful_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_prototype_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_prototype_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_tests_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_tests_2", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_technology_1", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("products_project_technology_2", sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["group_id"],
|
|
||||||
["groups.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"students_groups",
|
|
||||||
sa.Column("group_id", sa.Integer(), nullable=False),
|
|
||||||
sa.Column("student_id", sa.Integer(), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["group_id"],
|
|
||||||
["groups.id"],
|
|
||||||
),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["student_id"],
|
|
||||||
["students.id"],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"term_of_defences",
|
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column("start_date", sa.DateTime(), nullable=False),
|
|
||||||
sa.Column("end_date", sa.DateTime(), nullable=False),
|
|
||||||
sa.Column("examination_schedule_id", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("group_id", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("chairman_of_committee", sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["chairman_of_committee"],
|
|
||||||
["project_supervisors.id"],
|
|
||||||
),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["examination_schedule_id"],
|
|
||||||
["examination_schedules.id"],
|
|
||||||
),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["group_id"],
|
|
||||||
["groups.id"],
|
|
||||||
),
|
|
||||||
sa.PrimaryKeyConstraint("id"),
|
|
||||||
)
|
|
||||||
op.create_table(
|
|
||||||
"committees",
|
|
||||||
sa.Column("term_of_defence_id", sa.Integer(), nullable=True),
|
|
||||||
sa.Column("project_supervisor_id", sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["project_supervisor_id"],
|
|
||||||
["project_supervisors.id"],
|
|
||||||
),
|
|
||||||
sa.ForeignKeyConstraint(
|
|
||||||
["term_of_defence_id"],
|
|
||||||
["term_of_defences.id"],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table("committees")
|
|
||||||
op.drop_table("term_of_defences")
|
|
||||||
op.drop_table("students_groups")
|
|
||||||
op.drop_table("project_grade_sheets")
|
|
||||||
op.drop_table("temporary_availabilities")
|
|
||||||
op.drop_table("groups")
|
|
||||||
op.drop_index(op.f("ix_students_last_name"), table_name="students")
|
|
||||||
op.drop_index(op.f("ix_students_first_name"), table_name="students")
|
|
||||||
op.drop_index(op.f("ix_students_email"), table_name="students")
|
|
||||||
op.drop_table("students")
|
|
||||||
op.drop_index(
|
|
||||||
op.f("ix_project_supervisors_last_name"), table_name="project_supervisors"
|
|
||||||
)
|
|
||||||
op.drop_index(
|
|
||||||
op.f("ix_project_supervisors_first_name"), table_name="project_supervisors"
|
|
||||||
)
|
|
||||||
op.drop_index(
|
|
||||||
op.f("ix_project_supervisors_email"), table_name="project_supervisors"
|
|
||||||
)
|
|
||||||
op.drop_table("project_supervisors")
|
|
||||||
op.drop_table("examination_schedules")
|
|
||||||
op.drop_table("year_groups")
|
|
||||||
# ### end Alembic commands ###
|
|
64
backend/migrations/versions/64ad410af362_.py
Normal file
64
backend/migrations/versions/64ad410af362_.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 64ad410af362
|
||||||
|
Revises: ceaefb33117e
|
||||||
|
Create Date: 2022-10-27 09:44:53.799889
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '64ad410af362'
|
||||||
|
down_revision = 'ceaefb33117e'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('examination_schedules',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('title', sa.String(length=100), nullable=False),
|
||||||
|
sa.Column('mode', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('start_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('end_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('title')
|
||||||
|
)
|
||||||
|
op.create_table('enrollments',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('start_date', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('end_date', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('examination_schedule_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('group_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['examination_schedule_id'], ['examination_schedules.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('committees',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('enrollment_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['enrollment_id'], ['enrollments.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('committees_projects_supervisors',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('chairman', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('committee_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('member_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['committee_id'], ['committees.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['member_id'], ['project_supervisors.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('committees_projects_supervisors')
|
||||||
|
op.drop_table('committees')
|
||||||
|
op.drop_table('enrollments')
|
||||||
|
op.drop_table('examination_schedules')
|
||||||
|
# ### end Alembic commands ###
|
34
backend/migrations/versions/ceaefb33117e_.py
Normal file
34
backend/migrations/versions/ceaefb33117e_.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: ceaefb33117e
|
||||||
|
Revises: 151d3b6306ec
|
||||||
|
Create Date: 2022-06-12 17:43:24.714877
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'ceaefb33117e'
|
||||||
|
down_revision = '151d3b6306ec'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('groups', sa.Column('cdyd_kod', sa.String(length=60), nullable=True))
|
||||||
|
op.add_column('groups', sa.Column('prz_kod', sa.String(length=60), nullable=True))
|
||||||
|
op.add_column('groups', sa.Column('tzaj_kod', sa.String(length=60), nullable=True))
|
||||||
|
op.add_column('students', sa.Column('pesel', sa.String(length=11), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('students', 'pesel')
|
||||||
|
op.drop_column('groups', 'tzaj_kod')
|
||||||
|
op.drop_column('groups', 'prz_kod')
|
||||||
|
op.drop_column('groups', 'cdyd_kod')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,15 +0,0 @@
|
|||||||
upstream web_app {
|
|
||||||
server backend_service:5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://web_app;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_redirect off;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
@ -1,4 +0,0 @@
|
|||||||
[pytest]
|
|
||||||
filterwarnings =
|
|
||||||
error
|
|
||||||
ignore::UserWarning
|
|
@ -13,7 +13,3 @@ apiflask>=1.0.2,<1.1.0
|
|||||||
python-dotenv==0.21.0
|
python-dotenv==0.21.0
|
||||||
factory_boy>=3.2.1,<3.3.0
|
factory_boy>=3.2.1,<3.3.0
|
||||||
reportlab>=3.6.12,<3.7.0
|
reportlab>=3.6.12,<3.7.0
|
||||||
gunicorn>=20.1.0,<20.2.0
|
|
||||||
black>=22.12.0,<22.13.0
|
|
||||||
flake8>=6.0.0,<6.1.0
|
|
||||||
isort>=5.11.4,<5.12.0
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
max-line-length = 88
|
|
||||||
extend-ignore =
|
|
||||||
E203,
|
|
||||||
exclude =
|
|
||||||
migrations,
|
|
||||||
__pycache__,
|
|
||||||
tests
|
|
||||||
per-file-ignores = **/*/models.py:F401
|
|
||||||
|
|
@ -1,38 +1,18 @@
|
|||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from apiflask import APIFlask
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.ctx import AppContext
|
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
|
|
||||||
from app import create_app
|
from app import create_app
|
||||||
from app.dependencies import db
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def test_app() -> Generator[Flask, None, None]:
|
def app() -> Generator[Flask, None, None]:
|
||||||
yield create_app("testing")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def test_app_ctx_with_db(test_app) -> Generator[AppContext, None, None]:
|
|
||||||
with test_app.app_context() as ctx:
|
|
||||||
db.create_all()
|
|
||||||
yield ctx
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def test_client() -> FlaskClient:
|
|
||||||
app = create_app("testing")
|
app = create_app("testing")
|
||||||
with app.app_context():
|
|
||||||
db.create_all()
|
|
||||||
return app.test_client()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def test_app_with_context() -> Generator[APIFlask, None, None]:
|
|
||||||
app = create_app("testing")
|
|
||||||
with app.app_context():
|
|
||||||
db.create_all()
|
|
||||||
yield app
|
yield app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def client(app: Flask) -> FlaskClient:
|
||||||
|
return app.test_client()
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
NAZWISKO,IMIE,INDEKS,PESEL,EMAIL
|
|
||||||
Drzewiński,Patryk,452790,11111111111,patdrz1@st.amu.edu.pl
|
|
||||||
Skowronek,Adam,452791,22222222222,adasko8@st.amu.edu.pl
|
|
||||||
Kuzmenko,Mariia,452792,33333333333,markuz5@st.amu.edu.pl
|
|
|
@ -1,4 +0,0 @@
|
|||||||
NAZWISKO,NOTHING,INDEKS,PESEL,EMAIL
|
|
||||||
Drzewiński,Patryk,452790,11111111111,patdrz1@st.amu.edu.pl
|
|
||||||
Skowronek,Adam,452791,22222222222,adasko8@st.amu.edu.pl
|
|
||||||
Kuzmenko,Mariia,452792,33333333333,markuz5@st.amu.edu.pl
|
|
|
@ -1,4 +0,0 @@
|
|||||||
NAZWISKO,IMIE,INDEKS,PESEL,EMAIL
|
|
||||||
0030,Patryk,452790,11111111111,patdrz1@st.amu.edu.pl
|
|
||||||
4939,Adam,452791,22222222222,adasko8@st.amu.edu.pl
|
|
||||||
3232,Mariia,452792,33333333333,markuz5@st.amu.edu.pl
|
|
|
@ -1,186 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import random
|
|
||||||
|
|
||||||
from factory import Sequence, alchemy
|
|
||||||
from factory.faker import Faker
|
|
||||||
from factory.fuzzy import FuzzyDateTime, FuzzyInteger
|
|
||||||
|
|
||||||
from app.base.mode import ModeGroups
|
|
||||||
from app.dependencies import db
|
|
||||||
from app.examination_schedule.models import (
|
|
||||||
ExaminationSchedule,
|
|
||||||
TemporaryAvailability,
|
|
||||||
TermOfDefence,
|
|
||||||
)
|
|
||||||
from app.project_supervisor.models import ProjectSupervisor
|
|
||||||
from app.students.models import Group, ProjectGradeSheet, Student, YearGroup
|
|
||||||
|
|
||||||
|
|
||||||
class YearGroupFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = YearGroup
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
name = "2022/2023"
|
|
||||||
mode = ModeGroups.STATIONARY.value
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectSupervisorFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = ProjectSupervisor
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
first_name = Faker("first_name")
|
|
||||||
last_name = Faker("last_name")
|
|
||||||
email = Faker("email")
|
|
||||||
limit_group = 4
|
|
||||||
|
|
||||||
|
|
||||||
class GroupFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = Group
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
name = Sequence(lambda n: f"Group-{n}")
|
|
||||||
points_for_first_term = FuzzyInteger(1, 100)
|
|
||||||
points_for_second_term = FuzzyInteger(1, 100)
|
|
||||||
grade_for_first_term = FuzzyInteger(2, 5)
|
|
||||||
grade_for_second_term = FuzzyInteger(2, 5)
|
|
||||||
|
|
||||||
|
|
||||||
class StudentFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = Student
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
first_name = Faker("first_name")
|
|
||||||
last_name = Faker("last_name")
|
|
||||||
email = Faker("email")
|
|
||||||
index = Sequence(lambda n: 400_000 + n)
|
|
||||||
|
|
||||||
|
|
||||||
class ExaminationScheduleFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = ExaminationSchedule
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
title = Sequence(lambda n: f"Examination schedule {n}")
|
|
||||||
duration_time = 30
|
|
||||||
start_date = FuzzyDateTime(
|
|
||||||
datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc),
|
|
||||||
datetime.datetime(2020, 1, 5, tzinfo=datetime.timezone.utc),
|
|
||||||
)
|
|
||||||
end_date = FuzzyDateTime(
|
|
||||||
datetime.datetime(2020, 1, 10, tzinfo=datetime.timezone.utc),
|
|
||||||
datetime.datetime(2020, 1, 20, tzinfo=datetime.timezone.utc),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TermOfDefenceFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = TermOfDefence
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
start_date = Sequence(
|
|
||||||
lambda n: datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc)
|
|
||||||
+ datetime.timedelta(n * 30)
|
|
||||||
)
|
|
||||||
end_date = Sequence(
|
|
||||||
lambda n: datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc)
|
|
||||||
+ datetime.timedelta(n * 30 + 30)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TemporaryAvailabilityFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = TemporaryAvailability
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
start_date = Sequence(
|
|
||||||
lambda n: datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc)
|
|
||||||
+ datetime.timedelta(n * 30)
|
|
||||||
)
|
|
||||||
end_date = Sequence(
|
|
||||||
lambda n: datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc)
|
|
||||||
+ datetime.timedelta(n * 30 + 30)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TemporaryAvailabilityFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = TemporaryAvailability
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
start_date = Sequence(
|
|
||||||
lambda n: datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc)
|
|
||||||
+ datetime.timedelta(n * 30)
|
|
||||||
)
|
|
||||||
end_date = Sequence(
|
|
||||||
lambda n: datetime.datetime(2020, 1, 1, tzinfo=datetime.timezone.utc)
|
|
||||||
+ datetime.timedelta(n * 30 + 30)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectGradeSheetFactory(alchemy.SQLAlchemyModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = ProjectGradeSheet
|
|
||||||
sqlalchemy_session = db.session
|
|
||||||
|
|
||||||
presentation_required_content_1 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_required_content_2 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_was_compatible_1 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_was_compatible_2 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_showing_1 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_showing_2 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_answers_to_questions_from_committee_1 = random.choice([0, 1, 3, 4])
|
|
||||||
presentation_answers_to_questions_from_committee_2 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_project_vision_1 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_project_vision_2 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_requirements_1 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_requirements_2 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_for_clients_1 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_for_clients_2 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_for_developers_1 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_for_developers_2 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_license_1 = random.choice([0, 1, 3, 4])
|
|
||||||
documentation_license_2 = random.choice([0, 1, 3, 4])
|
|
||||||
|
|
||||||
group_work_regularity_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_regularity_2 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_division_of_work_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_division_of_work_2 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_contact_with_client_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_contact_with_client_2 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_management_of_risk_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_management_of_risk_2 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_work_methodology_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_work_methodology_2 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_management_of_source_code_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_management_of_source_code_2 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_devops_1 = random.choice([0, 1, 3, 4])
|
|
||||||
group_work_devops_2 = random.choice([0, 1, 3, 4])
|
|
||||||
|
|
||||||
products_project_complexity_of_product_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_complexity_of_product_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_access_to_application_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_access_to_application_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_security_issues_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_security_issues_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_access_to_test_application_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_access_to_test_application_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_acceptance_criteria_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_acceptance_criteria_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_expected_functionality_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_expected_functionality_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_promises_well_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_promises_well_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_has_been_implemented_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_has_been_implemented_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_is_useful_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_is_useful_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_prototype_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_prototype_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_tests_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_tests_2 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_technology_1 = random.choice([0, 1, 3, 4])
|
|
||||||
products_project_technology_2 = random.choice([0, 1, 3, 4])
|
|
@ -1,694 +0,0 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
from app.base.mode import EnrollmentsMode
|
|
||||||
from app.dependencies import db
|
|
||||||
from app.examination_schedule.models import ExaminationSchedule, TermOfDefence
|
|
||||||
|
|
||||||
from ...factory import (
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
GroupFactory,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
TemporaryAvailabilityFactory,
|
|
||||||
TermOfDefenceFactory,
|
|
||||||
YearGroupFactory,
|
|
||||||
)
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
get_data_of_term_of_defence,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_of_term_of_defences(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
create_many_models(32, TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defences/"
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
assert len(data.get("term_of_defences")) == 32
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/delete/{td.id}/",
|
|
||||||
None,
|
|
||||||
"Term of defence was deleted!",
|
|
||||||
200,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_term_of_defence_if_term_of_defence_or_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/enrollments/2/delete/1/",
|
|
||||||
None,
|
|
||||||
"Not found examination schedule or term of defence!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_term_of_defence(test_app_with_context, monkeypatch) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence()
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add",
|
|
||||||
data,
|
|
||||||
"Term of defence was created!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
td = TermOfDefence.query.first()
|
|
||||||
assert_model_changes(
|
|
||||||
td, {"start_date": data["start_date"], "end_date": data["end_date"]}
|
|
||||||
)
|
|
||||||
assert {member.id for member in td.members_of_committee} == set(
|
|
||||||
data["project_supervisors"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"Term of defences was created!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
assert TermOfDefence.query.count() == 5
|
|
||||||
|
|
||||||
for td in TermOfDefence.query.all():
|
|
||||||
assert {member.id for member in td.members_of_committee} == set(
|
|
||||||
data["project_supervisors"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences_with_invalid_id_of_chairman(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = 353
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"Invalid id of chairman committee!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/enrollments/32/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"Not found examination schedule!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences_if_project_supervisors_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [1, 2, 3]
|
|
||||||
data["chairman_of_committee"] = 1
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"Not found project supervisors!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences_with_invalid_duration_time(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(133)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"Invalid duration time!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences_if_start_date_is_greater_than_end_date(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(133)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
data["start_date"], data["end_date"] = data["end_date"], data["start_date"]
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"End date must be greater than start date!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_many_term_of_defences_with_invalid_date_range(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(120)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
data["end_date"] = (ex.end_date + datetime.timedelta(days=1)).strftime(
|
|
||||||
"%Y-%m-%dT%H:%M:%S.000Z"
|
|
||||||
)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/add-term-of-defences/",
|
|
||||||
data,
|
|
||||||
"Invalid date range!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
td.members_of_committee = project_supervisors
|
|
||||||
td.chairman_of_committee = project_supervisors[0].id
|
|
||||||
db.session.commit()
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[2].id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/update/{td.id}/",
|
|
||||||
data,
|
|
||||||
"Term of defence was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert td.chairman_of_committee == project_supervisors[2].id
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence_with_invalid_id_of_chairman(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = 353
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/update/{td.id}/",
|
|
||||||
data,
|
|
||||||
"Invalid id of chairman committee!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence_if_examination_schedule_or_term_of_defence_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [1, 2, 3]
|
|
||||||
data["chairman_of_committee"] = 1
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/32/update/23/",
|
|
||||||
data,
|
|
||||||
"Not found term of defence!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence_if_project_supervisors_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
data = get_data_of_term_of_defence(150)
|
|
||||||
data["project_supervisors"] = [1, 2, 3]
|
|
||||||
data["chairman_of_committee"] = 1
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/update/{td.id}/",
|
|
||||||
data,
|
|
||||||
"Not found project supervisors!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence_with_invalid_duration_time(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
data = get_data_of_term_of_defence(133)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/update/{td.id}/",
|
|
||||||
data,
|
|
||||||
"Invalid duration time!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence_if_start_date_is_greater_than_end_date(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(133)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
data["start_date"], data["end_date"] = data["end_date"], data["start_date"]
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/update/{td.id}/",
|
|
||||||
data,
|
|
||||||
"End date must be greater than start date!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_term_of_defence_with_invalid_date_range(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
3, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
|
|
||||||
data = get_data_of_term_of_defence(120)
|
|
||||||
data["project_supervisors"] = [ps.id for ps in project_supervisors]
|
|
||||||
data["chairman_of_committee"] = project_supervisors[0].id
|
|
||||||
data["end_date"] = (ex.end_date + datetime.timedelta(days=1)).strftime(
|
|
||||||
"%Y-%m-%dT%H:%M:%S.000Z"
|
|
||||||
)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/update/{td.id}/",
|
|
||||||
data,
|
|
||||||
"Invalid date range!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_temporary_availabilities(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
amount = 24
|
|
||||||
create_many_models(
|
|
||||||
amount,
|
|
||||||
TemporaryAvailabilityFactory,
|
|
||||||
examination_schedule_id=ex.id,
|
|
||||||
project_supervisor_id=ps.id,
|
|
||||||
)
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/temporary-availabilities/"
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
assert len(data.get("temporary_availabilities")) == amount
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_temporary_availabilities_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
url = "/api/coordinator/enrollments/43/temporary-availabilities/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
None,
|
|
||||||
"Not found examination schedule!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_of_assigned_group_to_term_of_defences(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
amount = 5
|
|
||||||
groups = create_many_models(amount, GroupFactory)
|
|
||||||
tds = create_many_models(
|
|
||||||
24, TermOfDefenceFactory, examination_schedule_id=ex.id
|
|
||||||
)
|
|
||||||
for group, td in zip(groups, tds):
|
|
||||||
td.group_id = group.id
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
url = (
|
|
||||||
f"/api/coordinator/enrollments/{ex.id}/assigned-group-to-term-of-defences/"
|
|
||||||
)
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
term_of_defences = data.get("term_of_defences")
|
|
||||||
assert len(term_of_defences) == amount
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_of_assigned_group_to_term_of_defences_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
url = "/api/coordinator/enrollments/43/assigned-group-to-term-of-defences/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
None,
|
|
||||||
"Not found examination schedule!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_new_group_to_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
group = create_one_model(GroupFactory)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": group.id},
|
|
||||||
"Group was added to term of defence!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_new_group_to_term_of_defence_if_examination_schedule_or_term_of_defence_dont_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
url = "/api/coordinator/enrollments/34/term-of-defence/4/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": 2},
|
|
||||||
"Not found examination schedule or term of defence!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_new_group_to_term_of_defence_if_group_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": 1},
|
|
||||||
"Not found group!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_new_group_to_term_of_defence_if_group_has_already_assigned_to_term_of_defence(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
group = create_one_model(GroupFactory)
|
|
||||||
create_one_model(
|
|
||||||
TermOfDefenceFactory, examination_schedule_id=ex.id, group_id=group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": group.id},
|
|
||||||
"Group has already assigned to term of defence!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
assert td.group_id is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_group_to_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
group = create_one_model(GroupFactory)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": group.id},
|
|
||||||
"Group was deleted from this term of defence!",
|
|
||||||
200,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert td.group_id is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_group_to_term_of_defence_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
url = "/api/coordinator/enrollments/3/term-of-defence/1/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
None,
|
|
||||||
"Not found examination schedule or term of defence!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_group_for_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
group2 = create_one_model(GroupFactory)
|
|
||||||
td = create_one_model(
|
|
||||||
TermOfDefenceFactory, examination_schedule_id=ex.id, group_id=group2.id
|
|
||||||
)
|
|
||||||
group = create_one_model(GroupFactory)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": group.id},
|
|
||||||
"Group for term of defence was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert td.group_id == group.id
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_group_for_term_of_defence_if_examination_schedule_or_term_of_defence_dont_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
url = "/api/coordinator/enrollments/34/term-of-defence/4/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": 2},
|
|
||||||
"Not found examination schedule or term of defence!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_group_for_term_of_defence_if_group_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": 1},
|
|
||||||
"Not found group!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_group_for_term_of_defence_of_defence_if_(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
group = create_one_model(GroupFactory)
|
|
||||||
create_one_model(
|
|
||||||
TermOfDefenceFactory, examination_schedule_id=ex.id, group_id=group.id
|
|
||||||
)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/enrollments/{ex.id}/term-of-defence/{td.id}/group/"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
{"group_id": group.id},
|
|
||||||
"Group has already assigned to term of defence!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
assert td.group_id is None
|
|
@ -1,191 +0,0 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
from app.base.mode import EnrollmentsMode
|
|
||||||
|
|
||||||
from ...factory import ExaminationScheduleFactory, YearGroupFactory
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
ex_data = {
|
|
||||||
"title": "new title",
|
|
||||||
"start_date": (datetime.datetime.now() + datetime.timedelta(days=5)).strftime(
|
|
||||||
"%Y-%m-%dT%H:%M:%S.000Z"
|
|
||||||
),
|
|
||||||
"end_date": (datetime.datetime.now() + datetime.timedelta(days=10)).strftime(
|
|
||||||
"%Y-%m-%dT%H:%M:%S.000Z"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_examination_schedules(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
create_many_models(34, ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
|
|
||||||
url = f"/api/coordinator/examination_schedule/{year_group.id}/?per_page=10"
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
assert data.get("max_pages") == 4
|
|
||||||
assert len(data.get("examination_schedules")) == 10
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_examination_schedule(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/examination_schedule/{ex.id}/",
|
|
||||||
None,
|
|
||||||
"Examination schedule was deleted!",
|
|
||||||
200,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_examination_schedule_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/examination_schedule/32/",
|
|
||||||
None,
|
|
||||||
"Examination schedule doesn't exist!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_examination_schedule(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/examination_schedule/{ex.id}/",
|
|
||||||
ex_data,
|
|
||||||
"Examination schedule was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert_model_changes(ex, ex_data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_examination_schedule_schedule_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/examination_schedule/32/",
|
|
||||||
ex_data,
|
|
||||||
"Examination schedule doesn't exist!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_examination_schedule(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/examination_schedule/{year_group.id}/",
|
|
||||||
ex_data,
|
|
||||||
"Examination schedule was created!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_examination_schedule_if_year_group_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/examination_schedule/33/",
|
|
||||||
ex_data,
|
|
||||||
"Year group doesn't exist!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_examination_schedule_with_invalid_dates(test_app_with_context) -> None:
|
|
||||||
invalid_data = {
|
|
||||||
"title": "examination schedule winter",
|
|
||||||
"start_date": ex_data["end_date"],
|
|
||||||
"end_date": ex_data["start_date"],
|
|
||||||
}
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/examination_schedule/{year_group.id}/",
|
|
||||||
invalid_data,
|
|
||||||
"Invalid data! End date must be greater than start date!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_examination_schedule_with_invalid_data(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
invalid_data = {
|
|
||||||
"title": "examination schedule winter",
|
|
||||||
"start_date": ex_data["end_date"],
|
|
||||||
"end_date": "30.03.2032 12:23.03.",
|
|
||||||
}
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
response_data = _test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/examination_schedule/33/",
|
|
||||||
invalid_data,
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
assert response_data["detail"]["json"]["end_date"][0] == "Not a valid datetime."
|
|
||||||
|
|
||||||
|
|
||||||
def test_open_enrollments(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
assert ex.open_enrollments == EnrollmentsMode.INIT.value
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/examination_schedule/{ex.id}/open-enrollments/",
|
|
||||||
None,
|
|
||||||
"You open enrollments for this examination schedule!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert ex.open_enrollments == EnrollmentsMode.OPEN.value
|
|
||||||
|
|
||||||
|
|
||||||
def test_close_enrollments(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
assert ex.open_enrollments == EnrollmentsMode.INIT.value
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/examination_schedule/{ex.id}/close-enrollments/",
|
|
||||||
None,
|
|
||||||
"You close enrollments for this examination schedule!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert ex.open_enrollments == EnrollmentsMode.CLOSE.value
|
|
@ -1,286 +0,0 @@
|
|||||||
import copy
|
|
||||||
|
|
||||||
from app.dependencies import db
|
|
||||||
from app.students.models import Group
|
|
||||||
|
|
||||||
from ...factory import (
|
|
||||||
GroupFactory,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
StudentFactory,
|
|
||||||
YearGroupFactory,
|
|
||||||
)
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
_test_case_group,
|
|
||||||
assert_model_changes,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_data = {"name": "Mobile app"}
|
|
||||||
|
|
||||||
invalid_data = {
|
|
||||||
"name": "Mobile app v2",
|
|
||||||
"students": [283, 12234],
|
|
||||||
"project_supervisor_id": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_groups(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
create_many_models(33, GroupFactory, year_group_id=yg.id)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{yg.id}/?per_page=10",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert data.get("max_pages") == 4
|
|
||||||
assert len(data.get("groups")) == 10
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_group(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{group.id}/detail/",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert_model_changes(group, data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_group_if_group_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/groups/11/detail/",
|
|
||||||
None,
|
|
||||||
"Not found group!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_group(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{group.id}/",
|
|
||||||
None,
|
|
||||||
"Group was deleted!",
|
|
||||||
202,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_group_if_group_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/groups/32/",
|
|
||||||
None,
|
|
||||||
"Not found group!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_group(test_app_with_context) -> None:
|
|
||||||
data = copy.copy(new_data)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
students = create_many_models(3, StudentFactory, year_group_id=yg.id)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=yg.id)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
data["students"] = [student.id for student in students]
|
|
||||||
data["project_supervisor_id"] = ps.id
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{group.id}/",
|
|
||||||
data,
|
|
||||||
"Group was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
_test_case_group(group, data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_group_with_invalid_project_supervisor_id(test_app_with_context) -> None:
|
|
||||||
data = copy.copy(new_data)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
students = create_many_models(3, StudentFactory, year_group_id=yg.id)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
data["students"] = [student.id for student in students]
|
|
||||||
data["project_supervisor_id"] = 10
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{group.id}/",
|
|
||||||
data,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_group_with_invalid_data(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
data = {"students": ["23", "hello", 3]}
|
|
||||||
response_data = _test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/3/",
|
|
||||||
data,
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
response_data["detail"]["json"]["students"]["1"][0]
|
|
||||||
== "Not a valid integer."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_group_with_invalid_student_ids(test_app_with_context) -> None:
|
|
||||||
data = copy.deepcopy(new_data)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
data["students"] = [2, 6, 4]
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{group.id}/",
|
|
||||||
data,
|
|
||||||
"Not found students!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_group_if_group_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/groups/333/",
|
|
||||||
new_data,
|
|
||||||
"Not found group!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_group_if_you_pass_empty_data(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/groups/333/",
|
|
||||||
{},
|
|
||||||
"You have passed empty data!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_group(test_app_with_context) -> None:
|
|
||||||
data = copy.deepcopy(new_data)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
students = create_many_models(3, StudentFactory)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory)
|
|
||||||
data["students"] = [student.id for student in students]
|
|
||||||
data["project_supervisor_id"] = ps.id
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{yg.id}/",
|
|
||||||
data,
|
|
||||||
"Group was created!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
assert Group.query.count() == 1
|
|
||||||
_test_case_group(Group.query.first(), data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_group_if_year_group_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/groups/22/",
|
|
||||||
invalid_data,
|
|
||||||
"Not found year group!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_group_if_project_supervisor_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{yg.id}/",
|
|
||||||
invalid_data,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_group_if_students_dont_exist(test_app_with_context) -> None:
|
|
||||||
data = copy.deepcopy(invalid_data)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=yg.id)
|
|
||||||
data["project_supervisor_id"] = ps.id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{yg.id}/",
|
|
||||||
data,
|
|
||||||
"Not found students!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_group_if_at_least_one_student_belong_to_other_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
data = copy.deepcopy(invalid_data)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=yg.id)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
data["project_supervisor_id"] = ps.id
|
|
||||||
student = create_one_model(StudentFactory, year_group_id=yg.id)
|
|
||||||
group.students.append(student)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
data["students"].extend([student.id])
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/groups/{yg.id}/",
|
|
||||||
data,
|
|
||||||
"One or more students have already belonged to group!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,230 +0,0 @@
|
|||||||
from app.project_supervisor.models import ProjectSupervisor
|
|
||||||
from app.students.models import Group
|
|
||||||
|
|
||||||
from ...factory import ProjectSupervisorFactory, YearGroupFactory
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
valid_data = {
|
|
||||||
"first_name": "John",
|
|
||||||
"last_name": "Smith",
|
|
||||||
"email": "johnsmith@gmail.com",
|
|
||||||
"limit_group": 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_project_supervisors(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
create_many_models(12, ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{year_group.id}/?per_page=10",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert data.get("max_pages") == 2
|
|
||||||
assert len(data.get("project_supervisors")) == 10
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_project_supervisors(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{yg.id}/",
|
|
||||||
valid_data,
|
|
||||||
"Project Supervisor was created!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
assert_model_changes(ProjectSupervisor.query.first(), valid_data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_project_supervisors_with_invalid_data(test_app_with_context) -> None:
|
|
||||||
data = {
|
|
||||||
"first_name": "John",
|
|
||||||
"last_name": "Smith",
|
|
||||||
"email": "johnsmitl.com",
|
|
||||||
"limit_group": 3,
|
|
||||||
}
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
message = _test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/project_supervisor/3/",
|
|
||||||
data,
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
assert message["detail"]["json"]["email"][0] == "Not a valid email address."
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_project_supervisors_if_project_supervisor_has_already_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
create_one_model(ProjectSupervisor, valid_data, year_group_id=yg.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{yg.id}/",
|
|
||||||
valid_data,
|
|
||||||
"Project Supervisor has already exists!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_project_supervisors_if_year_group_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/project_supervisor/22/",
|
|
||||||
valid_data,
|
|
||||||
"Not found year group!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_supervisor(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{ps.id}/detail/",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert_model_changes(ps, data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_supervisor_if_project_supervisor_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/project_supervisor/23/detail/",
|
|
||||||
None,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_project_supervisor(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{ps.id}/",
|
|
||||||
None,
|
|
||||||
"Project Supervisor was deleted!",
|
|
||||||
200,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_project_supervisor_if_project_supervisor_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/project_supervisor/23/",
|
|
||||||
None,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_project_supervisor_if_project_supervisor_has_got_at_least_one_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory)
|
|
||||||
create_one_model(Group, {"name": "new project"}, project_supervisor_id=ps.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{ps.id}/",
|
|
||||||
None,
|
|
||||||
"Project Supervisor has at least one group!",
|
|
||||||
400,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_project_supervisor(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{ps.id}/",
|
|
||||||
valid_data,
|
|
||||||
"Project Supervisor was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert_model_changes(ps, valid_data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_project_supervisor_if_you_passed_empty_data(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/project_supervisor/32/",
|
|
||||||
{},
|
|
||||||
"You have passed empty data!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_project_supervisor_with_invalid_data(test_app_with_context) -> None:
|
|
||||||
invalid_data = {"first_name": "Mark", "last_name": "Smith", "email": "invalidemail"}
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory)
|
|
||||||
data = _test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/project_supervisor/{ps.id}/",
|
|
||||||
invalid_data,
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
assert data["detail"]["json"]["email"][0] == "Not a valid email address."
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_project_supervisor_if_project_supervisor_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/project_supervisor/2332/",
|
|
||||||
valid_data,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,194 +0,0 @@
|
|||||||
import copy
|
|
||||||
|
|
||||||
from app.students.models import Student, YearGroup
|
|
||||||
|
|
||||||
from ...factory import StudentFactory, YearGroupFactory
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_data = {"first_name": "Martin", "last_name": "Green"}
|
|
||||||
|
|
||||||
data_to_create_student = {
|
|
||||||
"first_name": "Albert",
|
|
||||||
"last_name": "Marcus",
|
|
||||||
"index": 123_456,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_students(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
create_many_models(45, StudentFactory, year_group_id=yg.id)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/students/{yg.id}/?per_page=10",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert data.get("max_pages") == 5
|
|
||||||
assert len(data.get("students")) == 10
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_student(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
st = create_one_model(StudentFactory)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/students/{st.id}/detail/",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert_model_changes(st, data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_student_if_student_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/43/detail/",
|
|
||||||
None,
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_student(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
st = create_one_model(StudentFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/students/{st.id}/",
|
|
||||||
None,
|
|
||||||
"Student was deleted!",
|
|
||||||
202,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
assert len(Student.query.all()) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_student_if_student_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/43/",
|
|
||||||
None,
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_student(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
st = create_one_model(StudentFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/students/{st.id}/",
|
|
||||||
new_data,
|
|
||||||
"Student was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_student_with_invalid_data(test_app_with_context) -> None:
|
|
||||||
data = copy.copy(new_data)
|
|
||||||
data["index"] = 3_232_323
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
st = create_one_model(StudentFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/students/{st.id}/",
|
|
||||||
data,
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_edit_student_if_student_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/54/",
|
|
||||||
new_data,
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_student(test_app_with_context) -> None:
|
|
||||||
data = copy.copy(data_to_create_student)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
data["year_group_id"] = yg.id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/",
|
|
||||||
data,
|
|
||||||
"Student was created!",
|
|
||||||
200,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_student_with_invalid_data(test_app_with_context) -> None:
|
|
||||||
data = copy.copy(data_to_create_student)
|
|
||||||
data["index"] = 1_123_432
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
data["year_group_id"] = yg.id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/",
|
|
||||||
data,
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_student_if_year_group_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
data = copy.copy(data_to_create_student)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
data["year_group_id"] = 34
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/",
|
|
||||||
data,
|
|
||||||
"Not found year group!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_student_if_student_has_already_assigned_to_this_year_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
data = copy.copy(data_to_create_student)
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
create_one_model(Student, data, year_group_id=yg.id, email="tmp@gmail.com")
|
|
||||||
data["year_group_id"] = yg.id
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/students/",
|
|
||||||
data,
|
|
||||||
"Student has already assigned to this year group!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,162 +0,0 @@
|
|||||||
from app.base.mode import ModeGroups
|
|
||||||
from app.students.models import YearGroup
|
|
||||||
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
year_group_valid_data_1 = {"mode": ModeGroups.STATIONARY.value, "name": "2022/2023"}
|
|
||||||
|
|
||||||
year_group_valid_data_2 = {"mode": ModeGroups.NON_STATIONARY.value, "name": "2021/2022"}
|
|
||||||
|
|
||||||
year_group_valid_data_3 = {"mode": ModeGroups.STATIONARY.value, "name": "2021/2022"}
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_year_group(test_client) -> None:
|
|
||||||
_test_case_client(
|
|
||||||
test_client,
|
|
||||||
"/api/coordinator/year-group/",
|
|
||||||
year_group_valid_data_1,
|
|
||||||
"Year group was created!",
|
|
||||||
201,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_year_group_with_invalid_name(test_client) -> None:
|
|
||||||
data = {"mode": ModeGroups.STATIONARY.value, "name": "2022/203232a"}
|
|
||||||
_test_case_client(
|
|
||||||
test_client, "/api/coordinator/year-group/", data, "Validation error", 400
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_year_group_with_invalid_mode(test_client) -> None:
|
|
||||||
data = {"mode": "xxxx", "name": "2022/2033"}
|
|
||||||
_test_case_client(
|
|
||||||
test_client, "/api/coordinator/year-group/", data, "Validation error", 400
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_year_group_if_year_group_already_exists(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
create_one_model(YearGroup, year_group_valid_data_1)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/year-group/",
|
|
||||||
year_group_valid_data_1,
|
|
||||||
"Year group has already exists!",
|
|
||||||
400,
|
|
||||||
"error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_year_group(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroup, year_group_valid_data_1)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/year-group/{yg.id}/",
|
|
||||||
None,
|
|
||||||
"Year group was deleted!",
|
|
||||||
202,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_year_group_if_year_group_not_exists(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/coordinator/year-group/1/",
|
|
||||||
None,
|
|
||||||
"Year group doesn't exist!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_year_group(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroup, year_group_valid_data_1)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/year-group/{yg.id}/",
|
|
||||||
year_group_valid_data_2,
|
|
||||||
"Year group was updated!",
|
|
||||||
200,
|
|
||||||
method="put",
|
|
||||||
key="message",
|
|
||||||
)
|
|
||||||
assert_model_changes(yg, year_group_valid_data_2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_year_group_with_invalid_data(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroup, year_group_valid_data_1)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/year-group/{yg.id}/",
|
|
||||||
{"name": "", "mode": ""},
|
|
||||||
"Validation error",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="message",
|
|
||||||
)
|
|
||||||
assert_model_changes(yg, year_group_valid_data_1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_year_group_with_invalid_route_param_year_group_id(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/year-group/23/",
|
|
||||||
year_group_valid_data_2,
|
|
||||||
"Not found year group!",
|
|
||||||
404,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_year_group_with_valid_data_and_year_group_which_has_already_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
create_one_model(YearGroup, year_group_valid_data_2)
|
|
||||||
yg = create_one_model(YearGroup, year_group_valid_data_1)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/coordinator/year-group/{yg.id}/",
|
|
||||||
year_group_valid_data_2,
|
|
||||||
"Year group has already exists!",
|
|
||||||
400,
|
|
||||||
method="put",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
assert_model_changes(yg, year_group_valid_data_1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_year_group(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
ygs = [
|
|
||||||
create_one_model(YearGroup, data)
|
|
||||||
for data in (
|
|
||||||
year_group_valid_data_1,
|
|
||||||
year_group_valid_data_2,
|
|
||||||
year_group_valid_data_3,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client, "/api/coordinator/year-group/", None, 200, method="get"
|
|
||||||
)
|
|
||||||
assert data.get("max_pages") == 1
|
|
||||||
for year_group_data in data.get("year_groups"):
|
|
||||||
yg_id = year_group_data.get("id")
|
|
||||||
assert_model_changes(
|
|
||||||
list(filter(lambda yg: yg.id == yg_id, ygs))[0], year_group_data
|
|
||||||
)
|
|
@ -1,101 +0,0 @@
|
|||||||
from app.base.mode import EnrollmentsMode
|
|
||||||
|
|
||||||
from ...factory import (
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
TemporaryAvailabilityFactory,
|
|
||||||
YearGroupFactory,
|
|
||||||
)
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_enrollments_for_project_supervisor(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
ps2 = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
create_many_models(
|
|
||||||
7,
|
|
||||||
TemporaryAvailabilityFactory,
|
|
||||||
examination_schedule_id=ex.id,
|
|
||||||
project_supervisor_id=ps2.id,
|
|
||||||
)
|
|
||||||
amount = 5
|
|
||||||
create_many_models(
|
|
||||||
amount,
|
|
||||||
TemporaryAvailabilityFactory,
|
|
||||||
examination_schedule_id=ex.id,
|
|
||||||
project_supervisor_id=ps.id,
|
|
||||||
)
|
|
||||||
url = f"/api/project_supervisor/{ex.id}/temporary-availabilities/?id={ps.id}"
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
assert len(data.get("free_times")) == amount
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_enrollments_for_project_supervisor_if_examination_schedule_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
url = f"/api/project_supervisor/3/temporary-availabilities/?id={ps.id}"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
None,
|
|
||||||
"Not found examination schedule!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_enrollments_for_project_supervisor_if_project_supervisor_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
url = f"/api/project_supervisor/3/temporary-availabilities/?id=3"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
None,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_enrollments_for_project_supervisor_if_enrollments_is_not_open_yet(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
create_many_models(
|
|
||||||
7,
|
|
||||||
TemporaryAvailabilityFactory,
|
|
||||||
examination_schedule_id=ex.id,
|
|
||||||
project_supervisor_id=ps.id,
|
|
||||||
)
|
|
||||||
url = f"/api/project_supervisor/{ex.id}/temporary-availabilities/?id={ps.id}"
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
url,
|
|
||||||
None,
|
|
||||||
"Enrollments has started or closed! You have been delayed!",
|
|
||||||
400,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,229 +0,0 @@
|
|||||||
from app.dependencies import db
|
|
||||||
|
|
||||||
from ...factory import (
|
|
||||||
GroupFactory,
|
|
||||||
ProjectGradeSheetFactory,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
YearGroupFactory,
|
|
||||||
)
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
terms = [1, 2]
|
|
||||||
terms_uri = ["first-term", "second-term"]
|
|
||||||
|
|
||||||
data_1 = {
|
|
||||||
"documentation_for_clients_1": 0,
|
|
||||||
"documentation_for_developers_1": 4,
|
|
||||||
"documentation_license_1": 4,
|
|
||||||
"documentation_project_vision_1": 4,
|
|
||||||
"documentation_requirements_1": 3,
|
|
||||||
"group_work_contact_with_client_1": 3,
|
|
||||||
"group_work_devops_1": 0,
|
|
||||||
"group_work_division_of_work_1": 4,
|
|
||||||
"group_work_management_of_risk_1": 4,
|
|
||||||
"group_work_management_of_source_code_1": 4,
|
|
||||||
"group_work_regularity_1": 4,
|
|
||||||
"group_work_work_methodology_1": 4,
|
|
||||||
"presentation_answers_to_questions_from_committee_1": 4,
|
|
||||||
"presentation_required_content_1": 4,
|
|
||||||
"presentation_showing_1": 4,
|
|
||||||
"presentation_was_compatible_1": 3,
|
|
||||||
"products_project_acceptance_criteria_1": 4,
|
|
||||||
"products_project_access_to_application_1": 0,
|
|
||||||
"products_project_access_to_test_application_1": 0,
|
|
||||||
"products_project_complexity_of_product_1": 4,
|
|
||||||
"products_project_expected_functionality_1": 4,
|
|
||||||
"products_project_has_been_implemented_1": 0,
|
|
||||||
"products_project_is_useful_1": 4,
|
|
||||||
"products_project_promises_well_1": 4,
|
|
||||||
"products_project_prototype_1": 3,
|
|
||||||
"products_project_security_issues_1": 0,
|
|
||||||
"products_project_technology_1": 4,
|
|
||||||
"products_project_tests_1": 0,
|
|
||||||
}
|
|
||||||
data_2 = {
|
|
||||||
"documentation_for_clients_2": 3,
|
|
||||||
"documentation_for_developers_2": 4,
|
|
||||||
"documentation_license_2": 4,
|
|
||||||
"documentation_project_vision_2": 0,
|
|
||||||
"documentation_requirements_2": 4,
|
|
||||||
"group_work_contact_with_client_2": 3,
|
|
||||||
"group_work_devops_2": 4,
|
|
||||||
"group_work_division_of_work_2": 4,
|
|
||||||
"group_work_management_of_risk_2": 4,
|
|
||||||
"group_work_management_of_source_code_2": 4,
|
|
||||||
"group_work_regularity_2": 4,
|
|
||||||
"group_work_work_methodology_2": 4,
|
|
||||||
"presentation_answers_to_questions_from_committee_2": 0,
|
|
||||||
"presentation_required_content_2": 0,
|
|
||||||
"presentation_showing_2": 0,
|
|
||||||
"presentation_was_compatible_2": 0,
|
|
||||||
"products_project_acceptance_criteria_2": 3,
|
|
||||||
"products_project_access_to_application_2": 0,
|
|
||||||
"products_project_access_to_test_application_2": 0,
|
|
||||||
"products_project_complexity_of_product_2": 4,
|
|
||||||
"products_project_expected_functionality_2": 4,
|
|
||||||
"products_project_has_been_implemented_2": 3,
|
|
||||||
"products_project_is_useful_2": 3,
|
|
||||||
"products_project_promises_well_2": 0,
|
|
||||||
"products_project_prototype_2": 0,
|
|
||||||
"products_project_security_issues_2": 0,
|
|
||||||
"products_project_technology_2": 4,
|
|
||||||
"products_project_tests_2": 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
project_supervisor = create_one_model(
|
|
||||||
ProjectSupervisorFactory, year_group_id=yg.id
|
|
||||||
)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory,
|
|
||||||
year_group_id=yg.id,
|
|
||||||
project_supervisor_id=project_supervisor.id,
|
|
||||||
)
|
|
||||||
pgs = create_one_model(ProjectGradeSheetFactory, group_id=group.id)
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/{group.id}/?term={term}&id={project_supervisor.id}",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert_model_changes(pgs, data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet_if_project_supervisor_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/3/?term={term}&id=2",
|
|
||||||
None,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet_if_you_try_get_not_your_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
project_supervisor = create_one_model(
|
|
||||||
ProjectSupervisorFactory, year_group_id=yg.id
|
|
||||||
)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/3/?term={term}&id={project_supervisor.id}",
|
|
||||||
None,
|
|
||||||
"Group doesn't exist or you are not project supervisor of that group!",
|
|
||||||
400,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_project_grade_sheet_for_both_term(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term, data, attr_name, expected_points in zip(
|
|
||||||
terms_uri,
|
|
||||||
[data_1, data_2],
|
|
||||||
["points_for_first_term", "points_for_second_term"],
|
|
||||||
[93, 68.1],
|
|
||||||
):
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
project_supervisor = create_one_model(
|
|
||||||
ProjectSupervisorFactory, year_group_id=yg.id
|
|
||||||
)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory,
|
|
||||||
year_group_id=yg.id,
|
|
||||||
project_supervisor_id=project_supervisor.id,
|
|
||||||
)
|
|
||||||
pgs = create_one_model(ProjectGradeSheetFactory, group_id=group.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/{group.id}/{term}/?id={project_supervisor.id}",
|
|
||||||
data,
|
|
||||||
"Your project grade sheet was updated!",
|
|
||||||
200,
|
|
||||||
method="patch",
|
|
||||||
)
|
|
||||||
assert_model_changes(pgs, data)
|
|
||||||
assert getattr(group, attr_name) == expected_points
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_project_grade_sheet_for_both_term_if_project_grade_sheet_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term, data in zip(terms_uri, [data_1, data_2]):
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
project_supervisor = create_one_model(
|
|
||||||
ProjectSupervisorFactory, year_group_id=yg.id
|
|
||||||
)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory,
|
|
||||||
year_group_id=yg.id,
|
|
||||||
project_supervisor_id=project_supervisor.id,
|
|
||||||
)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/{group.id}/{term}/?id={project_supervisor.id}",
|
|
||||||
data,
|
|
||||||
"Not found project grade sheet!",
|
|
||||||
404,
|
|
||||||
method="patch",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_project_grade_sheet_for_both_term_if_try_update_project_grade_sheet_for_not_your_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term, data in zip(terms_uri, [data_1, data_2]):
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
project_supervisor = create_one_model(
|
|
||||||
ProjectSupervisorFactory, year_group_id=yg.id
|
|
||||||
)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/{group.id}/{term}/?id={project_supervisor.id}",
|
|
||||||
data,
|
|
||||||
"You cannot update project grade sheet! It's not your group!",
|
|
||||||
400,
|
|
||||||
method="patch",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_project_grade_sheet_for_both_term_if_project_supervisor_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term, data in zip(terms_uri, [data_1, data_2]):
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/project_supervisor/project-grade-sheet/group/3/{term}/?id=3",
|
|
||||||
data,
|
|
||||||
"Not found project supervisor!",
|
|
||||||
404,
|
|
||||||
method="patch",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,342 +0,0 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
from app.base.mode import EnrollmentsMode
|
|
||||||
from app.dependencies import db
|
|
||||||
from app.examination_schedule.models import ExaminationSchedule, TermOfDefence
|
|
||||||
|
|
||||||
from ...factory import (
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
GroupFactory,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
StudentFactory,
|
|
||||||
TermOfDefenceFactory,
|
|
||||||
YearGroupFactory,
|
|
||||||
)
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
get_data_of_term_of_defence,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_examination_schedule(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
create_many_models(23, ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
url = f"/api/students/examination-schedule/year-group/{year_group.id}/?student_id={st.id}"
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
assert len(data.get("examination_schedules")) == 23
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_examination_schedule_if_student_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/examination-schedule/year-group/{yg.id}/?student_id=2",
|
|
||||||
None,
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_term_of_defences(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
project_supervisors = create_many_models(
|
|
||||||
13, ProjectSupervisorFactory, year_group_id=year_group.id
|
|
||||||
)
|
|
||||||
ps = project_supervisors[0]
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory, year_group_id=year_group.id, project_supervisor_id=ps.id
|
|
||||||
)
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
term_of_defences = create_many_models(
|
|
||||||
13, TermOfDefenceFactory, examination_schedule_id=ex.id
|
|
||||||
)
|
|
||||||
ps_amount = 6
|
|
||||||
for i in range(ps_amount):
|
|
||||||
term_of_defences[i].members_of_committee.append(ps)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
url = f"/api/students/examination-schedule/{ex.id}/enrollments/?student_id={st.id}"
|
|
||||||
data = _test_case_client_without_response(client, url, None, 200, method="get")
|
|
||||||
assert len(data.get("term_of_defences")) == ps_amount
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_term_of_defences_if_student_doesnt_exist(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/students/examination-schedule/3/enrollments/?student_id=2",
|
|
||||||
None,
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory, year_group_id=year_group.id, project_supervisor_id=ps.id
|
|
||||||
)
|
|
||||||
td.members_of_committee.append(ps)
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"You have just assigned the group for this exam date!",
|
|
||||||
201,
|
|
||||||
method="post",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_student_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/students/3/enrollments/1/",
|
|
||||||
{"student_id": 2},
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_term_of_defence_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
st = create_one_model(StudentFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/students/3/enrollments/1/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"Term of defence not found!",
|
|
||||||
404,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_enrollments_havent_started_yet(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(ExaminationScheduleFactory, year_group_id=year_group.id)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"Enrollments is closed! You have been delayed!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_you_dont_have_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"You don't have a group or your group doesn't have an assigned project supervisor!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_your_group_has_already_assigned_to_any_term_of_defence(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory, year_group_id=year_group.id, project_supervisor_id=ps.id
|
|
||||||
)
|
|
||||||
td.group_id = group.id
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"Your group has already assigned to any exam date!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_your_project_supervisor_is_not_in_committee(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory, year_group_id=year_group.id, project_supervisor_id=ps.id
|
|
||||||
)
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"Your project supervisor is not in committee!",
|
|
||||||
400,
|
|
||||||
method="post",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_your_group_from_term_of_defence(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory, year_group_id=year_group.id, project_supervisor_id=ps.id
|
|
||||||
)
|
|
||||||
td = create_one_model(
|
|
||||||
TermOfDefenceFactory, examination_schedule_id=ex.id, group_id=group.id
|
|
||||||
)
|
|
||||||
td.members_of_committee.append(ps)
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"You have just removed the group for this exam date!",
|
|
||||||
200,
|
|
||||||
method="delete",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_your_group_from_term_of_defence_if_student_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/students/3/enrollments/1/",
|
|
||||||
{"student_id": 2},
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_your_group_from_term_of_defence_if_term_of_defence_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
st = create_one_model(StudentFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
"/api/students/3/enrollments/1/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"Not found term of defence!",
|
|
||||||
404,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_assign_your_group_to_term_of_defence_if_you_try_delete_not_your_group(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
year_group = create_one_model(YearGroupFactory)
|
|
||||||
ex = create_one_model(
|
|
||||||
ExaminationScheduleFactory,
|
|
||||||
year_group_id=year_group.id,
|
|
||||||
open_enrollments=EnrollmentsMode.OPEN.value,
|
|
||||||
)
|
|
||||||
td = create_one_model(TermOfDefenceFactory, examination_schedule_id=ex.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=year_group.id)
|
|
||||||
ps = create_one_model(ProjectSupervisorFactory, year_group_id=year_group.id)
|
|
||||||
group = create_one_model(
|
|
||||||
GroupFactory, year_group_id=year_group.id, project_supervisor_id=ps.id
|
|
||||||
)
|
|
||||||
td.group_id = group.id
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/{ex.id}/enrollments/{td.id}/",
|
|
||||||
{"student_id": st.id},
|
|
||||||
"You are not assigned to this group!",
|
|
||||||
400,
|
|
||||||
method="delete",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,91 +0,0 @@
|
|||||||
from app.dependencies import db
|
|
||||||
|
|
||||||
from ...factory import (
|
|
||||||
GroupFactory,
|
|
||||||
ProjectGradeSheetFactory,
|
|
||||||
StudentFactory,
|
|
||||||
YearGroupFactory,
|
|
||||||
)
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client,
|
|
||||||
_test_case_client_without_response,
|
|
||||||
assert_model_changes,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
terms = [1, 2]
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=yg.id)
|
|
||||||
pgs = create_one_model(ProjectGradeSheetFactory, group_id=group.id)
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/students/project-grade-sheet/year-group/{yg.id}/?student_id={st.id}&term={term}",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
assert_model_changes(pgs, data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet_if_student_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/project-grade-sheet/year-group/{yg.id}/?student_id=3&term={term}",
|
|
||||||
None,
|
|
||||||
"Not found student!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet_if_group_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=yg.id)
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/project-grade-sheet/year-group/{yg.id}/?student_id={st.id}&term={term}",
|
|
||||||
None,
|
|
||||||
"Not found group!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_detail_project_grade_sheet_if_project_grade_sheet_doesnt_exist(
|
|
||||||
test_app_with_context,
|
|
||||||
) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
for term in terms:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
st = create_one_model(StudentFactory, year_group_id=yg.id)
|
|
||||||
group = create_one_model(GroupFactory, year_group_id=yg.id)
|
|
||||||
group.students.append(st)
|
|
||||||
db.session.commit()
|
|
||||||
_test_case_client(
|
|
||||||
client,
|
|
||||||
f"/api/students/project-grade-sheet/year-group/{yg.id}/?student_id={st.id}&term={term}",
|
|
||||||
None,
|
|
||||||
"Not found project grade sheet!",
|
|
||||||
404,
|
|
||||||
method="get",
|
|
||||||
key="error",
|
|
||||||
)
|
|
@ -1,55 +0,0 @@
|
|||||||
from app.dependencies import db
|
|
||||||
from app.students.models import Group
|
|
||||||
|
|
||||||
from ...factory import GroupFactory, ProjectSupervisorFactory, YearGroupFactory
|
|
||||||
from ...utils import (
|
|
||||||
_test_case_client_without_response,
|
|
||||||
create_many_models,
|
|
||||||
create_one_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_list_available_groups(test_app_with_context) -> None:
|
|
||||||
with test_app_with_context.test_client() as client:
|
|
||||||
yg = create_one_model(YearGroupFactory)
|
|
||||||
amount = 6
|
|
||||||
limit_group_1 = 4
|
|
||||||
limit_group_2 = 2
|
|
||||||
groups = create_many_models(amount, GroupFactory, year_group_id=yg.id)
|
|
||||||
ps1 = create_many_models(
|
|
||||||
amount // 2,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
year_group_id=yg.id,
|
|
||||||
limit_group=limit_group_1,
|
|
||||||
)
|
|
||||||
ps2 = create_many_models(
|
|
||||||
amount // 2,
|
|
||||||
ProjectSupervisorFactory,
|
|
||||||
year_group_id=yg.id,
|
|
||||||
limit_group=limit_group_2,
|
|
||||||
)
|
|
||||||
for g, ps in zip(groups, [*ps1, *ps2]):
|
|
||||||
g.project_supervisor_id = ps.id
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
data = _test_case_client_without_response(
|
|
||||||
client,
|
|
||||||
f"/api/students/registrations/{yg.id}/?per_page=10",
|
|
||||||
None,
|
|
||||||
200,
|
|
||||||
method="get",
|
|
||||||
)
|
|
||||||
project_supervisors = [*ps1, *ps2]
|
|
||||||
project_supervisors_data = data.get("project_supervisors")
|
|
||||||
assert data.get("max_pages") == 1
|
|
||||||
assert len(project_supervisors_data) == amount
|
|
||||||
for ps_data in project_supervisors_data:
|
|
||||||
for ps_obj in project_supervisors:
|
|
||||||
if ps_data["email"] == ps_obj.email:
|
|
||||||
assert ps_data["available_groups"] == (
|
|
||||||
ps_obj.limit_group
|
|
||||||
- Group.query.filter(
|
|
||||||
Group.project_supervisor_id == ps_obj.id
|
|
||||||
).count()
|
|
||||||
)
|
|
||||||
continue
|
|
2
backend/tests/test_example.py
Normal file
2
backend/tests/test_example.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
def test_comparing_two_number():
|
||||||
|
assert 1 == 1
|
@ -1,199 +0,0 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
import pytest
|
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from app.base.mode import ModeGroups
|
|
||||||
from app.base.utils import is_allowed_extensions, order_by_column_name, paginate_models
|
|
||||||
from app.coordinator.exceptions import InvalidNameOrTypeHeaderException
|
|
||||||
from app.coordinator.utils import (
|
|
||||||
check_columns,
|
|
||||||
generate_csv,
|
|
||||||
generate_range_dates,
|
|
||||||
get_duration_time,
|
|
||||||
map_project_supervisors,
|
|
||||||
parse_csv,
|
|
||||||
)
|
|
||||||
from app.dependencies import db
|
|
||||||
from app.students.models import Group, Student
|
|
||||||
|
|
||||||
|
|
||||||
def test_is_allowed_extensions(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
for ext in current_app.config.get("ALLOWED_EXTENSIONS"):
|
|
||||||
assert is_allowed_extensions(f"file.{ext}") is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_is_allowed_extensions_with_invalid_extensions(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
assert is_allowed_extensions("file.invalid_ext") is False
|
|
||||||
assert is_allowed_extensions("file") is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_order_by_column_name_ascending_mode(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
query = order_by_column_name(Student.query, "index", "desc")
|
|
||||||
assert 'ORDER BY students."index"' in str(query)
|
|
||||||
|
|
||||||
|
|
||||||
def test_order_by_column_name_descending_mode(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
query = order_by_column_name(Student.query, "index", "desc")
|
|
||||||
assert 'ORDER BY students."index" DESC' in str(query)
|
|
||||||
|
|
||||||
|
|
||||||
def test_paginate_models(test_app_ctx_with_db) -> None:
|
|
||||||
with test_app_ctx_with_db:
|
|
||||||
st = Student(
|
|
||||||
index=123456,
|
|
||||||
first_name="Dominic",
|
|
||||||
last_name="Smith",
|
|
||||||
email="xxx@gmail.com",
|
|
||||||
)
|
|
||||||
st1 = Student(
|
|
||||||
index=123457,
|
|
||||||
first_name="John",
|
|
||||||
last_name="Newton",
|
|
||||||
email="zzz@gmail.com",
|
|
||||||
)
|
|
||||||
db.session.add_all([st, st1])
|
|
||||||
db.session.commit()
|
|
||||||
result = paginate_models(1, Student.query, 1)
|
|
||||||
|
|
||||||
items = result.get("items", [])
|
|
||||||
max_pages = result.get("max_pages", 0)
|
|
||||||
|
|
||||||
assert len(items) == 1
|
|
||||||
assert max_pages == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_columns() -> None:
|
|
||||||
dummy_data = {
|
|
||||||
"NAZWISKO": ["Smith"],
|
|
||||||
"IMIE": ["Dominic"],
|
|
||||||
"INDEKS": [343433],
|
|
||||||
"EMAIL": ["domsmi@gmail.com"],
|
|
||||||
}
|
|
||||||
df = pd.DataFrame(data=dummy_data)
|
|
||||||
assert check_columns(df) is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_columns_with_invalid_column_names() -> None:
|
|
||||||
dummy_data = {"col1": [1, 2], "col2": [2, 3]}
|
|
||||||
df = pd.DataFrame(data=dummy_data)
|
|
||||||
assert check_columns(df) is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_columns_with_invalid_column_types() -> None:
|
|
||||||
dummy_data = {
|
|
||||||
"NAZWISKO": [999],
|
|
||||||
"IMIE": ["Dominic"],
|
|
||||||
"INDEKS": [343433],
|
|
||||||
"EMAIL": ["domsmi@gmail.com"],
|
|
||||||
}
|
|
||||||
df = pd.DataFrame(data=dummy_data)
|
|
||||||
assert check_columns(df) is False
|
|
||||||
|
|
||||||
|
|
||||||
def get_path_to_fake_data(filename: str) -> str:
|
|
||||||
base_dir = current_app.config.get("BASE_DIR", "/")
|
|
||||||
return base_dir / "tests" / "data" / filename
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_csv(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
with open(get_path_to_fake_data("students.csv")) as f:
|
|
||||||
students = sorted(list(parse_csv(f, 1)), key=lambda s: s.index)
|
|
||||||
indexes = [452790 + i for i in range(3)]
|
|
||||||
assert len(students) == len(indexes)
|
|
||||||
for st, idx in zip(students, indexes):
|
|
||||||
assert st.index == idx
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_csv_with_invalid_column_header_name_in_csv_file(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
with open(get_path_to_fake_data("students_column_name.csv")) as f:
|
|
||||||
with pytest.raises(InvalidNameOrTypeHeaderException):
|
|
||||||
parse_csv(f, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_csv_with_invalid_column_type_in_csv_file(test_app) -> None:
|
|
||||||
with test_app.app_context():
|
|
||||||
with open(get_path_to_fake_data("students_column_type.csv")) as f:
|
|
||||||
with pytest.raises(InvalidNameOrTypeHeaderException):
|
|
||||||
parse_csv(f, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_generate_range_dates() -> None:
|
|
||||||
start_date = datetime.datetime(2022, 2, 2, 8, 0, 0, 0)
|
|
||||||
end_date = datetime.datetime(2022, 2, 2, 12, 0, 0, 0)
|
|
||||||
step = 30
|
|
||||||
expected_dates_amount = (end_date - start_date).total_seconds() / 60.0 / step
|
|
||||||
dates = list(generate_range_dates(start_date, end_date, step))
|
|
||||||
|
|
||||||
assert expected_dates_amount == len(dates)
|
|
||||||
|
|
||||||
for date in dates:
|
|
||||||
assert start_date <= date < end_date
|
|
||||||
|
|
||||||
|
|
||||||
def test_generate_csv() -> None:
|
|
||||||
students_data = [
|
|
||||||
{
|
|
||||||
"first_name": "Dominic",
|
|
||||||
"last_name": "Smith",
|
|
||||||
"email": "xxe@gmail.com",
|
|
||||||
"index": 123456,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"first_name": "Matthew",
|
|
||||||
"last_name": "Cash",
|
|
||||||
"email": "zze@gmail.com",
|
|
||||||
"index": 123455,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"first_name": "Martin",
|
|
||||||
"last_name": "Rose",
|
|
||||||
"email": "nne@gmail.com",
|
|
||||||
"index": 123446,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
students = [Student(**data) for data in students_data]
|
|
||||||
gr1 = Group(name="new-project")
|
|
||||||
gr2 = Group(name="system-pri")
|
|
||||||
gr1.students.append(students[0])
|
|
||||||
gr1.students.append(students[1])
|
|
||||||
gr2.students.append(students[2])
|
|
||||||
|
|
||||||
students_and_groups = [
|
|
||||||
(students[0], gr1),
|
|
||||||
(students[1], gr1),
|
|
||||||
(students[2], gr2),
|
|
||||||
]
|
|
||||||
generated_csv = generate_csv(students_and_groups)
|
|
||||||
for data in students_data:
|
|
||||||
for value in data.values():
|
|
||||||
assert str(value) in generated_csv
|
|
||||||
|
|
||||||
|
|
||||||
def test_map_project_supervisors() -> None:
|
|
||||||
project_supervisors_id = [(1, 2), (2, 3), (3, 7)]
|
|
||||||
groups = []
|
|
||||||
for i in range(3):
|
|
||||||
for _, ps_id in project_supervisors_id:
|
|
||||||
groups.append(Group(project_supervisor_id=ps_id))
|
|
||||||
|
|
||||||
mapped_ps = map_project_supervisors(
|
|
||||||
sorted(groups, key=lambda g: g.project_supervisor_id)
|
|
||||||
)
|
|
||||||
for expected_id, ps_id in project_supervisors_id:
|
|
||||||
assert mapped_ps[ps_id] == expected_id
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_duration_time() -> None:
|
|
||||||
assert get_duration_time(ModeGroups.STATIONARY.value) == 30
|
|
||||||
assert get_duration_time(ModeGroups.NON_STATIONARY.value) == 20
|
|
||||||
assert get_duration_time(ModeGroups.ENGLISH_SPEAKING_STATIONARY.value) == 30
|
|
||||||
assert get_duration_time("invalid value") is None
|
|
@ -1,32 +0,0 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from marshmallow import ValidationError
|
|
||||||
|
|
||||||
from app.coordinator.validators import (
|
|
||||||
validate_datetime_greater_than_now,
|
|
||||||
validate_index,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_index() -> None:
|
|
||||||
assert validate_index(123456) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_index_with_invalid_value() -> None:
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
validate_index(12345)
|
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
validate_index(1234567)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_datetime_greater_than_now() -> None:
|
|
||||||
d = datetime.datetime.now() + datetime.timedelta(days=2)
|
|
||||||
assert validate_datetime_greater_than_now(d) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_datetime_greater_than_now_with_invalid_data() -> None:
|
|
||||||
d = datetime.datetime.now() - datetime.timedelta(days=2)
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
validate_datetime_greater_than_now(d)
|
|
@ -1,98 +0,0 @@
|
|||||||
import datetime
|
|
||||||
from typing import List, Type, Union
|
|
||||||
|
|
||||||
from factory.alchemy import SQLAlchemyModelFactory
|
|
||||||
from flask.testing import FlaskClient
|
|
||||||
|
|
||||||
from app.dependencies import db
|
|
||||||
from app.examination_schedule.models import ExaminationSchedule
|
|
||||||
from app.students.models import Group
|
|
||||||
|
|
||||||
|
|
||||||
def assert_model_changes(model: db.Model, expected_data: dict) -> None:
|
|
||||||
for key, val in expected_data.items():
|
|
||||||
value = getattr(model, key)
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
value = value.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
||||||
assert value == val
|
|
||||||
|
|
||||||
|
|
||||||
def _test_case_client_without_response(
|
|
||||||
test_client: FlaskClient,
|
|
||||||
url: str,
|
|
||||||
data: Union[dict, None],
|
|
||||||
status_code: int,
|
|
||||||
method: str = "post",
|
|
||||||
) -> dict:
|
|
||||||
method_func = getattr(test_client, method)
|
|
||||||
if data is not None:
|
|
||||||
response = method_func(url, json=data)
|
|
||||||
else:
|
|
||||||
response = method_func(url)
|
|
||||||
# raise Exception(response.status_code, response.json)
|
|
||||||
assert response.status_code == status_code
|
|
||||||
return response.json
|
|
||||||
|
|
||||||
|
|
||||||
def _test_case_client(
|
|
||||||
test_client: FlaskClient,
|
|
||||||
url: str,
|
|
||||||
data: Union[dict, None],
|
|
||||||
message: str,
|
|
||||||
status_code: int,
|
|
||||||
key: str = "message",
|
|
||||||
method: str = "post",
|
|
||||||
) -> dict:
|
|
||||||
response_data = _test_case_client_without_response(
|
|
||||||
test_client, url, data, status_code, method
|
|
||||||
)
|
|
||||||
assert key in response_data.keys()
|
|
||||||
assert response_data.get(key) == message
|
|
||||||
return response_data
|
|
||||||
|
|
||||||
|
|
||||||
def _test_case_group(group: Group, data: dict) -> None:
|
|
||||||
assert group.name == data["name"]
|
|
||||||
assert group.project_supervisor_id == data["project_supervisor_id"]
|
|
||||||
|
|
||||||
for st in group.students:
|
|
||||||
assert st.id in data["students"]
|
|
||||||
|
|
||||||
|
|
||||||
def create_many_models(
|
|
||||||
amount: int, factory: Type[SQLAlchemyModelFactory], **kwargs
|
|
||||||
) -> List[SQLAlchemyModelFactory]:
|
|
||||||
models = [factory(**kwargs) for _ in range(amount)]
|
|
||||||
db.session.add_all(models)
|
|
||||||
db.session.commit()
|
|
||||||
return models
|
|
||||||
|
|
||||||
|
|
||||||
def create_one_model(
|
|
||||||
model: Union[Type[db.Model], Type[SQLAlchemyModelFactory]],
|
|
||||||
data: dict = None,
|
|
||||||
**kwargs
|
|
||||||
) -> Union[db.Model, SQLAlchemyModelFactory]:
|
|
||||||
if issubclass(model, SQLAlchemyModelFactory):
|
|
||||||
m = model(**kwargs) # it's a factory
|
|
||||||
else:
|
|
||||||
m = model(**data, **kwargs)
|
|
||||||
db.session.add(m)
|
|
||||||
db.session.commit()
|
|
||||||
return m
|
|
||||||
|
|
||||||
|
|
||||||
def get_data_of_term_of_defence(timedelta_minutes: int = 30) -> dict:
|
|
||||||
ex = ExaminationSchedule.query.first()
|
|
||||||
if ex is None:
|
|
||||||
date = datetime.datetime.now()
|
|
||||||
else:
|
|
||||||
date = ex.start_date
|
|
||||||
return {
|
|
||||||
"start_date": (date + datetime.timedelta(days=1)).strftime(
|
|
||||||
"%Y-%m-%dT%H:%M:%S.000Z"
|
|
||||||
),
|
|
||||||
"end_date": (
|
|
||||||
date + datetime.timedelta(days=1, minutes=timedelta_minutes)
|
|
||||||
).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user