Compare commits

..

97 Commits

Author SHA1 Message Date
2029f6c343 Fixes after tests 2023-01-18 00:35:58 +01:00
339ba42fb4 Add license 2023-01-17 11:34:07 +01:00
dba2b24267 Update GradeCard styles, add points sum 2023-01-17 00:40:02 +01:00
dd333a6674 Remove pesel field from form 2023-01-16 23:23:55 +01:00
dominik24c
fe06023f82 update and add functional tests for project supervisor endpoints 2023-01-16 22:51:52 +01:00
dominik24c
f4f9072c3f update and add functional tests for students view 2023-01-16 21:59:49 +01:00
1bed24a1bc Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-16 20:10:08 +01:00
be8f065886 Add autosave, improve styles 2023-01-16 20:04:07 +01:00
dominik24c
61fcda82ab update functional test and add enrollments endpoints tests for coordinator view 2023-01-16 19:18:03 +01:00
e0f2711b21 Add error message 2023-01-16 18:13:24 +01:00
38a6c6a6be Remove unused table fields 2023-01-16 17:53:51 +01:00
e7bc1f3dae Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-16 17:24:22 +01:00
5aa8bfee45 Update studentId, update opening enrollments 2023-01-16 17:23:45 +01:00
dominik24c
191c68acf4 update Group model - add grades field, add set grade for group endpoint for coordinator view 2023-01-16 01:12:15 +01:00
dominik24c
527612d63e update group model - replace integer field by float field for points_for_first_term and point_for_second term, fix calculations of points for project grade sheet and fix unit tests 2023-01-16 00:35:25 +01:00
dominik24c
5fda4127cd update assign_your_group_to_term_of_defence for student view - add checking open enrollments status of examination schedule before add group to term of defenece 2023-01-14 18:09:04 +01:00
dominik24c
40274dc8d7 add black, isort and flake8 package, format code, fix examination schedule and enrollments functionality, add chairman of committee field for TermOfDefence entity 2023-01-14 17:38:03 +01:00
dominik24c
038f9da93d fix group, students, project_supervisor endpoints for coordinator, fix importing csv students endpoint and add copy project supervisors from last year group, refactor code 2023-01-14 01:37:31 +01:00
48be4c6345 Update student terms of def endpoint 2023-01-13 04:08:34 +01:00
7a4f3128ef Update GradeCard, improve styles 2023-01-13 03:59:54 +01:00
3d389b606f Login screen improvements 2023-01-12 20:38:36 +01:00
3be0d04f28 Add basic error handling 2023-01-11 23:27:02 +01:00
dominik24c
6be66634f3 add functional tests of examination schedule endpoints for coordinator view 2023-01-10 00:17:25 +01:00
dominik24c
945def46d8 add functional tests of registrations endpoint for students view 2023-01-09 22:56:46 +01:00
dominik24c
02677ca511 add functional tests of year-group endpoint for students view and fix importing models 2023-01-09 22:32:58 +01:00
3009ca01b3 Revert "Fix module errors"
This reverts commit 5ef94e857c.
2023-01-09 21:25:43 +01:00
e97e92e8b7 Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-09 21:19:02 +01:00
5ef94e857c Fix module errors 2023-01-09 21:18:40 +01:00
045c159733 Update prod deployment config 2023-01-09 19:35:41 +00:00
d286e93755 Update color, add refetch after edit students 2023-01-09 20:11:08 +01:00
59c3026602 Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-09 19:55:49 +01:00
dominik24c
ea39814b81 add functional tests of group endpoints for coordinator view 2023-01-08 20:26:41 +01:00
b4d07993ba Change default view to week 2023-01-08 20:22:13 +01:00
57d0c9bd55 Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-08 19:20:18 +01:00
6e90840af5 Update creating term of defences 2023-01-08 19:18:45 +01:00
cd5b7d4dbd Filter term of defences for students 2023-01-08 19:17:17 +01:00
dominik24c
89da56bcef add functional tests of students endpoint for coordinator view 2023-01-08 17:15:02 +01:00
dominik24c
badbad7cf9 add functionals test for coordinator view to year-group and project supervisors endpoints and add many term of defences endpoint for coordinator view 2023-01-06 22:35:47 +01:00
df107cfc7d Temp fix for showing supervisor groups 2023-01-06 19:41:42 +01:00
dd76f23a8c Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-06 19:26:14 +01:00
4383f016ff Remove points 2023-01-06 19:25:27 +01:00
213a16743d Add more details in group view, add editing group students 2023-01-06 19:24:02 +01:00
4f42e69a5d Remove unused import 2023-01-06 17:26:22 +01:00
7539a6a9fb Add commitee initials in event title 2023-01-06 17:25:45 +01:00
46aa2c0af4 Update term of defences endpoint to include groups 2023-01-06 17:25:21 +01:00
7d03b2b471 Fix coordinator id 2023-01-06 17:19:56 +01:00
dominik24c
0ddc602959 merge changes on development branch 2023-01-04 22:55:50 +01:00
dominik24c
008fb92583 add unit tests for backend 2023-01-04 22:51:58 +01:00
98e203efb2 Reload on year change 2023-01-03 23:01:28 +01:00
d01fa307c6 Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2023-01-03 22:51:53 +01:00
7fe646a875 Add studentId 2023-01-03 22:51:07 +01:00
5d999ea9c8 Fix adding group limits when creating supervisor 2023-01-03 22:26:06 +01:00
dominik24c
c448f8a642 fix list project supervisors and students endpoints for coordinator view 2023-01-03 21:12:29 +01:00
0a7dd7e364 Add workload view 2023-01-02 23:50:56 +01:00
f3068f9797 Fix workload endpoint output 2023-01-02 23:17:37 +01:00
1b4318cece Add time picker 2022-12-30 22:08:00 +01:00
b98cfcfeb9 Replace luxon with dayjs 2022-12-30 21:39:58 +01:00
111f63e395 Update ids 2022-12-30 21:21:05 +01:00
patrol16d
ec5733213e change way od storage with user is loin in 2022-12-16 06:50:55 +01:00
patrol16d
075e792025 bug fix - bad merge 2022-12-16 04:09:27 +01:00
patrol16d
14a2fbc755 Merge branch 'development' of https://git.wmi.amu.edu.pl/s459309/system-pri into development 2022-12-16 03:35:18 +01:00
patrol16d
df7e2a02d1 Merge from develop into new commit 2022-12-16 03:28:27 +01:00
patrol16d
6ae0854fb9 add colors to different modes and stationaryMode to supervisors 2022-12-16 03:19:26 +01:00
bd256e0f44 Fix routes 2022-12-16 01:43:00 +01:00
25355be9ff Add leader to year group after create 2022-12-16 01:31:36 +01:00
817095cc3f Add one column in pdf export 2022-12-16 01:01:52 +01:00
292ef17be6 Fix export/import of students, add grade card view 2022-12-16 01:01:07 +01:00
dominik24c
5d94bba15f add points for terms in group view for coordinator 2022-12-15 22:32:01 +01:00
dominik24c
75deaab904 merge with frontend schedule view updates 2022-12-15 20:46:15 +01:00
dominik24c
3457f76b17 add project grade sheet view for students and project supervisor 2022-12-15 20:44:03 +01:00
29bcac33c0 Add event title 2022-11-25 11:54:11 +01:00
3cc763924e Fix hardcoded year group 2022-11-25 11:50:16 +01:00
dominik24c
fc3d84aa80 update generating of term of defences endpoint - optimize insert query 2022-11-24 23:01:35 +01:00
faa00ab353 Add availabilities for coordinator 2022-11-24 20:31:47 +01:00
ee8563fedb Update schedule components 2022-11-24 20:00:09 +01:00
dd116962e6 Update get supervisors request 2022-11-24 18:44:43 +01:00
dominik24c
5c2e5dcba3 update endpoint of generating term of defence and add endpoint of clear all term of defences 2022-11-23 23:45:51 +01:00
dominik24c
62599214dc add endpoints list of assigned group to term of defence and functionality of add, update, delete group to term of defence by coordinator 2022-11-19 20:43:05 +01:00
4d21532b1f Fix query in assigning group to term of defence 2022-11-18 11:39:12 +01:00
dominik24c
bd99609de2 add docker configuration for production 2022-11-18 00:20:50 +01:00
b2dfccf869 Fix if statement 2022-11-17 23:37:05 +01:00
22815bb405 Add creating year groups 2022-11-17 22:21:59 +01:00
9c7ae692f5 Fix adding group 2022-11-17 21:49:55 +01:00
2120ab9690 Update login, update api requests, add schedule view for supervisors 2022-11-17 21:36:36 +01:00
dominik24c
8dcb7528f9 update endpoint of downloading examination schedule in pdf format for coordinator view and fix font issues for polish characters 2022-11-17 20:33:20 +01:00
dominik24c
2e3a7b24f0 add endpoint for generate term of defence for coordinator view 2022-11-17 18:39:45 +01:00
dominik24c
0722eb9648 update endpoints for download and upload students list in csv file 2022-11-17 14:56:19 +01:00
dominik24c
247dfe3f31 merge 2022-11-17 10:24:44 +01:00
dominik24c
f71c917c61 merge branch and add student_groups table and change logic of endpoint what use group model 2022-11-17 10:21:36 +01:00
3125a696e4 Add base_url env 2022-11-16 23:15:03 +01:00
b0e48d3703 Add calendar translation 2022-11-16 22:40:51 +01:00
76218a92ca Update requests after api changes 2022-11-16 22:31:29 +01:00
b881d5a056 Add min max calendar hours, add event colors 2022-11-16 22:31:29 +01:00
dominik24c
afffc78537 update workloads statistics endpoint for coordinator 2022-11-16 21:06:33 +01:00
dominik24c
856372e5ab update enrollments endpoints for coordinator, project supervisors and students 2022-11-16 20:42:40 +01:00
dominik24c
eea98dd225 add year group and change logic of endpoints v2 2022-11-12 16:18:07 +01:00
dominik24c
82807b7c2e add year group and change logic of endpoints 2022-11-12 16:17:05 +01:00
155 changed files with 12530 additions and 1666 deletions

201
LICENSE Normal file
View 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.

View File

@ -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

View File

@ -13,4 +13,3 @@ COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

View 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
View 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

View File

@ -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

View File

@ -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

View File

@ -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
View 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"

View File

@ -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)

View File

@ -0,0 +1,5 @@
from marshmallow import Schema, fields
class MessageSchema(Schema):
message = fields.Str()

View File

@ -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"]
)

View File

@ -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"""

View File

@ -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

View File

@ -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
"""

View File

@ -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):

View File

@ -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

View File

@ -1,5 +0,0 @@
# from ..base.models import Base
#
#
# class Coordinator(Base):
# __tablename__ = 'coordinators'

View 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!"}

View File

@ -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)

View File

@ -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!"}

View File

@ -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

View File

@ -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!"}

View File

@ -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!"}

View File

@ -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

View File

@ -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}

View 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!"}

View File

@ -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

View File

@ -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)
)

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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

View 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()

View File

@ -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)

View File

@ -1,6 +1,5 @@
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
ma = Marshmallow()

View File

@ -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

View File

@ -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"
)

View File

@ -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)

View File

@ -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)

View File

@ -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}

View File

@ -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))

View File

@ -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"]}

View File

@ -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])

View File

@ -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

View File

@ -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!")

View 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()

View File

@ -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)

View File

@ -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}

View 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!"}

View File

@ -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]))

View File

@ -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

View File

@ -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.")

View File

@ -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)

View File

@ -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}

View 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)

View File

@ -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"]}

View File

@ -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()

View File

@ -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

View 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

Binary file not shown.

View File

@ -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")

View File

@ -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():

View File

@ -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 ###

View 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 ###

View File

@ -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 ###

View File

@ -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
View 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
View File

@ -0,0 +1,2 @@
[tool.isort]
profile = "black"

4
backend/pytest.ini Normal file
View File

@ -0,0 +1,4 @@
[pytest]
filterwarnings =
error
ignore::UserWarning

View File

@ -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
View File

@ -0,0 +1,10 @@
[flake8]
max-line-length = 88
extend-ignore =
E203,
exclude =
migrations,
__pycache__,
tests
per-file-ignores = **/*/models.py:F401

View File

View 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

View 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
1 NAZWISKO IMIE INDEKS PESEL EMAIL
2 Drzewiński Patryk 452790 11111111111 patdrz1@st.amu.edu.pl
3 Skowronek Adam 452791 22222222222 adasko8@st.amu.edu.pl
4 Kuzmenko Mariia 452792 33333333333 markuz5@st.amu.edu.pl

View 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
1 NAZWISKO NOTHING INDEKS PESEL EMAIL
2 Drzewiński Patryk 452790 11111111111 patdrz1@st.amu.edu.pl
3 Skowronek Adam 452791 22222222222 adasko8@st.amu.edu.pl
4 Kuzmenko Mariia 452792 33333333333 markuz5@st.amu.edu.pl

View 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
1 NAZWISKO IMIE INDEKS PESEL EMAIL
2 0030 Patryk 452790 11111111111 patdrz1@st.amu.edu.pl
3 4939 Adam 452791 22222222222 adasko8@st.amu.edu.pl
4 3232 Mariia 452792 33333333333 markuz5@st.amu.edu.pl

186
backend/tests/factory.py Normal file
View 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])

View 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

View File

@ -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

View 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",
)

View File

@ -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",
)

View 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",
)

View 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
)

View File

@ -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",
)

View File

@ -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",
)

View 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",
)

View File

@ -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",
)

View File

@ -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

View File

@ -1,2 +0,0 @@
def test_comparing_two_number():
assert 1 == 1

View File

View 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

View 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
View 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