Compare commits
97 Commits
master
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
2029f6c343 | |||
339ba42fb4 | |||
dba2b24267 | |||
dd333a6674 | |||
|
fe06023f82 | ||
|
f4f9072c3f | ||
1bed24a1bc | |||
be8f065886 | |||
|
61fcda82ab | ||
e0f2711b21 | |||
38a6c6a6be | |||
e7bc1f3dae | |||
5aa8bfee45 | |||
|
191c68acf4 | ||
|
527612d63e | ||
|
5fda4127cd | ||
|
40274dc8d7 | ||
|
038f9da93d | ||
48be4c6345 | |||
7a4f3128ef | |||
3d389b606f | |||
3be0d04f28 | |||
|
6be66634f3 | ||
|
945def46d8 | ||
|
02677ca511 | ||
3009ca01b3 | |||
e97e92e8b7 | |||
5ef94e857c | |||
045c159733 | |||
d286e93755 | |||
59c3026602 | |||
|
ea39814b81 | ||
b4d07993ba | |||
57d0c9bd55 | |||
6e90840af5 | |||
cd5b7d4dbd | |||
|
89da56bcef | ||
|
badbad7cf9 | ||
df107cfc7d | |||
dd76f23a8c | |||
4383f016ff | |||
213a16743d | |||
4f42e69a5d | |||
7539a6a9fb | |||
46aa2c0af4 | |||
7d03b2b471 | |||
|
0ddc602959 | ||
|
008fb92583 | ||
98e203efb2 | |||
d01fa307c6 | |||
7fe646a875 | |||
5d999ea9c8 | |||
|
c448f8a642 | ||
0a7dd7e364 | |||
f3068f9797 | |||
1b4318cece | |||
b98cfcfeb9 | |||
111f63e395 | |||
|
ec5733213e | ||
|
075e792025 | ||
|
14a2fbc755 | ||
|
df7e2a02d1 | ||
|
6ae0854fb9 | ||
bd256e0f44 | |||
25355be9ff | |||
817095cc3f | |||
292ef17be6 | |||
|
5d94bba15f | ||
|
75deaab904 | ||
|
3457f76b17 | ||
29bcac33c0 | |||
3cc763924e | |||
|
fc3d84aa80 | ||
faa00ab353 | |||
ee8563fedb | |||
dd116962e6 | |||
|
5c2e5dcba3 | ||
|
62599214dc | ||
4d21532b1f | |||
|
bd99609de2 | ||
b2dfccf869 | |||
22815bb405 | |||
9c7ae692f5 | |||
2120ab9690 | |||
|
8dcb7528f9 | ||
|
2e3a7b24f0 | ||
|
0722eb9648 | ||
|
247dfe3f31 | ||
|
f71c917c61 | ||
3125a696e4 | |||
b0e48d3703 | |||
76218a92ca | |||
b881d5a056 | |||
|
afffc78537 | ||
|
856372e5ab | ||
|
eea98dd225 | ||
|
82807b7c2e |
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
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
|
||||
First create `.env` file and fill with similar data like `./backend/.env.example`.
|
||||
First create `.env` files in `./backend` and `./frontend` and fill with similar data like `.env.example`.
|
||||
|
||||
Run application and init database
|
||||
```bash
|
||||
|
@ -13,4 +13,3 @@ COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
|
4
backend/Dockerfile.nginx.prod
Normal file
4
backend/Dockerfile.nginx.prod
Normal file
@ -0,0 +1,4 @@
|
||||
FROM nginx:1.23-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY ./nginx.conf /etc/nginx/conf.d
|
19
backend/Dockerfile.prod
Normal file
19
backend/Dockerfile.prod
Normal file
@ -0,0 +1,19 @@
|
||||
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,8 +25,24 @@ Run tests
|
||||
```bash
|
||||
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:
|
||||
Add new package
|
||||
```bash
|
||||
@ -45,15 +61,19 @@ flask startapp NAME_OF_APP
|
||||
```
|
||||
Above command create package structure:
|
||||
\
|
||||
Create serializer in `__schemas__.py` file:
|
||||
```python3
|
||||
class ExampleSchema(ma.SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = MODEL_NAME
|
||||
Create serializer in `schemas.py` file:
|
||||
```python3
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
class ExampleSchema(Schema):
|
||||
id = fields.Integer()
|
||||
name = fields.Str()
|
||||
```
|
||||
\
|
||||
Create models in `__models__.py` file:
|
||||
Create models in `models.py` file:
|
||||
```python3
|
||||
from ..dependencies import db
|
||||
|
||||
class Example(db.Model):
|
||||
__tablename__ = "examples"
|
||||
|
||||
@ -61,7 +81,7 @@ class Example(db.Model):
|
||||
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
|
||||
from flask import Blueprint, make_response, jsonify, Response
|
||||
|
||||
|
@ -1,27 +1,26 @@
|
||||
import os
|
||||
|
||||
from apiflask import APIFlask
|
||||
from flask_migrate import Migrate
|
||||
from flask_cors import CORS
|
||||
from flask_migrate import Migrate
|
||||
|
||||
from .api import api_bp
|
||||
from .commands.clear_db import clear_db
|
||||
from .commands.startapp import startapp
|
||||
from .config import config
|
||||
from .dependencies import db, ma
|
||||
from .commands.startapp import startapp
|
||||
from .commands.init_db import init_db
|
||||
from .commands.clear_db import clear_db
|
||||
from .errors import register_error_handlers
|
||||
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:
|
||||
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"))
|
||||
|
||||
if app.config['ENABLE_CORS']:
|
||||
if app.config["ENABLE_CORS"]:
|
||||
CORS(app)
|
||||
|
||||
db.init_app(app)
|
||||
@ -37,11 +36,9 @@ def create_app(config_name: str = '') -> APIFlask:
|
||||
|
||||
# register commands
|
||||
app.cli.add_command(startapp)
|
||||
app.cli.add_command(init_db)
|
||||
app.cli.add_command(clear_db)
|
||||
|
||||
# register errors
|
||||
register_error_handlers(app)
|
||||
# app.register_error_handler(413, request_entity_too_large)
|
||||
|
||||
return app
|
||||
|
@ -1,10 +1,11 @@
|
||||
from flask import Blueprint
|
||||
|
||||
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 .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
|
||||
api_bp.register_blueprint(coordinator_bp)
|
||||
|
22
backend/app/base/mode.py
Normal file
22
backend/app/base/mode.py
Normal file
@ -0,0 +1,22 @@
|
||||
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)
|
||||
last_name = db.Column(db.String(255), index=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True)
|
||||
email = db.Column(db.String(120), index=True, nullable=False)
|
||||
|
5
backend/app/base/schemas.py
Normal file
5
backend/app/base/schemas.py
Normal file
@ -0,0 +1,5 @@
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
|
||||
class MessageSchema(Schema):
|
||||
message = fields.Str()
|
@ -10,11 +10,13 @@ class PaginationResponse(TypedDict):
|
||||
max_pages: int
|
||||
|
||||
|
||||
def order_by_column_name(query: BaseQuery, model_field: str, order_by_col_name: Union[str, None]) -> BaseQuery:
|
||||
def order_by_column_name(
|
||||
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 == 'asc':
|
||||
if order_by_col_name == "asc":
|
||||
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))
|
||||
|
||||
return query
|
||||
@ -27,12 +29,12 @@ def paginate_models(page: int, query: BaseQuery, per_page=10) -> PaginationRespo
|
||||
else:
|
||||
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):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']
|
||||
return (
|
||||
"." in filename
|
||||
and filename.rsplit(".", 1)[1].lower()
|
||||
in current_app.config["ALLOWED_EXTENSIONS"]
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
from flask.cli import with_appcontext
|
||||
from click import command
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
from ..dependencies import db
|
||||
|
||||
|
||||
@command('clear_db')
|
||||
@command("clear_db")
|
||||
@with_appcontext
|
||||
def clear_db() -> None:
|
||||
"""Clear database"""
|
||||
|
@ -1,37 +0,0 @@
|
||||
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 re
|
||||
|
||||
from flask import current_app
|
||||
from click import command, argument
|
||||
from click import argument, command
|
||||
from click.exceptions import ClickException
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ def startapp(name: str) -> None:
|
||||
"""Create the application structure"""
|
||||
|
||||
if not re.match("^[a-zA-Z].*$", name):
|
||||
raise ClickException(f"The name argument must be type of string!")
|
||||
raise ClickException("The name argument must be type of string!")
|
||||
|
||||
app_dir = current_app.config["SRC_DIR"] / name
|
||||
if os.path.exists(app_dir):
|
||||
@ -26,12 +26,14 @@ def startapp(name: str) -> None:
|
||||
Below you write a schema of model
|
||||
Example:
|
||||
|
||||
from marshmallow import Schema
|
||||
|
||||
class ExampleSchema(ma.SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = MODEL_NAME
|
||||
\'\'\'
|
||||
|
||||
from ..dependencies import ma
|
||||
from marshmallow import Schema
|
||||
"""
|
||||
|
||||
model_content = """\'\'\'
|
||||
@ -43,8 +45,7 @@ class Example(db.Model):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
title = db.Column(db.String(255), unique=True)
|
||||
|
||||
\'\'\'
|
||||
\'\'\'
|
||||
|
||||
from ..dependencies import db
|
||||
"""
|
||||
|
@ -8,22 +8,30 @@ class Config:
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
SRC_DIR = BASE_DIR / "app"
|
||||
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
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SQLALCHEMY_DATABASE_URI = f'sqlite:///{BASE_DIR / "db.sqlite"}'
|
||||
LIMIT_STUDENTS_PER_GROUP = 5
|
||||
LIMIT_PERSONS_PER_COMMITTEE = 4
|
||||
LIMIT_MEMBERS_PER_COMMITTEE = 3
|
||||
|
||||
DESCRIPTION = 'System PRI'
|
||||
OPENAPI_VERSION = '3.0.2'
|
||||
DESCRIPTION = "System PRI"
|
||||
OPENAPI_VERSION = "3.0.2"
|
||||
|
||||
PROJECT_PRESENTATION_TIME = 30 # in minutes
|
||||
# Weights for project grade sheet
|
||||
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):
|
||||
|
@ -1,8 +1,6 @@
|
||||
class CSVException(Exception):
|
||||
"""Main csv exception"""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidNameOrTypeHeaderException(CSVException):
|
||||
"""Throw if csv file has invalid name or type of header"""
|
||||
pass
|
||||
|
@ -1,5 +0,0 @@
|
||||
# from ..base.models import Base
|
||||
#
|
||||
#
|
||||
# class Coordinator(Base):
|
||||
# __tablename__ = 'coordinators'
|
0
backend/app/coordinator/query/__init__.py
Normal file
0
backend/app/coordinator/query/__init__.py
Normal file
143
backend/app/coordinator/query/enrollments.py
Normal file
143
backend/app/coordinator/query/enrollments.py
Normal file
@ -0,0 +1,143 @@
|
||||
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,17 +1,19 @@
|
||||
from flask import Blueprint
|
||||
|
||||
from .examination_schedule import bp as examination_schedule_bp
|
||||
from .enrollments import bp as enrollments_bp
|
||||
from .examination_schedule import bp as examination_schedule_bp
|
||||
from .groups import bp as groups_bp
|
||||
from .project_supervisor import bp as project_supervisor_bp
|
||||
from .students import bp as students_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.register_blueprint(students_bp)
|
||||
bp.register_blueprint(project_supervisor_bp)
|
||||
bp.register_blueprint(groups_bp)
|
||||
bp.register_blueprint(year_group_bp)
|
||||
bp.register_blueprint(examination_schedule_bp)
|
||||
bp.register_blueprint(enrollments_bp)
|
||||
bp.register_blueprint(workloads_bp)
|
||||
|
@ -1,69 +1,235 @@
|
||||
import datetime
|
||||
|
||||
from apiflask import APIBlueprint
|
||||
from flask import abort, current_app
|
||||
from flask import abort
|
||||
|
||||
from ..schemas import MessageSchema, EnrollmentCreateSchema
|
||||
from ...examination_schedule.models import Enrollment, Committee, ExaminationSchedule
|
||||
from ...base.schemas import MessageSchema
|
||||
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.post('/<int:examination_schedule_id>/')
|
||||
@bp.input(EnrollmentCreateSchema, location='json')
|
||||
@bp.post("/<int:examination_schedule_id>/add")
|
||||
@bp.input(TermOfDefenceSchema)
|
||||
@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)
|
||||
def create_enrollments(examination_schedule_id: int, data: dict) -> dict:
|
||||
prt = current_app.config["PROJECT_PRESENTATION_TIME"]
|
||||
def update_term_of_defence(
|
||||
examination_schedule_id: int, term_of_defence_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!")
|
||||
|
||||
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!")
|
||||
td_query = TermOfDefence.query.filter(
|
||||
TermOfDefence.id == term_of_defence_id,
|
||||
TermOfDefence.examination_schedule_id == examination_schedule_id,
|
||||
)
|
||||
td = td_query.first()
|
||||
if td is None:
|
||||
abort(404, "Not found term of defence!")
|
||||
|
||||
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!")
|
||||
ex = get_examination_schedule_by_id(examination_schedule_id)
|
||||
|
||||
enrollment = Enrollment.query.filter(Enrollment.start_date >= start_date,
|
||||
Enrollment.start_date < end_date).first()
|
||||
if enrollment is not None:
|
||||
abort(400, "You have just created enrollments for this range date!")
|
||||
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
|
||||
)
|
||||
|
||||
enrollment = Enrollment.query.filter(Enrollment.end_date > start_date, Enrollment.end_date <= end_date).first()
|
||||
if enrollment is not None:
|
||||
abort(400, "You have just created enrollments for this range date! `1")
|
||||
|
||||
delta = end_date - start_date
|
||||
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)
|
||||
td_query.update(data)
|
||||
td.members_of_committee = []
|
||||
td.chairman_of_committee = chairman_of_committee_id
|
||||
db.session.commit()
|
||||
for p in project_supervisors:
|
||||
td.members_of_committee.append(p)
|
||||
db.session.commit()
|
||||
|
||||
committees = [Committee(enrollment_id=e.id) for e in enrollments]
|
||||
db.session.add_all(committees)
|
||||
db.session.commit()
|
||||
return {"message": "Enrollments was created!"}
|
||||
return {"message": "Term of defence was updated!"}
|
||||
|
||||
|
||||
@bp.delete('/<int:enrollment_id>/')
|
||||
@bp.delete("/<int:examination_schedule_id>/delete/<int:term_of_defence_id>/")
|
||||
@bp.output(MessageSchema)
|
||||
def delete_enrollment(enrollment_id: int) -> dict:
|
||||
enrollment = db.session.query(Enrollment).filter(Enrollment.id == enrollment_id).first()
|
||||
if enrollment is None:
|
||||
abort(404, "Enrollment doesn't exist!")
|
||||
db.session.delete(enrollment)
|
||||
def delete_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
|
||||
)
|
||||
db.session.delete(td)
|
||||
db.session.commit()
|
||||
return {"message": "Enrollment was deleted!"}
|
||||
return {"message": "Term of defence 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,45 +1,69 @@
|
||||
import datetime
|
||||
|
||||
from apiflask import APIBlueprint
|
||||
from flask import abort, Response, make_response
|
||||
from flask import Response, abort, current_app, make_response
|
||||
|
||||
from ...base.mode import EnrollmentsMode
|
||||
from ...base.schemas import MessageSchema
|
||||
from ...base.utils import paginate_models
|
||||
from ...dependencies import db
|
||||
from ...examination_schedule.models import ExaminationSchedule, Enrollment
|
||||
from ...students.models import Group
|
||||
from ...examination_schedule.models import ExaminationSchedule, TermOfDefence
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ..schemas import ExaminationScheduleSchema, ExaminationScheduleUpdateSchema, MessageSchema, \
|
||||
ExaminationSchedulesQuerySchema, ExaminationSchedulesPaginationSchema
|
||||
from ..utils import generate_examination_schedule_pdf_file
|
||||
from ...students.models import Group, YearGroup
|
||||
from ..query.enrollments import set_enrollments_mode
|
||||
from ..schemas.examination_schedule import (
|
||||
ExaminationScheduleSchema,
|
||||
ExaminationSchedulesPaginationSchema,
|
||||
ExaminationSchedulesQuerySchema,
|
||||
)
|
||||
from ..utils import generate_examination_schedule_pdf_file, get_duration_time
|
||||
|
||||
bp = APIBlueprint("examination_schedule", __name__, url_prefix="/examination_schedule")
|
||||
|
||||
|
||||
@bp.get('/')
|
||||
@bp.input(ExaminationSchedulesQuerySchema, location='query')
|
||||
@bp.get("/<int:year_group_id>/")
|
||||
@bp.input(ExaminationSchedulesQuerySchema, location="query")
|
||||
@bp.output(ExaminationSchedulesPaginationSchema)
|
||||
def list_examination_schedule(query: dict) -> dict:
|
||||
page = query.get('page')
|
||||
per_page = query.get('per_page')
|
||||
data = paginate_models(page, ExaminationSchedule.query, per_page)
|
||||
return {'examination_schedules': data['items'], 'max_pages': data['max_pages']}
|
||||
def list_examination_schedule(year_group_id: int, query: dict) -> dict:
|
||||
page = query.get("page")
|
||||
per_page = query.get("per_page")
|
||||
es_query = ExaminationSchedule.query.filter(
|
||||
ExaminationSchedule.year_group_id == year_group_id
|
||||
)
|
||||
data = paginate_models(page, es_query, per_page)
|
||||
return {"examination_schedules": data["items"], "max_pages": data["max_pages"]}
|
||||
|
||||
|
||||
@bp.post('/')
|
||||
@bp.post("/<int:year_group_id>/")
|
||||
@bp.input(ExaminationScheduleSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def create_examination_schedule(data: dict) -> dict:
|
||||
examination_schedule = ExaminationSchedule(**data)
|
||||
@bp.output(MessageSchema, status_code=201)
|
||||
def create_examination_schedule(year_group_id: int, data: dict) -> dict:
|
||||
yg = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
||||
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.commit()
|
||||
return {"message": "Examination schedule was created!"}
|
||||
|
||||
|
||||
@bp.put('/<int:id>/')
|
||||
@bp.put("/<int:examination_schedule_id>/")
|
||||
@bp.input(ExaminationScheduleSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def update_title_examination_schedule(id: int, data: dict) -> dict:
|
||||
examination_schedule_query = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id)
|
||||
def update_examination_schedule(examination_schedule_id: int, data: dict) -> dict:
|
||||
examination_schedule_query = db.session.query(ExaminationSchedule).filter(
|
||||
ExaminationSchedule.id == examination_schedule_id
|
||||
)
|
||||
examination_schedule = examination_schedule_query.first()
|
||||
|
||||
if examination_schedule is None:
|
||||
@ -49,10 +73,14 @@ def update_title_examination_schedule(id: int, data: dict) -> dict:
|
||||
return {"message": "Examination schedule was updated!"}
|
||||
|
||||
|
||||
@bp.delete('/<int:id>/')
|
||||
@bp.delete("/<int:examination_schedule_id>/")
|
||||
@bp.output(MessageSchema)
|
||||
def delete_examination_schedule(id: int) -> dict:
|
||||
examination_schedule = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id).first()
|
||||
def delete_examination_schedule(examination_schedule_id: int) -> dict:
|
||||
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!")
|
||||
db.session.delete(examination_schedule)
|
||||
@ -60,49 +88,56 @@ def delete_examination_schedule(id: int) -> dict:
|
||||
return {"message": "Examination schedule was deleted!"}
|
||||
|
||||
|
||||
@bp.put('/<int:id>/date')
|
||||
@bp.input(ExaminationScheduleUpdateSchema)
|
||||
@bp.put("/<int:examination_schedule_id>/open-enrollments/")
|
||||
@bp.output(MessageSchema)
|
||||
def set_date_of_examination_schedule(id: int, data: dict) -> dict:
|
||||
examination_schedule_query = db.session.query(ExaminationSchedule).filter(ExaminationSchedule.id == id)
|
||||
examination_schedule = examination_schedule_query.first()
|
||||
|
||||
if examination_schedule is None:
|
||||
abort(404, "Examination schedule doesn't exist!")
|
||||
|
||||
if data['start_date'] > data['end_date']:
|
||||
abort(400, "Invalid data! End date must be greater than start date!")
|
||||
|
||||
examination_schedule_query.update(data)
|
||||
db.session.commit()
|
||||
return {"message": "You set date of examination schedule!"}
|
||||
def open_enrollments(examination_schedule_id: int) -> dict:
|
||||
return set_enrollments_mode(examination_schedule_id, EnrollmentsMode.OPEN, "open")
|
||||
|
||||
|
||||
@bp.post('/<int:examination_schedule_id>/download/')
|
||||
@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()
|
||||
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 = []
|
||||
distinct_dates = (
|
||||
db.session.query(db.func.Date(TermOfDefence.start_date)).distinct().all()
|
||||
)
|
||||
|
||||
nested_term_of_defences = []
|
||||
for d in distinct_dates:
|
||||
date_tmp = datetime.datetime.strptime(d[0], "%Y-%m-%d").date()
|
||||
enrollment = db.session.query(Enrollment).join(ExaminationSchedule, isouter=True).\
|
||||
join(Group, isouter=True).join(ProjectSupervisor, isouter=True). \
|
||||
filter(ExaminationSchedule.id == examination_schedule_id).filter(
|
||||
db.func.Date(Enrollment.start_date) == date_tmp).all()
|
||||
nested_enrollments.append(enrollment)
|
||||
|
||||
# print(nested_enrollments)
|
||||
pdf = generate_examination_schedule_pdf_file(examination_schedule.title, nested_enrollments)
|
||||
term_of_defences = (
|
||||
db.session.query(TermOfDefence)
|
||||
.join(Group, isouter=True)
|
||||
.join(ProjectSupervisor, isouter=True)
|
||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
||||
.filter(TermOfDefence.group_id.isnot(None))
|
||||
.filter(db.func.Date(TermOfDefence.start_date) == date_tmp)
|
||||
.all()
|
||||
)
|
||||
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()
|
||||
filename = "_".join(title)
|
||||
|
||||
response = make_response(pdf)
|
||||
response.headers['Content-Type'] = 'application/pdf'
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=%s.pdf' % filename
|
||||
response.headers["Content-Type"] = "application/pdf"
|
||||
response.headers["Content-Disposition"] = "attachment; filename=%s.pdf" % filename
|
||||
return response
|
||||
|
@ -1,120 +1,166 @@
|
||||
from flask import abort, current_app
|
||||
from apiflask import APIBlueprint
|
||||
from flask_sqlalchemy import get_debug_queries
|
||||
from flask import abort
|
||||
|
||||
from ...students.models import Group, Student
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \
|
||||
GroupCreateSchema, MessageSchema, GroupQuerySchema
|
||||
from ...dependencies import db
|
||||
from ...base.schemas import MessageSchema
|
||||
from ...base.utils import paginate_models
|
||||
from ...dependencies import db
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ...students.models import Group, ProjectGradeSheet, Student, YearGroup
|
||||
from ..schemas.groups import (
|
||||
GroupCreateSchema,
|
||||
GroupEditSchema,
|
||||
GroupQuerySchema,
|
||||
GroupSetGradeSchema,
|
||||
GroupsPaginationSchema,
|
||||
)
|
||||
from ..schemas.students import DetailGroupSchema
|
||||
from ..utils import attach_grade_to_group_models
|
||||
|
||||
bp = APIBlueprint("groups", __name__, url_prefix="/groups")
|
||||
|
||||
|
||||
@bp.route("/", methods=["GET"])
|
||||
@bp.input(GroupQuerySchema, location='query')
|
||||
@bp.get("/<int:year_group_id>/")
|
||||
@bp.input(GroupQuerySchema, location="query")
|
||||
@bp.output(GroupsPaginationSchema)
|
||||
def list_groups(query: dict) -> dict:
|
||||
search_name = query.get('name')
|
||||
page = query.get('page')
|
||||
per_page = query.get('per_page')
|
||||
|
||||
groups_query = Group.search_by_name(search_name)
|
||||
def list_groups(year_group_id: int, query: dict) -> dict:
|
||||
search_name = query.get("name")
|
||||
page = query.get("page")
|
||||
per_page = query.get("per_page")
|
||||
|
||||
groups_query = Group.search_by_name(year_group_id, search_name)
|
||||
data = paginate_models(page, groups_query, per_page)
|
||||
|
||||
return {
|
||||
"groups": data['items'],
|
||||
"max_pages": data['max_pages']
|
||||
}
|
||||
items = data["items"]
|
||||
attach_grade_to_group_models(items)
|
||||
return {"groups": items, "max_pages": data["max_pages"]}
|
||||
|
||||
|
||||
@bp.route("/", methods=["POST"])
|
||||
@bp.post("/<int:year_group_id>/")
|
||||
@bp.input(GroupCreateSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def create_group(data: dict) -> dict:
|
||||
name = data['name']
|
||||
students_indexes = data['students']
|
||||
project_supervisor_id = data['project_supervisor_id']
|
||||
@bp.output(MessageSchema, status_code=201)
|
||||
def create_group(year_group_id: int, data: dict) -> dict:
|
||||
name = data["name"]
|
||||
students_ids = data["students"]
|
||||
project_supervisor_id = data["project_supervisor_id"]
|
||||
|
||||
project_supervisor = ProjectSupervisor.query.filter_by(id=project_supervisor_id).first()
|
||||
yg = YearGroup.query.filter(YearGroup.id == year_group_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:
|
||||
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")
|
||||
abort(404, "Not found project supervisor!")
|
||||
|
||||
limit = db.session.query(ProjectSupervisor.limit_group - db.func.count(ProjectSupervisor.id)).join(Group).filter(
|
||||
ProjectSupervisor.id == project_supervisor_id).group_by(ProjectSupervisor.id).scalar()
|
||||
group = Group(
|
||||
name=name,
|
||||
project_supervisor_id=project_supervisor_id,
|
||||
year_group_id=year_group_id,
|
||||
)
|
||||
|
||||
if limit is not None and limit <= 0:
|
||||
abort(400, "Can't create new group, project supervisor achieved a limit of groups")
|
||||
students_without_groups = (
|
||||
db.session.query(Student, Group)
|
||||
.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()
|
||||
)
|
||||
|
||||
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):
|
||||
if len(students_without_groups) > 0:
|
||||
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.commit()
|
||||
|
||||
students = db.session.query(Student).filter(Student.index.in_(students_indexes)).all()
|
||||
project_supervisor.count_groups += 1
|
||||
for student in students:
|
||||
student.group_id = group.id
|
||||
|
||||
group.students.append(student)
|
||||
pgs = ProjectGradeSheet(group_id=group.id)
|
||||
db.session.add(pgs)
|
||||
db.session.commit()
|
||||
|
||||
return {"message": "Group was created!"}
|
||||
|
||||
|
||||
@bp.route("/<int:id>/", methods=["GET"])
|
||||
@bp.output(GroupSchema)
|
||||
def detail_group(id: int) -> Group:
|
||||
group = Group.query.filter_by(id=id).first()
|
||||
@bp.get("/<int:group_id>/detail/")
|
||||
@bp.output(DetailGroupSchema)
|
||||
def detail_group(group_id: int) -> Group:
|
||||
group = Group.query.filter_by(id=group_id).first()
|
||||
if group is None:
|
||||
abort(400, f"Group with id {id} doesn't exist!")
|
||||
abort(404, "Not found group!")
|
||||
attach_grade_to_group_models([group])
|
||||
return group
|
||||
|
||||
|
||||
@bp.route("/<int:id>/", methods=["DELETE"])
|
||||
@bp.delete("/<int:group_id>/")
|
||||
@bp.output(MessageSchema, status_code=202)
|
||||
def delete_group(id: int) -> dict:
|
||||
group = Group.query.filter_by(id=id).first()
|
||||
def delete_group(group_id: int) -> dict:
|
||||
group = Group.query.filter_by(id=group_id).first()
|
||||
if group is None:
|
||||
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
|
||||
abort(404, "Not found group!")
|
||||
|
||||
group.students = []
|
||||
db.session.delete(group)
|
||||
db.session.commit()
|
||||
return {"message": "Group was deleted!"}
|
||||
|
||||
|
||||
@bp.route("/<int:id>", methods=["PUT"])
|
||||
@bp.put("/<int:group_id>/")
|
||||
@bp.input(GroupEditSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def edit_group(id: int, data: dict) -> dict:
|
||||
def edit_group(group_id: int, data: dict) -> dict:
|
||||
if not data:
|
||||
abort(400, 'You have passed empty data!')
|
||||
abort(400, "You have passed empty data!")
|
||||
|
||||
group_query = Group.query.filter_by(id=id)
|
||||
group_query = Group.query.filter_by(id=group_id)
|
||||
group = group_query.first()
|
||||
|
||||
if group is None:
|
||||
abort(400, f"Group with id {id} doesn't exist!")
|
||||
abort(404, "Not found group!")
|
||||
|
||||
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)
|
||||
db.session.commit()
|
||||
return {"message": "Group was updated!"}
|
||||
|
||||
return {"message": "Grade was updated!"}
|
||||
|
@ -1,97 +1,152 @@
|
||||
from flask import abort
|
||||
from apiflask import APIBlueprint
|
||||
from flask_sqlalchemy import get_debug_queries
|
||||
from flask import abort
|
||||
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ...students.models import Group
|
||||
from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \
|
||||
ProjectSupervisorCreateSchema, MessageSchema, ProjectSupervisorQuerySchema
|
||||
from ...dependencies import db
|
||||
from ...base.schemas import MessageSchema
|
||||
from ...base.utils import paginate_models
|
||||
from ...dependencies import db
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ...students.models import Group, YearGroup
|
||||
from ..schemas.project_supervisor import (
|
||||
ProjectSupervisorCreateSchema,
|
||||
ProjectSupervisorEditSchema,
|
||||
ProjectSupervisorQuerySchema,
|
||||
ProjectSupervisorSchema,
|
||||
ProjectSupervisorsPaginationSchema,
|
||||
)
|
||||
from ..schemas.students import MessageWithIdSchema
|
||||
|
||||
bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
|
||||
|
||||
|
||||
@bp.route("/", methods=["GET"])
|
||||
@bp.input(ProjectSupervisorQuerySchema, location='query')
|
||||
@bp.get("/<int:year_group_id>/")
|
||||
@bp.input(ProjectSupervisorQuerySchema, location="query")
|
||||
@bp.output(ProjectSupervisorsPaginationSchema)
|
||||
def list_project_supervisors(query: dict) -> dict:
|
||||
fullname = query.get('fullname')
|
||||
order_by_first_name = query.get('order_by_first_name')
|
||||
order_by_last_name = query.get('order_by_last_name')
|
||||
mode = query.get('mode')
|
||||
page = query.get('page')
|
||||
per_page = query.get('per_page')
|
||||
def list_project_supervisors(year_group_id: int, query: dict) -> dict:
|
||||
fullname = query.get("fullname")
|
||||
order_by_first_name = query.get("order_by_first_name")
|
||||
order_by_last_name = query.get("order_by_last_name")
|
||||
page = query.get("page")
|
||||
per_page = query.get("per_page")
|
||||
|
||||
project_supervisor_query = ProjectSupervisor.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
||||
fullname, mode, order_by_first_name, order_by_last_name)
|
||||
project_supervisor_query = ProjectSupervisor.search_by_fullname(
|
||||
year_group_id, fullname, order_by_first_name, order_by_last_name
|
||||
)
|
||||
|
||||
data = paginate_models(page, project_supervisor_query, per_page)
|
||||
# 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.route("/", methods=["POST"])
|
||||
@bp.post("/<int:year_group_id>/")
|
||||
@bp.input(ProjectSupervisorCreateSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def create_project_supervisor(data: dict) -> dict:
|
||||
first_name = data['first_name']
|
||||
last_name = data['last_name']
|
||||
project_supervisor = ProjectSupervisor.query.filter_by(first_name=first_name).filter_by(last_name=last_name).first()
|
||||
@bp.output(MessageWithIdSchema, status_code=201)
|
||||
def create_project_supervisor(year_group_id: int, data: dict) -> dict:
|
||||
year_group = YearGroup.query.filter(YearGroup.id == year_group_id).first()
|
||||
if year_group is None:
|
||||
abort(404, "Not found year group!")
|
||||
|
||||
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:
|
||||
abort(400, "Project Supervisor has already exists!")
|
||||
|
||||
project_supervisor = ProjectSupervisor(**data)
|
||||
|
||||
project_supervisor = ProjectSupervisor(**data, year_group_id=year_group_id)
|
||||
db.session.add(project_supervisor)
|
||||
db.session.commit()
|
||||
|
||||
return {"message": "Project Supervisor was created!"}
|
||||
return {"message": "Project Supervisor was created!", "id": project_supervisor.id}
|
||||
|
||||
|
||||
@bp.route("/<int:id>/", methods=["GET"])
|
||||
@bp.get("/<int:project_supervisor_id>/detail/")
|
||||
@bp.output(ProjectSupervisorSchema)
|
||||
def detail_project_supervisor(id: int) -> ProjectSupervisor:
|
||||
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
|
||||
def detail_project_supervisor(project_supervisor_id: int) -> ProjectSupervisor:
|
||||
project_supervisor = ProjectSupervisor.query.filter_by(
|
||||
id=project_supervisor_id
|
||||
).first()
|
||||
if project_supervisor is None:
|
||||
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||
abort(404, "Not found project supervisor!")
|
||||
return project_supervisor
|
||||
|
||||
|
||||
@bp.route("/<int:id>/", methods=["DELETE"])
|
||||
@bp.output(MessageSchema, status_code=202)
|
||||
def delete_project_supervisor(id: int) -> dict:
|
||||
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
|
||||
@bp.delete("/<int:project_supervisor_id>/")
|
||||
@bp.output(MessageSchema)
|
||||
def delete_project_supervisor(project_supervisor_id: int) -> dict:
|
||||
project_supervisor = ProjectSupervisor.query.filter_by(
|
||||
id=project_supervisor_id
|
||||
).first()
|
||||
if project_supervisor is None:
|
||||
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||
abort(404, "Not found project supervisor!")
|
||||
|
||||
count_groups = db.session.query(db.func.count(ProjectSupervisor.id)).join(Group).\
|
||||
filter(ProjectSupervisor.id == id).group_by(ProjectSupervisor.id)
|
||||
|
||||
if count_groups is not None:
|
||||
abort(400, f"Project Supervisor with id {id} has gropus!")
|
||||
count_groups = len(
|
||||
Group.query.filter(Group.project_supervisor_id == project_supervisor.id).all()
|
||||
)
|
||||
if count_groups > 0:
|
||||
abort(400, "Project Supervisor has at least one group!")
|
||||
|
||||
db.session.delete(project_supervisor)
|
||||
db.session.commit()
|
||||
return {"message": "Project Supervisor was deleted!"}
|
||||
|
||||
|
||||
@bp.route("/<int:id>", methods=["PUT"])
|
||||
@bp.put("/<int:project_supervisor_id>/")
|
||||
@bp.input(ProjectSupervisorEditSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def edit_project_supervisor(id: int, data: dict) -> dict:
|
||||
def edit_project_supervisor(project_supervisor_id: int, data: dict) -> dict:
|
||||
if not data:
|
||||
abort(400, 'You have passed empty data!')
|
||||
|
||||
project_supervisor_query = ProjectSupervisor.query.filter_by(id=id)
|
||||
abort(400, "You have passed empty data!")
|
||||
|
||||
project_supervisor_query = ProjectSupervisor.query.filter_by(
|
||||
id=project_supervisor_id
|
||||
)
|
||||
project_supervisor = project_supervisor_query.first()
|
||||
|
||||
if project_supervisor is None:
|
||||
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||
|
||||
abort(404, "Not found project supervisor!")
|
||||
|
||||
project_supervisor_query.update(data)
|
||||
db.session.commit()
|
||||
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,78 +1,85 @@
|
||||
from random import randint
|
||||
from itertools import islice
|
||||
from typing import List
|
||||
from random import randint
|
||||
|
||||
from flask import Response, abort
|
||||
from apiflask import APIBlueprint
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from flask_sqlalchemy import get_debug_queries
|
||||
from flask import Response, abort
|
||||
from sqlalchemy import or_
|
||||
|
||||
from ...students.models import Student, Group
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ..schemas import StudentSchema, StudentEditSchema, StudentsPaginationSchema, \
|
||||
StudentCreateSchema, MessageSchema, FileSchema, StudentQuerySchema, StudentListFileDownloaderSchema
|
||||
from ...base.schemas import MessageSchema
|
||||
from ...base.utils import is_allowed_extensions, paginate_models
|
||||
from ...dependencies import db
|
||||
from ..utils import parse_csv, generate_csv
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ...students.models import Group, Student, YearGroup
|
||||
from ..exceptions import InvalidNameOrTypeHeaderException
|
||||
from ...base.utils import paginate_models, is_allowed_extensions
|
||||
from ..schemas.students import (
|
||||
FileSchema,
|
||||
StudentCreateSchema,
|
||||
StudentEditSchema,
|
||||
StudentListFileDownloaderSchema,
|
||||
StudentQuerySchema,
|
||||
StudentSchema,
|
||||
StudentsPaginationSchema,
|
||||
YearGroupInfoQuery,
|
||||
)
|
||||
from ..utils import generate_csv, parse_csv
|
||||
|
||||
bp = APIBlueprint("students", __name__, url_prefix="/students")
|
||||
|
||||
|
||||
@bp.route("/", methods=["GET"])
|
||||
@bp.input(StudentQuerySchema, location='query')
|
||||
@bp.get("/<int:year_group_id>/")
|
||||
@bp.input(StudentQuerySchema, location="query")
|
||||
@bp.output(StudentsPaginationSchema)
|
||||
def list_students(query: dict) -> dict:
|
||||
fullname = query.get('fullname')
|
||||
order_by_first_name = query.get('order_by_first_name')
|
||||
order_by_last_name = query.get('order_by_last_name')
|
||||
mode = query.get('mode')
|
||||
page = query.get('page')
|
||||
per_page = query.get('per_page')
|
||||
def list_students(year_group_id: int, query: dict) -> dict:
|
||||
# add filter by year group
|
||||
fullname = query.get("fullname")
|
||||
order_by_first_name = query.get("order_by_first_name")
|
||||
order_by_last_name = query.get("order_by_last_name")
|
||||
page = query.get("page")
|
||||
per_page = query.get("per_page")
|
||||
|
||||
student_query = Student.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
||||
fullname, mode, order_by_first_name, order_by_last_name)
|
||||
student_query = (
|
||||
Student.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
|
||||
)
|
||||
)
|
||||
|
||||
data = paginate_models(page, student_query, per_page)
|
||||
# print(get_debug_queries()[0])
|
||||
return {
|
||||
"students": data['items'],
|
||||
"max_pages": data['max_pages']
|
||||
}
|
||||
return {"students": data["items"], "max_pages": data["max_pages"]}
|
||||
|
||||
|
||||
@bp.route("/<int:index>/", methods=["GET"])
|
||||
@bp.get("/<int:student_id>/detail/")
|
||||
@bp.output(StudentSchema)
|
||||
def detail_student(index: int) -> Student:
|
||||
student = Student.query.filter_by(index=index).first()
|
||||
def detail_student(student_id: int) -> Student:
|
||||
student = Student.query.filter_by(id=student_id).first()
|
||||
if student is None:
|
||||
abort(404, f"Student with {index} index doesn't exist!")
|
||||
abort(404, "Not found student!")
|
||||
return student
|
||||
|
||||
|
||||
@bp.route("/<int:index>/", methods=["DELETE"])
|
||||
@bp.delete("/<int:student_id>/")
|
||||
@bp.output(MessageSchema, status_code=202)
|
||||
def delete_student(index: int) -> dict:
|
||||
student = Student.query.filter_by(index=index).first()
|
||||
def delete_student(student_id: int) -> dict:
|
||||
student = Student.query.filter_by(id=student_id).first()
|
||||
if student is None:
|
||||
abort(404, f"Student with {index} index doesn't exist!")
|
||||
abort(404, "Not found student!")
|
||||
db.session.delete(student)
|
||||
db.session.commit()
|
||||
return {"message": "Student was deleted!"}
|
||||
|
||||
|
||||
@bp.route("/<int:index>/", methods=["PUT"])
|
||||
@bp.put("/<int:student_id>/")
|
||||
@bp.input(StudentEditSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def edit_student(index: int, data: dict) -> dict:
|
||||
def edit_student(student_id: int, data: dict) -> dict:
|
||||
if not data:
|
||||
abort(400, 'You have passed empty data!')
|
||||
abort(400, "You have passed empty data!")
|
||||
|
||||
student_query = Student.query.filter_by(index=index)
|
||||
student_query = Student.query.filter(Student.id == student_id)
|
||||
student = student_query.first()
|
||||
|
||||
if student is None:
|
||||
abort(404, 'Not found student!')
|
||||
abort(404, "Not found student!")
|
||||
|
||||
student_query.update(data)
|
||||
db.session.commit()
|
||||
@ -80,62 +87,97 @@ def edit_student(index: int, data: dict) -> dict:
|
||||
return {"message": "Student was updated!"}
|
||||
|
||||
|
||||
@bp.route("/", methods=["POST"])
|
||||
@bp.post("/")
|
||||
@bp.input(StudentCreateSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def create_student(data: dict) -> dict:
|
||||
index = data['index']
|
||||
student = Student.query.filter_by(index=index).first()
|
||||
if student is not None:
|
||||
abort(400, "Student has already exists!")
|
||||
index = data["index"]
|
||||
yg_id = data["year_group_id"]
|
||||
del data["year_group_id"]
|
||||
|
||||
dummy_email = f'student{randint(1, 300_000)}@gmail.com'
|
||||
student = Student(**data, email=dummy_email)
|
||||
student = Student.query.filter(
|
||||
Student.index == index, Student.year_group_id == yg_id
|
||||
).first()
|
||||
if student is not None:
|
||||
abort(400, "Student has already assigned to this year group!")
|
||||
|
||||
year_group = YearGroup.query.filter(YearGroup.id == yg_id).first()
|
||||
if year_group is None:
|
||||
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.commit()
|
||||
|
||||
return {"message": "Student was created!"}
|
||||
|
||||
|
||||
@bp.route("/upload/", methods=["POST"])
|
||||
@bp.input(FileSchema, location='form_and_files')
|
||||
@bp.post("/upload/")
|
||||
@bp.input(YearGroupInfoQuery, location="query")
|
||||
@bp.input(FileSchema, location="form_and_files")
|
||||
@bp.output(MessageSchema)
|
||||
def upload_students(file: dict) -> dict:
|
||||
uploaded_file = file.get('file')
|
||||
def upload_students(query: dict, file: dict) -> dict:
|
||||
"""Add only Students to chosen year group if students exist in db and
|
||||
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):
|
||||
try:
|
||||
students = parse_csv(uploaded_file, mode=True)
|
||||
students = parse_csv(uploaded_file, year_group_id)
|
||||
while True:
|
||||
sliced_students = islice(students, 5)
|
||||
list_of_students = list(sliced_students)
|
||||
|
||||
if len(list_of_students) == 0:
|
||||
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()
|
||||
|
||||
except InvalidNameOrTypeHeaderException:
|
||||
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:
|
||||
abort(400, "Invalid extension of file")
|
||||
|
||||
return {"message": "Students was created by uploading csv file!"}
|
||||
|
||||
|
||||
@bp.route("/download/", methods=["POST"])
|
||||
@bp.input(StudentListFileDownloaderSchema, location='query')
|
||||
@bp.post("/download/")
|
||||
@bp.input(StudentListFileDownloaderSchema, location="query")
|
||||
def download_students(query: dict) -> Response:
|
||||
mode = query.get('mode')
|
||||
mode = mode if mode is not None else True
|
||||
students = db.session.query(Student).join(Group). \
|
||||
join(ProjectSupervisor).filter(Student.mode == mode).all()
|
||||
year_group_id = query.get("year_group_id")
|
||||
students_and_groups = (
|
||||
db.session.query(Student, Group)
|
||||
.join(Group, Student.groups)
|
||||
.filter(Group.year_group_id == year_group_id)
|
||||
.join(ProjectSupervisor)
|
||||
.all()
|
||||
)
|
||||
|
||||
if len(students) == 0:
|
||||
abort(404, "Not found students, which are assigned to group!")
|
||||
csv_file = generate_csv(students)
|
||||
response = Response(csv_file, mimetype='text/csv')
|
||||
response.headers.set("Content-Disposition", "attachment", filename="students_list.csv")
|
||||
if len(students_and_groups) == 0:
|
||||
abort(404, "Not found students!")
|
||||
|
||||
csv_file = generate_csv(students_and_groups)
|
||||
response = Response(csv_file, mimetype="text/csv")
|
||||
response.headers.set(
|
||||
"Content-Disposition", "attachment", filename="students_list.csv"
|
||||
)
|
||||
return response
|
||||
|
@ -1,33 +1,38 @@
|
||||
from apiflask import APIBlueprint
|
||||
from flask import abort
|
||||
from flask_sqlalchemy import get_debug_queries
|
||||
|
||||
from ...dependencies import db
|
||||
from ...examination_schedule.models import ExaminationSchedule, Enrollment, Committee
|
||||
from ...examination_schedule.models import ExaminationSchedule, TermOfDefence
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ..schemas import WorkloadSchema
|
||||
from ..schemas.examination_schedule import WorkloadSchema
|
||||
|
||||
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)
|
||||
def workloads_statistics(examination_schedule_id: int) -> dict:
|
||||
es = ExaminationSchedule.query.filter_by(id=examination_schedule_id).first()
|
||||
if es is None:
|
||||
abort(404, "Not found examination schedule!")
|
||||
|
||||
statistics = db.session.query(
|
||||
ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name,
|
||||
db.func.count(Enrollment.group_id),
|
||||
db.func.count(Committee.id),
|
||||
).join(Committee.members). \
|
||||
join(Enrollment, isouter=True). \
|
||||
group_by(ProjectSupervisor.id).all()
|
||||
statistics = (
|
||||
db.session.query(
|
||||
ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name,
|
||||
db.func.count(TermOfDefence.group_id),
|
||||
db.func.count(TermOfDefence.id),
|
||||
)
|
||||
.join(TermOfDefence.members_of_committee)
|
||||
.group_by(ProjectSupervisor.id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# print(statistics)
|
||||
# print(len(statistics))
|
||||
# print(get_debug_queries())
|
||||
workloads = ({"full_name": s[0], "groups_assigned_to_his_committee": s[1], "assigned_to_committee": s[2]} for s in
|
||||
statistics)
|
||||
return {'workloads': workloads}
|
||||
workloads = (
|
||||
{
|
||||
"full_name": s[0],
|
||||
"groups_assigned_to_his_committee": s[1],
|
||||
"assigned_to_committee": s[2],
|
||||
}
|
||||
for s in statistics
|
||||
)
|
||||
return {"workloads": workloads}
|
||||
|
84
backend/app/coordinator/routes/year_group.py
Normal file
84
backend/app/coordinator/routes/year_group.py
Normal file
@ -0,0 +1,84 @@
|
||||
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!"}
|
@ -1,9 +0,0 @@
|
||||
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,8 +1,69 @@
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
from ..validators import validate_datetime_greater_than_now
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
|
||||
class EnrollmentCreateSchema(Schema):
|
||||
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 TermOfDefenceSchema(Schema):
|
||||
start_date = fields.DateTime(required=True)
|
||||
end_date = fields.DateTime(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,28 +1,30 @@
|
||||
from marshmallow import fields, validate, Schema
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
from ..validators import validate_datetime_greater_than_now
|
||||
|
||||
|
||||
class ExaminationScheduleSchema(Schema):
|
||||
title = fields.Str(validate=validate.Length(min=1, max=100), required=True)
|
||||
mode = fields.Boolean(required=True)
|
||||
|
||||
|
||||
class ExaminationScheduleUpdateSchema(Schema):
|
||||
start_date = fields.DateTime(validate=validate_datetime_greater_than_now, required=True)
|
||||
end_date = fields.DateTime(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):
|
||||
id = fields.Integer(required=True)
|
||||
title = fields.Str(validate=validate.Length(min=1, max=100), required=True)
|
||||
mode = fields.Boolean(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)
|
||||
id = fields.Integer()
|
||||
title = fields.Str()
|
||||
open_enrollments = fields.String()
|
||||
start_date = fields.DateTime()
|
||||
end_date = fields.DateTime()
|
||||
|
||||
|
||||
class ExaminationSchedulesPaginationSchema(Schema):
|
||||
examination_schedules = fields.List(fields.Nested(ExaminationScheduleListItemSchema))
|
||||
examination_schedules = fields.List(
|
||||
fields.Nested(ExaminationScheduleListItemSchema)
|
||||
)
|
||||
max_pages = fields.Integer()
|
||||
|
||||
|
||||
|
@ -1,28 +1,35 @@
|
||||
from marshmallow import fields, validate
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
from ...dependencies import ma
|
||||
from ..validators import validate_index
|
||||
from .students import GroupSchema, StudentSchema
|
||||
from .students import GroupSchema
|
||||
|
||||
|
||||
class GroupQuerySchema(ma.Schema):
|
||||
class GroupQuerySchema(Schema):
|
||||
name = fields.Str()
|
||||
page = fields.Integer()
|
||||
per_page = fields.Integer()
|
||||
|
||||
|
||||
class GroupsPaginationSchema(ma.Schema):
|
||||
class GroupsPaginationSchema(Schema):
|
||||
groups = fields.List(fields.Nested(GroupSchema))
|
||||
max_pages = fields.Integer()
|
||||
|
||||
|
||||
class GroupCreateSchema(ma.Schema):
|
||||
class GroupCreateSchema(Schema):
|
||||
name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||
project_supervisor_id = fields.Integer(required=True)
|
||||
students = fields.List(fields.Integer(validate=validate_index, required=True))
|
||||
students = fields.List(fields.Integer(required=True))
|
||||
|
||||
|
||||
class GroupEditSchema(ma.Schema):
|
||||
class GroupEditSchema(Schema):
|
||||
name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||
project_supervisor_id = fields.Integer(validate=validate_index)
|
||||
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
|
||||
project_supervisor_id = fields.Integer()
|
||||
students = fields.List(fields.Integer())
|
||||
|
||||
|
||||
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,36 +1,38 @@
|
||||
from marshmallow import fields, validate
|
||||
|
||||
from ...dependencies import ma
|
||||
from ..validators import validate_index
|
||||
from .students import ProjectSupervisorSchema
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
|
||||
class ProjectSupervisorQuerySchema(ma.Schema):
|
||||
class ProjectSupervisorSchema(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()
|
||||
order_by_first_name = fields.Str()
|
||||
order_by_last_name = fields.Str()
|
||||
page = fields.Integer()
|
||||
per_page = fields.Integer()
|
||||
mode = fields.Integer()
|
||||
|
||||
|
||||
class ProjectSupervisorsPaginationSchema(ma.Schema):
|
||||
class ProjectSupervisorsPaginationSchema(Schema):
|
||||
project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema))
|
||||
max_pages = fields.Integer()
|
||||
|
||||
|
||||
class ProjectSupervisorCreateSchema(ma.Schema):
|
||||
class ProjectSupervisorCreateSchema(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=1, max=255), required=True)
|
||||
email = fields.Str(
|
||||
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()
|
||||
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,15 +1,9 @@
|
||||
from marshmallow import fields, validate
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
from ...dependencies import ma
|
||||
from ...students.models import Student, Group
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ...students.models import Group, Student
|
||||
from ..validators import validate_index
|
||||
|
||||
|
||||
class ProjectSupervisorSchema(ma.SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = ProjectSupervisor
|
||||
include_relationships = False
|
||||
from .project_supervisor import ProjectSupervisorSchema
|
||||
|
||||
|
||||
class GroupSchema(ma.SQLAlchemyAutoSchema):
|
||||
@ -20,7 +14,7 @@ class GroupSchema(ma.SQLAlchemyAutoSchema):
|
||||
|
||||
|
||||
class StudentSchema(ma.SQLAlchemyAutoSchema):
|
||||
group = fields.Nested(GroupSchema)
|
||||
groups = fields.List(fields.Nested(GroupSchema))
|
||||
|
||||
class Meta:
|
||||
model = Student
|
||||
@ -32,31 +26,29 @@ class StudentsPaginationSchema(ma.Schema):
|
||||
|
||||
|
||||
class StudentListFileDownloaderSchema(ma.Schema):
|
||||
mode = fields.Integer()
|
||||
year_group_id = fields.Integer(required=True)
|
||||
|
||||
|
||||
class StudentCreateSchema(ma.Schema):
|
||||
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||
pesel = fields.Str(validate=validate.Length(min=0, max=11), required=True)
|
||||
index = fields.Integer(validate=validate_index, required=True)
|
||||
mode = fields.Boolean(required=True)
|
||||
year_group_id = fields.Integer()
|
||||
|
||||
|
||||
class StudentEditSchema(ma.Schema):
|
||||
first_name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||
last_name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||
pesel = fields.Str(validate=validate.Length(min=0, max=11))
|
||||
index = fields.Integer(validate=validate_index)
|
||||
mode = fields.Boolean()
|
||||
|
||||
|
||||
class MessageSchema(ma.Schema):
|
||||
class MessageWithIdSchema(ma.Schema):
|
||||
message = fields.Str(required=True)
|
||||
id = fields.Str(required=False)
|
||||
|
||||
|
||||
class FileSchema(ma.Schema):
|
||||
file = fields.Raw(type='file', required=True)
|
||||
file = fields.Raw(metadata={"type": "file"}, required=True)
|
||||
|
||||
|
||||
class StudentQuerySchema(ma.Schema):
|
||||
@ -65,4 +57,17 @@ class StudentQuerySchema(ma.Schema):
|
||||
order_by_last_name = fields.Str()
|
||||
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
|
||||
|
30
backend/app/coordinator/schemas/year_group.py
Normal file
30
backend/app/coordinator/schemas/year_group.py
Normal file
@ -0,0 +1,30 @@
|
||||
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,62 +1,100 @@
|
||||
import datetime
|
||||
import copy
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
from itertools import chain
|
||||
from typing import Generator, Any, List
|
||||
from pathlib import Path
|
||||
from typing import Any, Generator, List, TextIO, Tuple, Union
|
||||
|
||||
import pandas as pd
|
||||
from flask import current_app
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.enums import TA_CENTER
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import mm, inch
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Table
|
||||
from reportlab.lib.units import inch, mm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
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 ..students.models import Student
|
||||
from ..examination_schedule.models import Enrollment
|
||||
|
||||
|
||||
def check_columns(df: pd.DataFrame) -> bool:
|
||||
headers = set(df.keys().values)
|
||||
columns = ['NAZWISKO', 'IMIE', 'INDEKS', 'PESEL', 'EMAIL']
|
||||
|
||||
if len(headers - set(columns)) != 0:
|
||||
return False
|
||||
|
||||
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
|
||||
column_names = ["NAZWISKO", "IMIE", "INDEKS", "EMAIL"]
|
||||
column_types = ["object", "object", "int", "object"]
|
||||
return all((column_name in headers for column_name in column_names)) and all(
|
||||
(
|
||||
str(df.dtypes[column_name]).startswith(column_type)
|
||||
for column_name, column_type in zip(column_names, column_types)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def parse_csv(file, mode) -> Generator[Student, Any, None]:
|
||||
def parse_csv(
|
||||
file: Union[FileStorage, TextIO], year_group_id: int
|
||||
) -> Generator[Student, Any, None]:
|
||||
df = pd.read_csv(file)
|
||||
|
||||
if not check_columns(df):
|
||||
raise InvalidNameOrTypeHeaderException
|
||||
|
||||
students = (Student(last_name=dict(item.items())['NAZWISKO'],
|
||||
first_name=dict(item.items())['IMIE'],
|
||||
index=dict(item.items())['INDEKS'],
|
||||
pesel=str(int(dict(item.items())['PESEL'])) if not pd.isna(
|
||||
dict(item.items())['PESEL']) else None,
|
||||
email=dict(item.items())['EMAIL'],
|
||||
mode=mode)
|
||||
for _, item in df.iterrows())
|
||||
students = (
|
||||
Student(
|
||||
last_name=dict(item.items())["NAZWISKO"],
|
||||
first_name=dict(item.items())["IMIE"],
|
||||
index=dict(item.items())["INDEKS"],
|
||||
email=dict(item.items())["EMAIL"],
|
||||
year_group_id=year_group_id,
|
||||
)
|
||||
for _, item in df.iterrows()
|
||||
)
|
||||
|
||||
return students
|
||||
|
||||
|
||||
def generate_csv(students: List[Student]) -> str:
|
||||
headers = ['PESEL', 'INDEKS', 'IMIE', 'NAZWISKO', 'EMAIL', 'CDYD_KOD', 'PRZ_KOD', 'TZAJ_KOD', 'GR_NR', 'PRG_KOD']
|
||||
data = [(student.pesel, student.index, student.first_name, student.last_name, student.email,
|
||||
student.group.cdyd_kod, student.group.prz_kod, student.group.tzaj_kod, student.group.project_supervisor_id,
|
||||
None) for student in students]
|
||||
def map_project_supervisors(groups: List[Group]) -> dict:
|
||||
i = 1
|
||||
mapped_project_supervisors = {}
|
||||
for group in groups:
|
||||
if group.project_supervisor_id not in mapped_project_supervisors.keys():
|
||||
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)
|
||||
for row in data:
|
||||
for idx, item in enumerate(row):
|
||||
@ -66,9 +104,32 @@ def generate_csv(students: List[Student]) -> str:
|
||||
return df.to_csv(index=False)
|
||||
|
||||
|
||||
def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[List[Enrollment]]) -> bytes:
|
||||
def generate_range_dates(
|
||||
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)
|
||||
headers = ["lp.", "Godzina", "Nazwa projektu", "Opiekun", "Zespol", "Komisja"]
|
||||
headers = [
|
||||
"lp.",
|
||||
"Godzina",
|
||||
"Nazwa projektu",
|
||||
"Opiekun",
|
||||
"Zespol",
|
||||
"Komisja",
|
||||
"Uwagi",
|
||||
]
|
||||
pdf_buffer = BytesIO()
|
||||
my_doc = SimpleDocTemplate(
|
||||
pdf_buffer,
|
||||
@ -77,65 +138,75 @@ def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[
|
||||
leftMargin=1 * inch,
|
||||
rightMargin=1 * inch,
|
||||
bottomMargin=1 * inch,
|
||||
title=title
|
||||
title=title,
|
||||
)
|
||||
|
||||
pdfmetrics.registerFont(TTFont("Lato", base_dir / "fonts" / "Lato.ttf"))
|
||||
style = getSampleStyleSheet()
|
||||
bodyText = style['BodyText']
|
||||
bodyText.fontName = 'Helvetica'
|
||||
bodyText = style["BodyText"]
|
||||
bodyText.fontName = "Lato"
|
||||
normal = style["Heading1"]
|
||||
normal.alignment = TA_CENTER
|
||||
flowables = []
|
||||
|
||||
# print(nested_enrollments)
|
||||
for enrollments in nested_enrollments:
|
||||
if len(enrollments) == 0:
|
||||
for term_of_defences in nested_term_of_defences:
|
||||
if len(term_of_defences) == 0:
|
||||
continue
|
||||
date = datetime.datetime.strftime(enrollments[0].start_date, '%d/%m/%Y')
|
||||
date = datetime.strftime(term_of_defences[0].start_date.date(), "%d.%m.%Y")
|
||||
paragraph_1 = Paragraph(f"{title} ~ {date}", normal)
|
||||
flowables.append(paragraph_1)
|
||||
data = [headers]
|
||||
|
||||
for idx, e in enumerate(enrollments, start=1):
|
||||
new_date = e.start_date + datetime.timedelta(hours=2)
|
||||
group_name = e.group.name if e.group is not None else ""
|
||||
if group_name != '':
|
||||
ps = e.group.project_supervisor
|
||||
for idx, td in enumerate(term_of_defences, start=1):
|
||||
new_date = td.start_date + timedelta(hours=2)
|
||||
group_name = td.group.name if td.group is not None else ""
|
||||
if group_name != "":
|
||||
ps = td.group.project_supervisor
|
||||
project_supervisor_fullname = f"{ps.first_name[0]}. {ps.last_name}"
|
||||
students = e.group.students
|
||||
# print(students)
|
||||
students = td.group.students
|
||||
team = ", ".join([f"{s.first_name} {s.last_name}" for s in students])
|
||||
else:
|
||||
project_supervisor_fullname = ""
|
||||
team = ""
|
||||
|
||||
members = e.committee.members
|
||||
# print(members)
|
||||
if len(members) == 0:
|
||||
committee = ''
|
||||
else:
|
||||
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])
|
||||
members = td.members_of_committee
|
||||
|
||||
if len(members) == 0:
|
||||
committee = ""
|
||||
else:
|
||||
members_iter = (f"{m.first_name[0]}. {m.last_name}" for m in members)
|
||||
committee = ", ".join(members_iter)
|
||||
|
||||
data.append([str(idx), new_date.strftime("%H:%M"),
|
||||
Paragraph(group_name, bodyText),
|
||||
Paragraph(project_supervisor_fullname, bodyText),
|
||||
Paragraph(team, bodyText),
|
||||
Paragraph(committee, bodyText),
|
||||
])
|
||||
data.append(
|
||||
[
|
||||
str(idx),
|
||||
new_date.strftime("%H:%M"),
|
||||
Paragraph(group_name, bodyText),
|
||||
Paragraph(project_supervisor_fullname, bodyText),
|
||||
Paragraph(team, bodyText),
|
||||
Paragraph(committee, bodyText),
|
||||
]
|
||||
)
|
||||
# print(data)
|
||||
|
||||
table = Table(data=data,
|
||||
style=[
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#A6F1A6")),
|
||||
('BACKGROUND', (0, 0), (1, -1), colors.HexColor("#A6F1A6"))
|
||||
],
|
||||
colWidths=[0.25 * inch, 0.7 * inch, 1.6 * inch, 1.5 * inch, 4 * inch, 3 * inch]
|
||||
)
|
||||
table = Table(
|
||||
data=data,
|
||||
style=[
|
||||
("GRID", (0, 0), (-1, -1), 0.5, colors.black),
|
||||
("BACKGROUND", (0, 0), (-1, 0), 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,
|
||||
],
|
||||
)
|
||||
flowables.append(table)
|
||||
flowables.append(PageBreak())
|
||||
|
||||
@ -143,3 +214,121 @@ def generate_examination_schedule_pdf_file(title: str, nested_enrollments: List[
|
||||
pdf_value = pdf_buffer.getvalue()
|
||||
pdf_buffer.close()
|
||||
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,6 +1,5 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_marshmallow import Marshmallow
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
ma = Marshmallow()
|
||||
|
||||
|
@ -1,18 +1,13 @@
|
||||
import json
|
||||
from typing import Tuple
|
||||
|
||||
from apiflask import APIFlask
|
||||
from werkzeug.exceptions import RequestEntityTooLarge, HTTPException
|
||||
|
||||
|
||||
def request_entity_too_large(error: RequestEntityTooLarge) -> Tuple[dict, int]:
|
||||
return {'error': 'File too large!'}, 413
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
|
||||
def register_error_handlers(app: APIFlask):
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_http_exception(e):
|
||||
response = e.get_response()
|
||||
response.data = json.dumps({'error': e.description})
|
||||
response.content_type = 'application/json'
|
||||
response.data = json.dumps({"error": e.description})
|
||||
response.content_type = "application/json"
|
||||
return response
|
||||
|
@ -1,38 +1,68 @@
|
||||
from ..dependencies import db
|
||||
from ..base.mode import EnrollmentsMode
|
||||
from ..base.models import Base
|
||||
from ..dependencies import db
|
||||
|
||||
|
||||
class ExaminationSchedule(Base):
|
||||
__tablename__ = 'examination_schedules'
|
||||
__tablename__ = "examination_schedules"
|
||||
|
||||
title = db.Column(db.String(100), unique=True, nullable=False)
|
||||
mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary
|
||||
start_date = db.Column(db.DateTime)
|
||||
end_date = db.Column(db.DateTime)
|
||||
duration_time = db.Column(db.Integer, nullable=False) # in minutes
|
||||
open_enrollments = db.Column(
|
||||
db.String(1), default=EnrollmentsMode.INIT.value, nullable=False
|
||||
)
|
||||
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")
|
||||
|
||||
|
||||
class Enrollment(Base):
|
||||
__tablename__ = 'enrollments'
|
||||
committee = db.Table(
|
||||
"committees",
|
||||
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)
|
||||
end_date = db.Column(db.DateTime, nullable=False)
|
||||
examination_schedule_id = db.Column(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))
|
||||
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
|
||||
group = db.relationship("Group", uselist=False, backref='enrollment')
|
||||
examination_schedule_id = db.Column(
|
||||
db.Integer, db.ForeignKey("examination_schedules.id")
|
||||
)
|
||||
examination_schedule = db.relationship(
|
||||
"ExaminationSchedule", backref="term_of_defences"
|
||||
)
|
||||
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 Committee(Base):
|
||||
__tablename__ = 'committees'
|
||||
class TemporaryAvailability(Base):
|
||||
__tablename__ = "temporary_availabilities"
|
||||
|
||||
enrollment_id = db.Column(db.Integer, db.ForeignKey('enrollments.id', ondelete='CASCADE'))
|
||||
members = db.relationship('ProjectSupervisor', secondary='committees_projects_supervisors', backref='committees')
|
||||
|
||||
|
||||
class CommitteeProjectSupervisor(Base):
|
||||
__tablename__ = 'committees_projects_supervisors'
|
||||
|
||||
chairman = db.Column(db.Boolean, default=False, nullable=False)
|
||||
committee_id = db.Column(db.Integer, db.ForeignKey('committees.id'))
|
||||
member_id = db.Column(db.Integer, db.ForeignKey('project_supervisors.id'))
|
||||
start_date = db.Column(db.DateTime, nullable=False)
|
||||
end_date = db.Column(db.DateTime, nullable=False)
|
||||
examination_schedule_id = db.Column(
|
||||
db.Integer, db.ForeignKey("examination_schedules.id"), nullable=False
|
||||
)
|
||||
examination_schedule = db.relationship(
|
||||
"ExaminationSchedule", backref="temporary_availabilities"
|
||||
)
|
||||
project_supervisor_id = db.Column(
|
||||
db.Integer, db.ForeignKey("project_supervisors.id"), nullable=False
|
||||
)
|
||||
project_supervisor = db.relationship(
|
||||
"ProjectSupervisor", backref="temporary_availabilities"
|
||||
)
|
||||
|
@ -1,9 +1,3 @@
|
||||
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.register_blueprint(enrollments_bp)
|
||||
bp.register_blueprint(examination_schedule_bp)
|
||||
|
@ -1,61 +0,0 @@
|
||||
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)
|
@ -1,32 +0,0 @@
|
||||
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}
|
@ -1,41 +0,0 @@
|
||||
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))
|
@ -1,27 +0,0 @@
|
||||
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"]}
|
@ -1,44 +0,0 @@
|
||||
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,34 +1,50 @@
|
||||
from flask_sqlalchemy import BaseQuery
|
||||
|
||||
from ..dependencies import db
|
||||
from ..base.models import Person, Base
|
||||
from ..base.models import Base, Person
|
||||
from ..base.utils import order_by_column_name
|
||||
from ..dependencies import db
|
||||
from ..students.models import YearGroup
|
||||
|
||||
|
||||
class ProjectSupervisor(Base, Person):
|
||||
__tablename__ = "project_supervisors"
|
||||
|
||||
limit_group = db.Column(db.Integer, default=3, nullable=False)
|
||||
count_groups = db.Column(db.Integer, default=0, nullable=False)
|
||||
mode = db.Column(db.Integer, default=0, nullable=False) # 0 - stationary, 1 - non-stationary, 2 - both
|
||||
is_coordinator = db.Column(db.Boolean, default=False, nullable=False)
|
||||
year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id"))
|
||||
year_group = db.relationship("YearGroup", backref="project_supervisors")
|
||||
|
||||
__table__args = db.UniqueConstraint(
|
||||
"email", "year_group_id", name="uc_email_year_group_id"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None,
|
||||
mode: int = None,
|
||||
order_by_first_name: str = None,
|
||||
order_by_last_name: str = None) -> BaseQuery:
|
||||
def search_by_fullname(
|
||||
cls,
|
||||
year_group_id: int = None,
|
||||
fullname: str = None,
|
||||
order_by_first_name: str = None,
|
||||
order_by_last_name: str = None,
|
||||
) -> BaseQuery:
|
||||
project_supervisors_query = cls.query
|
||||
|
||||
if mode is not None:
|
||||
project_supervisors_query = project_supervisors_query.filter(mode != 1 - mode)
|
||||
if year_group_id is not None:
|
||||
project_supervisors_query = project_supervisors_query.filter(
|
||||
ProjectSupervisor.year_group_id == year_group_id
|
||||
)
|
||||
|
||||
if fullname is not None:
|
||||
project_supervisors_query = project_supervisors_query.filter(
|
||||
(ProjectSupervisor.first_name + ' ' + ProjectSupervisor.last_name).like(f'{fullname}%'))
|
||||
(ProjectSupervisor.first_name + " " + ProjectSupervisor.last_name).like(
|
||||
f"{fullname}%"
|
||||
)
|
||||
)
|
||||
|
||||
project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.first_name,
|
||||
order_by_first_name)
|
||||
project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.last_name,
|
||||
order_by_last_name)
|
||||
project_supervisors_query = order_by_column_name(
|
||||
project_supervisors_query, ProjectSupervisor.first_name, order_by_first_name
|
||||
)
|
||||
project_supervisors_query = order_by_column_name(
|
||||
project_supervisors_query, ProjectSupervisor.last_name, order_by_last_name
|
||||
)
|
||||
|
||||
return project_supervisors_query
|
||||
|
@ -1,33 +0,0 @@
|
||||
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!")
|
0
backend/app/project_supervisor/query/__init__.py
Normal file
0
backend/app/project_supervisor/query/__init__.py
Normal file
35
backend/app/project_supervisor/query/project_grade_sheet.py
Normal file
35
backend/app/project_supervisor/query/project_grade_sheet.py
Normal file
@ -0,0 +1,35 @@
|
||||
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,7 +1,9 @@
|
||||
from flask import Blueprint
|
||||
|
||||
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.register_blueprint(enrollments_bp)
|
||||
bp.register_blueprint(project_grade_sheet_bp)
|
||||
|
@ -1,64 +1,199 @@
|
||||
from apiflask import APIBlueprint
|
||||
from flask import abort, current_app
|
||||
from flask import abort
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
from ..schemas import MessageSchema, CommitteeCreateSchema, TemporaryProjectSupervisorSchema
|
||||
from ...examination_schedule.models import Committee
|
||||
from ...base.mode import EnrollmentsMode
|
||||
from ...base.schemas import MessageSchema
|
||||
from ...dependencies import db
|
||||
from ...examination_schedule.models import (
|
||||
ExaminationSchedule,
|
||||
TemporaryAvailability,
|
||||
TermOfDefence,
|
||||
)
|
||||
from ..models import ProjectSupervisor
|
||||
from ..query import get_enrollment_by_enrollment_and_examination_schedule_ids, \
|
||||
check_the_project_supervisor_is_in_committee, check_the_enrollments_has_just_started
|
||||
from ..schemas import (
|
||||
ListOfFreeTimesSchema,
|
||||
ListOfTermOfDefenceSchema,
|
||||
TemporaryProjectSupervisorSchema,
|
||||
TimeAvailabilityCreateSchema,
|
||||
)
|
||||
|
||||
bp = APIBlueprint("enrollments", __name__, url_prefix="/")
|
||||
|
||||
|
||||
@bp.post('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||
@bp.input(CommitteeCreateSchema)
|
||||
@bp.post("/<int:examination_schedule_id>/enrollments/")
|
||||
@bp.input(TimeAvailabilityCreateSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def assign_yourself_to_committee(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||
def set_your_free_time_to_examination_schedule(
|
||||
examination_schedule_id: int, data: dict
|
||||
) -> dict:
|
||||
# this code will be removed
|
||||
project_supervisor = db.session.query(ProjectSupervisor).filter(
|
||||
ProjectSupervisor.id == data['project_supervisor_id']).first()
|
||||
project_supervisor = ProjectSupervisor.query.filter(
|
||||
ProjectSupervisor.id == data["project_supervisor_id"]
|
||||
).first()
|
||||
if project_supervisor is None:
|
||||
abort(404, "ProjectSupervisor doesn't exist!")
|
||||
print(project_supervisor)
|
||||
################
|
||||
|
||||
limit_of_committtee = current_app.config['LIMIT_PERSONS_PER_COMMITTEE']
|
||||
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.year_group_id != project_supervisor.year_group_id:
|
||||
abort(400, "You are not assigned to this year group!")
|
||||
|
||||
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||
check_the_enrollments_has_just_started(enrollment.examination_schedule.start_date, "assign")
|
||||
if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value:
|
||||
abort(400, "Enrollments has started or closed! You have been delayed!")
|
||||
|
||||
size_of_committee = db.session.query(Committee).join(Committee.members). \
|
||||
filter(Committee.enrollment_id == enrollment.id).count()
|
||||
sd = data.get("start_date")
|
||||
ed = data.get("end_date")
|
||||
if sd > ed:
|
||||
abort(400, "Invalid data! End date must be greater than start date!")
|
||||
|
||||
if size_of_committee >= limit_of_committtee:
|
||||
abort(400, "The committee is full!")
|
||||
start_date = examination_schedule.start_date
|
||||
end_date = examination_schedule.end_date
|
||||
if not (
|
||||
start_date.timestamp() <= sd.timestamp()
|
||||
and end_date.timestamp() >= ed.timestamp()
|
||||
):
|
||||
abort(400, "Invalid date range!")
|
||||
|
||||
if check_the_project_supervisor_is_in_committee(enrollment.id, project_supervisor.id):
|
||||
abort(400, "You have already in this committee!")
|
||||
ta = (
|
||||
TemporaryAvailability.query.filter(
|
||||
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!",
|
||||
)
|
||||
|
||||
enrollment.committee.members.append(project_supervisor)
|
||||
db.session.add(enrollment)
|
||||
ta = TemporaryAvailability(**data, examination_schedule_id=examination_schedule_id)
|
||||
db.session.add(ta)
|
||||
db.session.commit()
|
||||
|
||||
return {"message": "You have just assigned yourself to committee!"}
|
||||
return {"message": "You have just assigned your free time!"}
|
||||
|
||||
|
||||
@bp.delete('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||
@bp.delete(
|
||||
"/<int:examination_schedule_id>/enrollments/<int:temporary_availability_id>/"
|
||||
)
|
||||
@bp.input(TemporaryProjectSupervisorSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def delete_yourself_from_committee(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||
def delete_your_free_time_from_examination_schedule(
|
||||
examination_schedule_id: int, temporary_availability_id: int, data: dict
|
||||
) -> dict:
|
||||
# this code will be removed
|
||||
project_supervisor = db.session.query(ProjectSupervisor).filter(ProjectSupervisor.id == data['id']).first()
|
||||
project_supervisor = (
|
||||
db.session.query(ProjectSupervisor)
|
||||
.filter(ProjectSupervisor.id == data["id"])
|
||||
.first()
|
||||
)
|
||||
if project_supervisor is None:
|
||||
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!")
|
||||
|
||||
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||
check_the_enrollments_has_just_started(enrollment.examination_schedule.start_date, "delete")
|
||||
if examination_schedule.open_enrollments != EnrollmentsMode.INIT.value:
|
||||
abort(400, "Enrollments has started or closed! You have been delayed!")
|
||||
|
||||
if not check_the_project_supervisor_is_in_committee(enrollment.id, project_supervisor.id):
|
||||
abort(400, "You are not assigned to this committee!")
|
||||
ta = TemporaryAvailability.query.filter(
|
||||
TemporaryAvailability.examination_schedule_id == examination_schedule_id,
|
||||
TemporaryAvailability.project_supervisor_id == project_supervisor.id,
|
||||
TemporaryAvailability.id == temporary_availability_id,
|
||||
).first()
|
||||
|
||||
enrollment.committee.members.remove(project_supervisor)
|
||||
if ta is None:
|
||||
abort(404, "Your free time doesn't exist!")
|
||||
|
||||
db.session.delete(ta)
|
||||
db.session.commit()
|
||||
return {"message": "You have just removed from committee!"}
|
||||
return {"message": "You have just removed your free time!"}
|
||||
|
||||
|
||||
@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}
|
||||
|
70
backend/app/project_supervisor/routes/project_grade_sheet.py
Normal file
70
backend/app/project_supervisor/routes/project_grade_sheet.py
Normal file
@ -0,0 +1,70 @@
|
||||
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,16 +1,33 @@
|
||||
from marshmallow import fields, validate, Schema
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
|
||||
# MessageSchema, CommitteeCreateSchema
|
||||
|
||||
class MessageSchema(Schema):
|
||||
message = fields.Str()
|
||||
class FreeTimeSchema(Schema):
|
||||
id = fields.Integer()
|
||||
start_date = fields.DateTime(required=True)
|
||||
end_date = fields.DateTime(required=True)
|
||||
|
||||
|
||||
class CommitteeCreateSchema(Schema):
|
||||
project_supervisor_id = fields.Integer(required=True) # temporary field it will be removed in the future
|
||||
class ListOfFreeTimesSchema(Schema):
|
||||
free_times = fields.List(fields.Nested(FreeTimeSchema))
|
||||
|
||||
|
||||
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
|
||||
class TemporaryProjectSupervisorSchema(Schema):
|
||||
id = fields.Integer(required=True)
|
||||
|
||||
|
||||
class ProjectSupervisorTermQuerySchema(Schema):
|
||||
id = fields.Integer(required=True)
|
||||
term = fields.Integer(required=True, validate=validate.OneOf([1, 2]))
|
||||
|
@ -1,57 +1,166 @@
|
||||
from datetime import datetime
|
||||
|
||||
from flask_sqlalchemy import BaseQuery
|
||||
|
||||
from ..dependencies import db
|
||||
from ..base.models import Person, Base
|
||||
from ..base.models import Base, Person
|
||||
from ..base.utils import order_by_column_name
|
||||
from ..examination_schedule.models import Enrollment
|
||||
from ..dependencies import db
|
||||
from ..examination_schedule.models import TermOfDefence
|
||||
|
||||
|
||||
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):
|
||||
__tablename__ = "groups"
|
||||
|
||||
name = db.Column(db.String(60), nullable=False)
|
||||
cdyd_kod = db.Column(db.String(60), default='2022/SZ')
|
||||
prz_kod = db.Column(db.String(60), default='06-DPRILI0')
|
||||
tzaj_kod = db.Column(db.String(60), default='LAB')
|
||||
project_supervisor_id = db.Column(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)
|
||||
points_for_second_term = db.Column(db.Integer, default=0, nullable=False)
|
||||
# enrollment = db.relationship('Enrollment', uselist=False, backref='group')
|
||||
cdyd_kod = db.Column(db.String(60), default="2022/SZ")
|
||||
prz_kod = db.Column(db.String(60), default="06-DPRILI0")
|
||||
tzaj_kod = db.Column(db.String(60), default="LAB")
|
||||
project_supervisor_id = db.Column(
|
||||
db.Integer, db.ForeignKey("project_supervisors.id")
|
||||
)
|
||||
project_supervisor = db.relationship("ProjectSupervisor", backref="groups")
|
||||
year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id"))
|
||||
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
|
||||
def search_by_name(cls, search_name: str = None) -> BaseQuery:
|
||||
group_query = cls.query
|
||||
def search_by_name(cls, year_group_id: int, search_name: str = None) -> BaseQuery:
|
||||
group_query = cls.query.filter(Group.year_group_id == year_group_id)
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Student(Person):
|
||||
class ProjectGradeSheet(Base):
|
||||
__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"
|
||||
|
||||
pesel = db.Column(db.String(11), default='')
|
||||
index = db.Column(db.Integer, primary_key=True)
|
||||
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
|
||||
group = db.relationship('Group', backref='students', lazy=True)
|
||||
mode = db.Column(db.Boolean, default=True, nullable=False) # True - stationary, False - non-stationary
|
||||
index = db.Column(db.Integer, nullable=False)
|
||||
groups = db.relationship(
|
||||
"Group", secondary=students_groups, back_populates="students"
|
||||
)
|
||||
year_group_id = db.Column(db.Integer, db.ForeignKey("year_groups.id"))
|
||||
year_group = db.relationship("YearGroup", backref="students")
|
||||
|
||||
__table__args = db.UniqueConstraint(
|
||||
"index", "year_group_id", name="uc_index_year_group_id"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None,
|
||||
mode: bool = None,
|
||||
order_by_first_name: str = None,
|
||||
order_by_last_name: str = None) -> BaseQuery:
|
||||
student_query = cls.query
|
||||
|
||||
if mode is not None:
|
||||
student_query = student_query.filter_by(mode=mode)
|
||||
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
||||
cls,
|
||||
year_group_id: int,
|
||||
fullname: str = None,
|
||||
order_by_first_name: str = None,
|
||||
order_by_last_name: str = None,
|
||||
) -> BaseQuery:
|
||||
student_query = cls.query.filter(Student.year_group_id == year_group_id)
|
||||
|
||||
if fullname is not None:
|
||||
student_query = student_query.filter((Student.first_name + ' ' + Student.last_name).like(f'{fullname}%'))
|
||||
student_query = student_query.filter(
|
||||
(Student.first_name + " " + Student.last_name).like(f"{fullname}%")
|
||||
)
|
||||
|
||||
student_query = order_by_column_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.first_name, order_by_first_name
|
||||
)
|
||||
student_query = order_by_column_name(
|
||||
student_query, Student.last_name, order_by_last_name
|
||||
)
|
||||
|
||||
return student_query
|
||||
|
@ -1,10 +0,0 @@
|
||||
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,9 +1,11 @@
|
||||
from flask import Blueprint
|
||||
|
||||
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
|
||||
|
||||
bp = Blueprint("students", __name__, url_prefix="/students")
|
||||
|
||||
bp.register_blueprint(registrations_bp)
|
||||
bp.register_blueprint(enrollments_bp)
|
||||
bp.register_blueprint(project_grade_sheet_bp)
|
||||
bp.register_blueprint(registrations_bp)
|
||||
|
@ -1,69 +1,170 @@
|
||||
import datetime
|
||||
|
||||
from apiflask import APIBlueprint
|
||||
from flask import abort
|
||||
|
||||
from ..schemas import MessageSchema, TemporaryStudentSchema
|
||||
from ...base.mode import EnrollmentsMode
|
||||
from ...base.schemas import MessageSchema
|
||||
from ...dependencies import db
|
||||
from ...examination_schedule.models import Enrollment
|
||||
from ..models import Student, Group
|
||||
from ...examination_schedule.models import ExaminationSchedule
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ...project_supervisor.query import get_enrollment_by_enrollment_and_examination_schedule_ids, \
|
||||
check_the_project_supervisor_is_in_committee
|
||||
from ..query import check_the_enrollments_has_just_started
|
||||
from ..models import Group, Student, TermOfDefence
|
||||
from ..schemas import (
|
||||
ExaminationScheduleListSchema,
|
||||
TemporaryStudentSchema,
|
||||
TermOfDefenceStudentListSchema,
|
||||
)
|
||||
|
||||
bp = APIBlueprint("enrollments", __name__, url_prefix="/")
|
||||
|
||||
|
||||
@bp.post('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||
@bp.post("/<int:examination_schedule_id>/enrollments/<int:term_of_defence_id>/")
|
||||
@bp.input(TemporaryStudentSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def assign_group_for_this_exam_date(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||
@bp.output(MessageSchema, status_code=201)
|
||||
def assign_your_group_to_term_of_defence(
|
||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
||||
) -> dict:
|
||||
# this code will be removed
|
||||
student = Student.query.filter(Student.index == data['student_index']).first()
|
||||
student = Student.query.filter(Student.id == data.get("student_id")).first()
|
||||
if student is None:
|
||||
abort(404, "Student doesn't exist!")
|
||||
abort(404, "Not found student!")
|
||||
################
|
||||
st = Student.query.join(Group).join(ProjectSupervisor).filter(Student.index == student.index).first()
|
||||
term_of_defence = (
|
||||
TermOfDefence.query.filter(
|
||||
TermOfDefence.id == term_of_defence_id,
|
||||
TermOfDefence.examination_schedule_id == examination_schedule_id,
|
||||
)
|
||||
.join(ExaminationSchedule)
|
||||
.first()
|
||||
)
|
||||
|
||||
enrollment = db.session.query(Enrollment.id).filter(Enrollment.group_id == st.group.id).first()
|
||||
if enrollment is not None:
|
||||
if term_of_defence is None or (ex := term_of_defence.examination_schedule) is None:
|
||||
abort(404, "Term of defence not found!")
|
||||
|
||||
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!")
|
||||
|
||||
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||
check_the_enrollments_has_just_started(enrollment.examination_schedule)
|
||||
td = (
|
||||
TermOfDefence.query.join(TermOfDefence.members_of_committee)
|
||||
.filter_by(id=group.project_supervisor_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
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):
|
||||
if td is None:
|
||||
abort(400, "Your project supervisor is not in committee!")
|
||||
|
||||
enrollment.group_id = st.group.id
|
||||
db.session.add(enrollment)
|
||||
term_of_defence.group_id = group.id
|
||||
db.session.add(term_of_defence)
|
||||
db.session.commit()
|
||||
return {"message": "You have just assigned the group for this exam date!"}
|
||||
|
||||
|
||||
@bp.delete('/<int:examination_schedule_id>/enrollments/<int:enrollment_id>/')
|
||||
@bp.delete("/<int:examination_schedule_id>/enrollments/<int:term_of_defence_id>/")
|
||||
@bp.input(TemporaryStudentSchema)
|
||||
@bp.output(MessageSchema)
|
||||
def delete_group_for_this_exam_date(examination_schedule_id: int, enrollment_id: int, data: dict) -> dict:
|
||||
def delete_your_group_from_term_of_defence(
|
||||
examination_schedule_id: int, term_of_defence_id: int, data: dict
|
||||
) -> dict:
|
||||
# this code will be removed
|
||||
student = Student.query.filter(Student.index == data['student_index']).first()
|
||||
student = Student.query.filter(Student.id == data.get("student_id")).first()
|
||||
if student is None:
|
||||
abort(404, "Student doesn't exist!")
|
||||
abort(404, "Not found student!")
|
||||
################
|
||||
|
||||
enrollment = get_enrollment_by_enrollment_and_examination_schedule_ids(examination_schedule_id, enrollment_id)
|
||||
term_of_defence = (
|
||||
TermOfDefence.query.join(ExaminationSchedule)
|
||||
.filter(TermOfDefence.id == term_of_defence_id)
|
||||
.filter(TermOfDefence.examination_schedule_id == examination_schedule_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
if student.group.id != enrollment.group_id:
|
||||
abort(400, "You are not assigned to this committee!")
|
||||
if term_of_defence is None:
|
||||
abort(404, "Not found term of defence!")
|
||||
ex = term_of_defence.examination_schedule
|
||||
|
||||
check_the_enrollments_has_just_started(enrollment.examination_schedule)
|
||||
group = (
|
||||
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!")
|
||||
|
||||
enrollment.group = None
|
||||
db.session.add(enrollment)
|
||||
term_of_defence.group_id = None
|
||||
db.session.add(term_of_defence)
|
||||
db.session.commit()
|
||||
|
||||
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}
|
||||
|
38
backend/app/students/routes/project_grade_sheet.py
Normal file
38
backend/app/students/routes/project_grade_sheet.py
Normal file
@ -0,0 +1,38 @@
|
||||
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,38 +1,34 @@
|
||||
from apiflask import APIBlueprint
|
||||
|
||||
from ...base.utils import paginate_models
|
||||
from ...dependencies import db
|
||||
from ...project_supervisor.models import ProjectSupervisor
|
||||
from ..models import Group
|
||||
from ...dependencies import db
|
||||
from ..schemas import ProjectSupervisorQuerySchema, ProjectSupervisorPaginationSchema
|
||||
from ...base.utils import paginate_models
|
||||
from ..schemas import ProjectSupervisorPaginationSchema, ProjectSupervisorQuerySchema
|
||||
|
||||
bp = APIBlueprint("registrations", __name__, url_prefix="/registrations")
|
||||
|
||||
|
||||
@bp.get('/')
|
||||
@bp.input(ProjectSupervisorQuerySchema, location='query')
|
||||
@bp.get("/<int:year_group_id>/")
|
||||
@bp.input(ProjectSupervisorQuerySchema, location="query")
|
||||
@bp.output(ProjectSupervisorPaginationSchema)
|
||||
def list_available_groups(query: dict) -> dict:
|
||||
mode = 0 if query.get('mode') else 1
|
||||
page = query.get('page')
|
||||
per_page = query.get('per_page')
|
||||
def list_available_groups(year_group_id: int, query: dict) -> dict:
|
||||
page = query.get("page")
|
||||
per_page = query.get("per_page")
|
||||
|
||||
available_groups = (ProjectSupervisor.limit_group - ProjectSupervisor.count_groups)
|
||||
ps_query = db.session.query(ProjectSupervisor, available_groups).join(Group, isouter=True)
|
||||
|
||||
if mode is not None:
|
||||
ps_query = ps_query.filter(ProjectSupervisor.mode != 1-mode)
|
||||
|
||||
ps_query = ps_query.group_by(ProjectSupervisor.id)
|
||||
available_groups = ProjectSupervisor.limit_group - db.func.count(Group.id)
|
||||
ps_query = (
|
||||
db.session.query(ProjectSupervisor, available_groups)
|
||||
.join(Group, isouter=True)
|
||||
.filter(ProjectSupervisor.year_group_id == year_group_id)
|
||||
.group_by(ProjectSupervisor.id)
|
||||
)
|
||||
|
||||
data = paginate_models(page, ps_query, per_page)
|
||||
|
||||
project_supervisors = []
|
||||
for project_supervisor, available_groups in data['items']:
|
||||
setattr(project_supervisor, 'available_groups', available_groups)
|
||||
for project_supervisor, available_groups in data["items"]:
|
||||
setattr(project_supervisor, "available_groups", available_groups)
|
||||
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,28 +1,186 @@
|
||||
from marshmallow import fields, Schema
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
POINTS = [0, 1, 3, 4]
|
||||
|
||||
|
||||
class ProjectSupervisorSchema(Schema):
|
||||
class ProjectSupervisorWithAvailableGroupsSchema(Schema):
|
||||
first_name = fields.Str()
|
||||
last_name = fields.Str()
|
||||
email = fields.Str()
|
||||
mode = fields.Integer()
|
||||
available_groups = fields.Integer()
|
||||
|
||||
|
||||
class ProjectSupervisorPaginationSchema(Schema):
|
||||
project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema))
|
||||
project_supervisors = fields.List(
|
||||
fields.Nested(ProjectSupervisorWithAvailableGroupsSchema)
|
||||
)
|
||||
max_pages = fields.Integer()
|
||||
|
||||
|
||||
class ProjectSupervisorQuerySchema(Schema):
|
||||
page = fields.Integer()
|
||||
per_page = fields.Integer()
|
||||
mode = fields.Boolean()
|
||||
|
||||
|
||||
class TemporaryStudentSchema(Schema):
|
||||
student_index = fields.Integer(required=True)
|
||||
student_id = fields.Integer(required=True)
|
||||
show_available = fields.Boolean(required=False)
|
||||
|
||||
|
||||
class MessageSchema(Schema):
|
||||
message = fields.Str()
|
||||
class ExaminationScheduleStudentSchema(Schema):
|
||||
id = fields.Integer()
|
||||
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 os
|
||||
import importlib
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from flask import current_app
|
||||
@ -7,8 +7,8 @@ from flask import current_app
|
||||
|
||||
def get_app_directories() -> list:
|
||||
directories = []
|
||||
src_dir = current_app.config['SRC_DIR']
|
||||
excluded_dirs = current_app.config['EXCLUDED_DIRS']
|
||||
src_dir = current_app.config["SRC_DIR"]
|
||||
excluded_dirs = current_app.config["EXCLUDED_DIRS"]
|
||||
|
||||
for dirname in os.listdir(src_dir):
|
||||
path = src_dir / dirname
|
||||
|
58
backend/config/weights_project_grade_sheet.json
Normal file
58
backend/config/weights_project_grade_sheet.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"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
|
||||
}
|
BIN
backend/fonts/Lato.ttf
Normal file
BIN
backend/fonts/Lato.ttf
Normal file
Binary file not shown.
@ -1,7 +1,9 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from app import create_app
|
||||
|
||||
load_dotenv()
|
||||
app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
load_dotenv()
|
||||
app = create_app()
|
||||
app.run()
|
||||
app.run(host="0.0.0.0")
|
||||
|
@ -3,9 +3,8 @@ from __future__ import with_statement
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from alembic import context
|
||||
from flask import current_app
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
@ -14,17 +13,17 @@ config = context.config
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
config.set_main_option(
|
||||
'sqlalchemy.url',
|
||||
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
||||
'%', '%%'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
"sqlalchemy.url",
|
||||
str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"),
|
||||
)
|
||||
target_metadata = current_app.extensions["migrate"].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
@ -45,9 +44,7 @@ def run_migrations_offline():
|
||||
|
||||
"""
|
||||
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():
|
||||
context.run_migrations()
|
||||
@ -65,20 +62,20 @@ def run_migrations_online():
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
if getattr(config.cmd_opts, "autogenerate", False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
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:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions['migrate'].configure_args
|
||||
**current_app.extensions["migrate"].configure_args
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
|
@ -1,68 +0,0 @@
|
||||
"""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 ###
|
315
backend/migrations/versions/5f2f440d05e2_.py
Normal file
315
backend/migrations/versions/5f2f440d05e2_.py
Normal file
@ -0,0 +1,315 @@
|
||||
"""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 ###
|
@ -1,64 +0,0 @@
|
||||
"""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 ###
|
@ -1,34 +0,0 @@
|
||||
"""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 ###
|
15
backend/nginx.conf
Normal file
15
backend/nginx.conf
Normal file
@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
2
backend/pyproject.toml
Normal file
2
backend/pyproject.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[tool.isort]
|
||||
profile = "black"
|
4
backend/pytest.ini
Normal file
4
backend/pytest.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
error
|
||||
ignore::UserWarning
|
@ -13,3 +13,7 @@ apiflask>=1.0.2,<1.1.0
|
||||
python-dotenv==0.21.0
|
||||
factory_boy>=3.2.1,<3.3.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
|
||||
|
10
backend/setup.cfg
Normal file
10
backend/setup.cfg
Normal file
@ -0,0 +1,10 @@
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
extend-ignore =
|
||||
E203,
|
||||
exclude =
|
||||
migrations,
|
||||
__pycache__,
|
||||
tests
|
||||
per-file-ignores = **/*/models.py:F401
|
||||
|
0
backend/tests/__init__.py
Normal file
0
backend/tests/__init__.py
Normal file
@ -1,18 +1,38 @@
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
from apiflask import APIFlask
|
||||
from flask import Flask
|
||||
from flask.ctx import AppContext
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
from app import create_app
|
||||
from app.dependencies import db
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def app() -> Generator[Flask, None, None]:
|
||||
def test_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")
|
||||
yield app
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
return app.test_client()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(app: Flask) -> FlaskClient:
|
||||
return app.test_client()
|
||||
def test_app_with_context() -> Generator[APIFlask, None, None]:
|
||||
app = create_app("testing")
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
|
4
backend/tests/data/students.csv
Normal file
4
backend/tests/data/students.csv
Normal file
@ -0,0 +1,4 @@
|
||||
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
|
|
4
backend/tests/data/students_column_name.csv
Normal file
4
backend/tests/data/students_column_name.csv
Normal file
@ -0,0 +1,4 @@
|
||||
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
|
|
4
backend/tests/data/students_column_type.csv
Normal file
4
backend/tests/data/students_column_type.csv
Normal file
@ -0,0 +1,4 @@
|
||||
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
|
|
186
backend/tests/factory.py
Normal file
186
backend/tests/factory.py
Normal file
@ -0,0 +1,186 @@
|
||||
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])
|
0
backend/tests/functional_tests/__init__.py
Normal file
0
backend/tests/functional_tests/__init__.py
Normal file
694
backend/tests/functional_tests/coordinator/test_enrollments.py
Normal file
694
backend/tests/functional_tests/coordinator/test_enrollments.py
Normal file
@ -0,0 +1,694 @@
|
||||
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
|
@ -0,0 +1,191 @@
|
||||
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
|
286
backend/tests/functional_tests/coordinator/test_groups.py
Normal file
286
backend/tests/functional_tests/coordinator/test_groups.py
Normal file
@ -0,0 +1,286 @@
|
||||
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",
|
||||
)
|
@ -0,0 +1,230 @@
|
||||
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",
|
||||
)
|
194
backend/tests/functional_tests/coordinator/test_students.py
Normal file
194
backend/tests/functional_tests/coordinator/test_students.py
Normal file
@ -0,0 +1,194 @@
|
||||
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",
|
||||
)
|
162
backend/tests/functional_tests/coordinator/test_year_group.py
Normal file
162
backend/tests/functional_tests/coordinator/test_year_group.py
Normal file
@ -0,0 +1,162 @@
|
||||
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
|
||||
)
|
@ -0,0 +1,101 @@
|
||||
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",
|
||||
)
|
@ -0,0 +1,229 @@
|
||||
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",
|
||||
)
|
0
backend/tests/functional_tests/students/__init__.py
Normal file
0
backend/tests/functional_tests/students/__init__.py
Normal file
342
backend/tests/functional_tests/students/test_enrollments.py
Normal file
342
backend/tests/functional_tests/students/test_enrollments.py
Normal file
@ -0,0 +1,342 @@
|
||||
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",
|
||||
)
|
@ -0,0 +1,91 @@
|
||||
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",
|
||||
)
|
@ -0,0 +1,55 @@
|
||||
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
|
@ -1,2 +0,0 @@
|
||||
def test_comparing_two_number():
|
||||
assert 1 == 1
|
0
backend/tests/unit_tests/__init__.py
Normal file
0
backend/tests/unit_tests/__init__.py
Normal file
199
backend/tests/unit_tests/test_utils.py
Normal file
199
backend/tests/unit_tests/test_utils.py
Normal file
@ -0,0 +1,199 @@
|
||||
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
|
32
backend/tests/unit_tests/test_validators.py
Normal file
32
backend/tests/unit_tests/test_validators.py
Normal file
@ -0,0 +1,32 @@
|
||||
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)
|
98
backend/tests/utils.py
Normal file
98
backend/tests/utils.py
Normal file
@ -0,0 +1,98 @@
|
||||
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