Compare commits
20 Commits
main
...
lena_backe
| Author | SHA1 | Date | |
|---|---|---|---|
| d4ab5005a4 | |||
| dad73a03f5 | |||
| c37dbbf68c | |||
| 86eb94436f | |||
| d879fcfde7 | |||
| 23cedfe4e1 | |||
| 7ba2e811f5 | |||
| a58a7219ee | |||
| bf4a0c1100 | |||
| 611ea95c12 | |||
| 2f521dd674 | |||
| 8ca935b1d5 | |||
| 988e6db8c4 | |||
| 24da47b509 | |||
| c4889aaa73 | |||
| 88f37d6c46 | |||
| 42636941e8 | |||
| 81b4238394 | |||
| d2f61d8cb5 | |||
| aaa4d26a3b |
16
.env.example
16
.env.example
@@ -1,16 +0,0 @@
|
||||
# .env
|
||||
|
||||
MYSQL_DATABASE=app
|
||||
MYSQL_USER=appuser
|
||||
MYSQL_PASSWORD=secret
|
||||
MYSQL_ROOT_PASSWORD=supersecret
|
||||
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DRIVER=aiomysql
|
||||
DB_NAME=app
|
||||
DB_USER=appuser
|
||||
DB_PASSWORD=secret
|
||||
|
||||
SECRET_KEY=supersecretkey
|
||||
ALGORITHM=HS256
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
||||
.env
|
||||
20
Makefile
20
Makefile
@@ -1,20 +0,0 @@
|
||||
up:
|
||||
docker compose up -d
|
||||
|
||||
down:
|
||||
docker compose down
|
||||
|
||||
remove:
|
||||
docker compose down -v
|
||||
|
||||
restart:
|
||||
docker compose restart
|
||||
|
||||
logs:
|
||||
docker compose logs
|
||||
|
||||
monitor:
|
||||
docker compose logs -f backend
|
||||
|
||||
build:
|
||||
docker compose build
|
||||
@@ -1,90 +0,0 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
|
||||
# CI
|
||||
.codeclimate.yml
|
||||
.travis.yml
|
||||
.taskcluster.yml
|
||||
|
||||
# Docker
|
||||
docker-compose.y[a]ml
|
||||
compose.y[a]ml
|
||||
Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Virtual environment
|
||||
.env
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
@@ -1,9 +0,0 @@
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DRIVER=mysql+aiomysql
|
||||
DB_NAME=app
|
||||
DB_USER=appuser
|
||||
DB_PASSWORD=secret
|
||||
|
||||
SECRET_KEY=gV64m9aIzFG4qpgVphvQbPQrtAO0nM-7YwwOvu0XPt5KJOjAy4AfgLkqJXYEt
|
||||
ALGORITHM=HS256
|
||||
@@ -1,2 +1,2 @@
|
||||
[flake8]
|
||||
max-line-length=110
|
||||
max-line-length = 101
|
||||
|
||||
16
backend/.gitignore
vendored
16
backend/.gitignore
vendored
@@ -106,7 +106,6 @@ ipython_config.py
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
#poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
@@ -168,19 +167,6 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
@@ -188,7 +174,7 @@ cython_debug/
|
||||
.pypirc
|
||||
|
||||
# Cursor
|
||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
|
||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||
# refer to https://docs.cursor.com/context/ignore-files
|
||||
.cursorignore
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
FROM python:3.12.3-slim
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Установка зависимостей для MySQL
|
||||
RUN apt-get update && apt-get install -y \
|
||||
default-libmysqlclient-dev \
|
||||
gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Установка Poetry
|
||||
RUN pip install poetry
|
||||
|
||||
# Копируем зависимости
|
||||
COPY pyproject.toml poetry.lock* ./
|
||||
|
||||
# Устанавливаем зависимости
|
||||
RUN poetry config virtualenvs.create false && \
|
||||
poetry install --no-root --no-interaction --no-ansi
|
||||
|
||||
# Копируем остальные файлы
|
||||
COPY . .
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
up:
|
||||
docker compose up -d
|
||||
|
||||
down:
|
||||
docker compose down
|
||||
|
||||
remove:
|
||||
docker compose down -v
|
||||
|
||||
dev:
|
||||
uvicorn app.main:app --reload
|
||||
|
||||
makemigration:
|
||||
alembic revision --autogenerate
|
||||
|
||||
migrate:
|
||||
alembic upgrade head
|
||||
@@ -1,216 +1 @@
|
||||
# Шаблон приложения FastAPI с аутентификацией и авторизацией
|
||||
|
||||
Этот проект представляет собой готовый шаблон для разработки масштабируемых веб-приложений на основе **FastAPI** с
|
||||
полноценной системой аутентификации и авторизации. Проект включает модульную архитектуру, поддерживает гибкое
|
||||
логирование с **loguru**, и взаимодействие с базой данных через **SQLAlchemy** с асинхронной поддержкой. Система
|
||||
миграций **Alembic** упрощает работу со схемой базы данных.
|
||||
|
||||
## Стек технологий
|
||||
|
||||
- **Веб-фреймворк**: FastAPI
|
||||
- **ORM**: SQLAlchemy с асинхронной поддержкой через aiosqlite
|
||||
- **База данных**: SQLite (легко заменяемая на другую SQL-СУБД)
|
||||
- **Система миграций**: Alembic
|
||||
- **Авторизация/Аутентификация**: bcrypt для хеширования паролей, python-jose для защиты данных с использованием JWT
|
||||
|
||||
## Зависимости проекта
|
||||
|
||||
- `fastapi[all]==0.115.0` - высокопроизводительный веб-фреймворк
|
||||
- `pydantic==2.9.2` - валидация данных
|
||||
- `uvicorn==0.31.0` - ASGI-сервер
|
||||
- `jinja2==3.1.4` - шаблонизатор
|
||||
- `SQLAlchemy==2.0.35` - ORM для работы с базами данных
|
||||
- `aiosqlite==0.20.0` - асинхронная поддержка SQLite
|
||||
- `alembic==1.13.3` - управление миграциями базы данных
|
||||
- `bcrypt==4.0.1` и `passlib[bcrypt]==1.7.4` - хеширование паролей
|
||||
- `python-jose==3.3.0` - работа с JWT токенами
|
||||
- `loguru==0.7.2` - красивое и удобное логирование
|
||||
|
||||
## Структура проекта
|
||||
|
||||
Проект построен с учётом модульной архитектуры, что позволяет легко расширять приложение и упрощает его поддержку.
|
||||
Каждый модуль отвечает за отдельные задачи, такие как авторизация или управление данными.
|
||||
|
||||
### Основная структура проекта
|
||||
|
||||
```
|
||||
├── app/
|
||||
│ ├── auth/ # Модуль авторизации и аутентификации
|
||||
│ │ ├── dao.py # Data Access Object для работы с БД
|
||||
│ │ ├── models.py # Модели данных для авторизации
|
||||
│ │ ├── router.py # Роутеры FastAPI для маршрутизации
|
||||
│ │ ├── schemas.py # Схемы для валидации данных
|
||||
│ │ └── utils.py # Вспомогательные функции для авторизации
|
||||
│ ├── dao/ # Общие DAO для приложения
|
||||
│ │ ├── database.py # Подключение к базе данных и управление сессиями
|
||||
│ │ └── base.py # Базовый класс DAO для работы с БД
|
||||
│ ├── dependencies # Зависимости в проекте
|
||||
│ │ ├── auth_dep.py # Зависимости для авторизации
|
||||
│ │ └── dao_dep.py # Зависимости для сессий SQLAlchemy
|
||||
│ ├── migration/ # Миграции базы данных
|
||||
│ │ ├── versions/ # Файлы миграций
|
||||
│ │ ├── env.py # Настройки среды для Alembic
|
||||
│ │ ├── README # Документация по миграциям
|
||||
│ │ └── script.py.mako # Шаблон для генерации миграций
|
||||
│ ├── static/ # Статические файлы приложения
|
||||
│ │ └── .gitkeep # Пустой файл для сохранения папки в Git
|
||||
│ ├── config.py # Конфигурация приложения
|
||||
│ ├── exceptions.py # Исключения для обработки ошибок
|
||||
│ ├── main.py # Основной файл для запуска приложения
|
||||
├── data/ # Папка для хранения файла БД
|
||||
│ └── db.sqlite3 # Файл базы данных SQLite
|
||||
├── .env # Конфигурация окружения
|
||||
├── alembic.ini # Конфигурация Alembic
|
||||
├── README.md # Документация проекта
|
||||
└── requirements.txt # Зависимости проекта
|
||||
```
|
||||
|
||||
Обновленный раздел с подробным описанием основных модулей:
|
||||
|
||||
---
|
||||
|
||||
### Основные модули
|
||||
|
||||
#### **app/auth** - Модуль для аутентификации и авторизации
|
||||
|
||||
Модуль отвечает за управление процессами аутентификации (входа пользователей) и авторизации (проверки доступа).
|
||||
Основные файлы:
|
||||
|
||||
- **`dao.py`**: Объект доступа к данным пользователей. Содержит методы для работы с базой данных (создание, обновление,
|
||||
поиск пользователей и т. д.).
|
||||
- **`dependencies.py`**: Внедрение зависимостей, таких как проверка токенов и авторизация для защищённых маршрутов.
|
||||
- **`models.py`**: Определяет ORM-модели данных для пользователей (например, таблица Users в базе данных).
|
||||
- **`router.py`**: Роутер для маршрутизации запросов, связанных с аутентификацией. Определяет эндпоинты для входа,
|
||||
регистрации и проверки доступа.
|
||||
- **`schemas.py`**: Определяет Pydantic-схемы для валидации входных данных и структуры ответов (например, формат данных
|
||||
для регистрации пользователей).
|
||||
- **`utils.py`**: Вспомогательные функции для работы с токенами (создание, проверка JWT) и шифрование паролей.
|
||||
|
||||
---
|
||||
|
||||
#### **app/dao** - Базовый слой доступа к данным (Data Access Layer)
|
||||
|
||||
Модуль содержит абстракции для работы с базой данных. Используется для управления подключениями и реализации
|
||||
CRUD-операций.
|
||||
|
||||
- **`base.py`**: Базовый класс DAO, предоставляющий общие методы для работы с базой данных, такие как добавление,
|
||||
обновление, удаление и поиск записей.
|
||||
- **`database.py`**: Отвечает за подключение к базе данных, управление сессиями SQLAlchemy, а также создание
|
||||
асинхронного подключения (например, через `aiosqlite`).
|
||||
|
||||
---
|
||||
|
||||
#### **app/migration** - Управление миграциями базы данных с Alembic
|
||||
|
||||
Модуль упрощает управление схемой базы данных и позволяет безопасно вносить изменения.
|
||||
|
||||
- **`versions/`**: Хранятся файлы миграций, автоматически создаваемые Alembic.
|
||||
- **`env.py`**: Основной файл конфигурации для Alembic. Определяет подключение к базе данных и взаимодействие с ORM.
|
||||
- **`script.py.mako`**: Шаблон для генерации новых файлов миграций.
|
||||
|
||||
---
|
||||
|
||||
#### **app/dependencies** - Управление миграциями базы данных с Alembic
|
||||
|
||||
Модуль содержит зависимости, которые используются в проекте.
|
||||
|
||||
- **`auth_dep.py`**: Зависимости, связанные с авторизацией пользователя в системе
|
||||
- **`dao_dep.py`**: Зависимости, связанные с управллением сессией SQLAlchemy и с работой с дочерними классами BaseDao
|
||||
|
||||
---
|
||||
|
||||
#### **config.py** - Настройки и конфигурация приложения
|
||||
|
||||
- Определяет параметры приложения, загружаемые из файла `.env`. Например:
|
||||
- `SECRET_KEY`: Секретный ключ для подписания JWT.
|
||||
- `ALGORITHM`: Алгоритм хеширования токенов.
|
||||
- `DATABASE_URL`: URL для подключения к базе данных.
|
||||
- Обеспечивает удобное управление конфигурацией для разных окружений (локальное, тестовое, продакшн).
|
||||
|
||||
---
|
||||
|
||||
#### **main.py** - Основной файл для запуска приложения
|
||||
|
||||
- **Инициализация приложения**: Настраивает FastAPI-приложение, включая параметры, такие как название, версия, и
|
||||
описание.
|
||||
- **Подключение роутеров**: Регистрирует маршруты, определённые в модулях приложения, например:
|
||||
- `app.auth.router` для маршрутов авторизации.
|
||||
- Любые дополнительные модули (например, `app.users.router`).
|
||||
- **Настройка зависимостей**: Внедряет глобальные зависимости, такие как подключение к базе данных или параметры
|
||||
конфигурации.
|
||||
- **Настройка middleware**: Добавляет промежуточные слои для обработки запросов (например, CORS, сжатие, обработка
|
||||
ошибок).
|
||||
- **Обработка ошибок**: Определяет глобальные обработчики исключений, чтобы возвращать понятные ответы при возникновении
|
||||
ошибок (например, 401 Unauthorized или 500 Internal Server Error).
|
||||
- **Запуск сервера**: Используется для старта приложения с помощью ASGI-сервера (Uvicorn).
|
||||
|
||||
## Настройка аутентификации и авторизации
|
||||
|
||||
Для аутентификации используется JSON Web Token (JWT) с bcrypt для хеширования паролей и python-jose для генерации и
|
||||
проверки токенов. Это обеспечивает безопасное хранение данных и защищает API-эндпоинты.
|
||||
|
||||
## Запуск приложения
|
||||
|
||||
1. Клонируйте репозиторий:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Yakvenalex/FastApiWithAuthSample.git .
|
||||
```
|
||||
|
||||
2. Установите зависимости:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Создайте и настройте `.env` файл:
|
||||
|
||||
```env
|
||||
SECRET_KEY=supersecretkey
|
||||
ALGORITHM=HS256
|
||||
```
|
||||
|
||||
4. Запустите приложение с Uvicorn:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app --reload --port 8005
|
||||
```
|
||||
|
||||
При необходимости замените port на нужный.
|
||||
|
||||
## Миграции базы данных
|
||||
|
||||
1. Инициализируйте Alembic:
|
||||
|
||||
```bash
|
||||
cd app
|
||||
alembic init -t async migration
|
||||
```
|
||||
|
||||
Затем переместите `alembic.ini` в корень проекта.
|
||||
|
||||
2. В `alembic.ini` установите `script_location` как `app/migration`.
|
||||
|
||||
3. Создайте миграцию:
|
||||
|
||||
```bash
|
||||
alembic revision --autogenerate -m "Initial migration"
|
||||
```
|
||||
|
||||
4. Примените миграции:
|
||||
|
||||
```bash
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
## Лучшие практики
|
||||
|
||||
- Разделяйте функциональность приложения на модули для удобства тестирования и поддержки.
|
||||
- Обрабатывайте ошибки с четкими ответами и HTTP-кодами.
|
||||
- Проводите миграции с Alembic для управления схемой базы данных.
|
||||
- Используйте переменные окружения для безопасного хранения конфиденциальных данных.
|
||||
|
||||
---
|
||||
|
||||
Этот шаблон является мощной и удобной основой для разработки приложений на FastAPI с поддержкой аутентификации,
|
||||
авторизации и структурированной архитектуры, готовой к масштабированию.
|
||||
# Бекенд
|
||||
|
||||
@@ -1,84 +1,8 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts.
|
||||
# Use forward slashes (/) also on windows to provide an os agnostic path
|
||||
script_location = app/migration
|
||||
script_location = alembic
|
||||
; sqlalchemy.url = mysql+pymysql://myappuser:mypassword@db/myapp
|
||||
sqlalchemy.url = mysql+pymysql://myappuser:mypassword@localhost/myapp
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to ZoneInfo()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to migration/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:migration/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
|
||||
1
backend/alembic/README
Normal file
1
backend/alembic/README
Normal file
@@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
||||
52
backend/alembic/env.py
Normal file
52
backend/alembic/env.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
from alembic import context
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Добавляем путь к проекту
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from app.models import Base
|
||||
from app.config import settings
|
||||
|
||||
config = context.config
|
||||
config.set_main_option("sqlalchemy.url", settings.database_url)
|
||||
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -19,8 +19,10 @@ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
${downgrades if downgrades else "pass"}
|
||||
46
backend/alembic/versions/40906c8e083f_create_users.py
Normal file
46
backend/alembic/versions/40906c8e083f_create_users.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""create users
|
||||
|
||||
Revision ID: 40906c8e083f
|
||||
Revises:
|
||||
Create Date: 2025-05-18 18:11:23.585211
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '40906c8e083f'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('email', sa.String(length=255), nullable=True),
|
||||
sa.Column('username', sa.String(length=50), nullable=True),
|
||||
sa.Column('hashed_password', sa.String(length=255), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
||||
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_id'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||
op.drop_table('users')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1 +0,0 @@
|
||||
from .router import router
|
||||
@@ -1,72 +0,0 @@
|
||||
from fastapi import APIRouter, Response, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.utils import authenticate_user, set_tokens
|
||||
from app.dao import UsersDAO
|
||||
from app.dependencies.auth_dep import (
|
||||
get_current_user,
|
||||
check_refresh_token,
|
||||
)
|
||||
from app.dependencies.dao_dep import get_session_with_commit, get_session_without_commit
|
||||
from app.exceptions import UserAlreadyExistsException, IncorrectEmailOrPasswordException
|
||||
from app.models import User
|
||||
from app.schemas import UserCreateSchema, UserAuthSchema, EmailSchema, UserAddDBSchema, UserSchema
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/register")
|
||||
async def register_user(
|
||||
user_data: UserCreateSchema, session: AsyncSession = Depends(get_session_with_commit)
|
||||
) -> dict:
|
||||
# Проверка существования пользователя
|
||||
user_dao = UsersDAO(session)
|
||||
|
||||
existing_user = await user_dao.find_one_or_none(
|
||||
filters=EmailSchema(email=user_data.email)
|
||||
)
|
||||
if existing_user:
|
||||
raise UserAlreadyExistsException
|
||||
|
||||
# Подготовка данных для добавления
|
||||
user_data_dict = user_data.model_dump()
|
||||
|
||||
# Добавление пользователя
|
||||
await user_dao.add(values=UserAddDBSchema(**user_data_dict))
|
||||
|
||||
return {"message": "Вы успешно зарегистрированы!"}
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
async def auth_user(
|
||||
response: Response,
|
||||
user_data: UserAuthSchema,
|
||||
session: AsyncSession = Depends(get_session_without_commit),
|
||||
) -> dict:
|
||||
users_dao = UsersDAO(session)
|
||||
user = await users_dao.find_one_or_none(filters=EmailSchema(email=user_data.email))
|
||||
|
||||
if not (user and await authenticate_user(user=user, password=user_data.password)):
|
||||
raise IncorrectEmailOrPasswordException
|
||||
set_tokens(response, user.id)
|
||||
return {"message": "Авторизация успешна!"}
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(response: Response):
|
||||
response.delete_cookie("user_access_token")
|
||||
response.delete_cookie("user_refresh_token")
|
||||
return {"message": "Пользователь успешно вышел из системы"}
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def get_me(user_data: User = Depends(get_current_user)) -> UserSchema:
|
||||
return UserSchema.model_validate(user_data)
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def process_refresh_token(
|
||||
response: Response, user: User = Depends(check_refresh_token)
|
||||
):
|
||||
set_tokens(response, user.id)
|
||||
return {"message": "Токены успешно обновлены"}
|
||||
@@ -1,70 +0,0 @@
|
||||
from passlib.context import CryptContext
|
||||
from jose import jwt
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi.responses import Response
|
||||
from app.config import settings
|
||||
|
||||
|
||||
def create_tokens(data: dict) -> dict:
|
||||
# Текущее время в UTC
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# AccessToken - 30 минут
|
||||
access_expire = now + timedelta(minutes=30)
|
||||
access_payload = data.copy()
|
||||
access_payload.update({"exp": int(access_expire.timestamp()), "type": "access"})
|
||||
access_token = jwt.encode(
|
||||
access_payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM
|
||||
)
|
||||
|
||||
# RefreshToken - 7 дней
|
||||
refresh_expire = now + timedelta(days=7)
|
||||
refresh_payload = data.copy()
|
||||
refresh_payload.update({"exp": int(refresh_expire.timestamp()), "type": "refresh"})
|
||||
refresh_token = jwt.encode(
|
||||
refresh_payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM
|
||||
)
|
||||
return {"access_token": access_token, "refresh_token": refresh_token}
|
||||
|
||||
|
||||
async def authenticate_user(user, password):
|
||||
if (
|
||||
not user
|
||||
or verify_password(plain_password=password, hashed_password=user.password)
|
||||
is False
|
||||
):
|
||||
return None
|
||||
return user
|
||||
|
||||
|
||||
def set_tokens(response: Response, user_id: int):
|
||||
new_tokens = create_tokens(data={"sub": str(user_id)})
|
||||
access_token = new_tokens.get("access_token")
|
||||
refresh_token = new_tokens.get("refresh_token")
|
||||
|
||||
response.set_cookie(
|
||||
key="user_access_token",
|
||||
value=access_token,
|
||||
httponly=True,
|
||||
secure=True,
|
||||
samesite="lax",
|
||||
)
|
||||
|
||||
response.set_cookie(
|
||||
key="user_refresh_token",
|
||||
value=refresh_token,
|
||||
httponly=True,
|
||||
secure=True,
|
||||
samesite="lax",
|
||||
)
|
||||
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
@@ -1,24 +1,14 @@
|
||||
import os
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
BASE_DIR: str = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
if os.path.exists(os.path.join(BASE_DIR, ".env")):
|
||||
import dotenv
|
||||
dotenv.load_dotenv(os.path.join(BASE_DIR, ".env"))
|
||||
|
||||
class Settings(BaseSettings):
|
||||
BASE_DIR: str = BASE_DIR
|
||||
DB_HOST: str = os.getenv("DB_HOST")
|
||||
DB_PORT: str = os.getenv("DB_PORT")
|
||||
DB_DRIVER: str = os.getenv("DB_DRIVER")
|
||||
DB_NAME: str = os.getenv("DB_NAME")
|
||||
DB_USER: str = os.getenv("DB_USER")
|
||||
DB_PASSWORD: str = os.getenv("DB_PASSWORD")
|
||||
SECRET_KEY: str = os.getenv("SECRET_KEY")
|
||||
ALGORITHM: str = os.getenv("ALGORITHM")
|
||||
database_url: str = "mysql+pymysql://myappuser:mypassword@localhost/myapp"
|
||||
secret_key: str = "your-secret-key"
|
||||
algorithm: str = "HS256"
|
||||
access_token_expire_minutes: int = 30
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
|
||||
# Получаем параметры для загрузки переменных среды
|
||||
settings = Settings()
|
||||
database_url = f"{settings.DB_DRIVER}://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}/{settings.DB_NAME}"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from .database import async_session_maker
|
||||
|
||||
from .user import UsersDAO
|
||||
@@ -1,176 +0,0 @@
|
||||
from typing import List, TypeVar, Generic, Type
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import update as sqlalchemy_update, delete as sqlalchemy_delete, func
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
from app.models import Model
|
||||
|
||||
T = TypeVar("T", bound=Model)
|
||||
|
||||
|
||||
class BaseDAO(Generic[T]):
|
||||
model: Type[T] = None
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self._session = session
|
||||
if self.model is None:
|
||||
raise ValueError("Модель должна быть указана в дочернем классе")
|
||||
|
||||
async def find_one_or_none_by_id(self, data_id: int):
|
||||
try:
|
||||
query = select(self.model).filter_by(id=data_id)
|
||||
result = await self._session.execute(query)
|
||||
record = result.scalar_one_or_none()
|
||||
log_message = (
|
||||
f"Запись {self.model.__name__} с ID {data_id} "
|
||||
+ "{'найдена' if record else 'не найдена'}."
|
||||
)
|
||||
logger.info(log_message)
|
||||
return record
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при поиске записи с ID {data_id}: {e}")
|
||||
raise
|
||||
|
||||
async def find_one_or_none(self, filters: BaseModel):
|
||||
filter_dict = filters.model_dump(exclude_unset=True)
|
||||
logger.info(
|
||||
f"Поиск одной записи {self.model.__name__} по фильтрам: {filter_dict}"
|
||||
)
|
||||
try:
|
||||
query = select(self.model).filter_by(**filter_dict)
|
||||
result = await self._session.execute(query)
|
||||
record = result.scalar_one_or_none()
|
||||
log_message = f"Запись {'найдена' if record else 'не найдена'} по фильтрам: {filter_dict}"
|
||||
logger.info(log_message)
|
||||
return record
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при поиске записи по фильтрам {filter_dict}: {e}")
|
||||
raise
|
||||
|
||||
async def find_all(self, filters: BaseModel | None = None):
|
||||
filter_dict = filters.model_dump(exclude_unset=True) if filters else {}
|
||||
logger.info(
|
||||
f"Поиск всех записей {self.model.__name__} по фильтрам: {filter_dict}"
|
||||
)
|
||||
try:
|
||||
query = select(self.model).filter_by(**filter_dict)
|
||||
result = await self._session.execute(query)
|
||||
records = result.scalars().all()
|
||||
logger.info(f"Найдено {len(records)} записей.")
|
||||
return records
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(
|
||||
f"Ошибка при поиске всех записей по фильтрам {filter_dict}: {e}"
|
||||
)
|
||||
raise
|
||||
|
||||
async def add(self, values: BaseModel):
|
||||
values_dict = values.model_dump(exclude_unset=True)
|
||||
logger.info(
|
||||
f"Добавление записи {self.model.__name__} с параметрами: {values_dict}"
|
||||
)
|
||||
try:
|
||||
new_instance = self.model(**values_dict)
|
||||
self._session.add(new_instance)
|
||||
logger.info(f"Запись {self.model.__name__} успешно добавлена.")
|
||||
await self._session.flush()
|
||||
return new_instance
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при добавлении записи: {e}")
|
||||
raise
|
||||
|
||||
async def add_many(self, instances: List[BaseModel]):
|
||||
values_list = [item.model_dump(exclude_unset=True) for item in instances]
|
||||
logger.info(
|
||||
f"Добавление нескольких записей {self.model.__name__}. Количество: {len(values_list)}"
|
||||
)
|
||||
try:
|
||||
new_instances = [self.model(**values) for values in values_list]
|
||||
self._session.add_all(new_instances)
|
||||
logger.info(f"Успешно добавлено {len(new_instances)} записей.")
|
||||
await self._session.flush()
|
||||
return new_instances
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при добавлении нескольких записей: {e}")
|
||||
raise
|
||||
|
||||
async def update(self, filters: BaseModel, values: BaseModel):
|
||||
filter_dict = filters.model_dump(exclude_unset=True)
|
||||
values_dict = values.model_dump(exclude_unset=True)
|
||||
logger.info(
|
||||
f"Обновление записей {self.model.__name__} по фильтру: {filter_dict} с параметрами: {values_dict}"
|
||||
)
|
||||
try:
|
||||
query = (
|
||||
sqlalchemy_update(self.model)
|
||||
.where(*[getattr(self.model, k) == v for k, v in filter_dict.items()])
|
||||
.values(**values_dict)
|
||||
.execution_options(synchronize_session="fetch")
|
||||
)
|
||||
result = await self._session.execute(query)
|
||||
logger.info(f"Обновлено {result.rowcount} записей.")
|
||||
await self._session.flush()
|
||||
return result.rowcount
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при обновлении записей: {e}")
|
||||
raise
|
||||
|
||||
async def delete(self, filters: BaseModel):
|
||||
filter_dict = filters.model_dump(exclude_unset=True)
|
||||
logger.info(f"Удаление записей {self.model.__name__} по фильтру: {filter_dict}")
|
||||
if not filter_dict:
|
||||
logger.error("Нужен хотя бы один фильтр для удаления.")
|
||||
raise ValueError("Нужен хотя бы один фильтр для удаления.")
|
||||
try:
|
||||
query = sqlalchemy_delete(self.model).filter_by(**filter_dict)
|
||||
result = await self._session.execute(query)
|
||||
logger.info(f"Удалено {result.rowcount} записей.")
|
||||
await self._session.flush()
|
||||
return result.rowcount
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при удалении записей: {e}")
|
||||
raise
|
||||
|
||||
async def count(self, filters: BaseModel | None = None):
|
||||
filter_dict = filters.model_dump(exclude_unset=True) if filters else {}
|
||||
logger.info(
|
||||
f"Подсчет количества записей {self.model.__name__} по фильтру: {filter_dict}"
|
||||
)
|
||||
try:
|
||||
query = select(func.count(self.model.id)).filter_by(**filter_dict)
|
||||
result = await self._session.execute(query)
|
||||
count = result.scalar()
|
||||
logger.info(f"Найдено {count} записей.")
|
||||
return count
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при подсчете записей: {e}")
|
||||
raise
|
||||
|
||||
async def bulk_update(self, records: List[BaseModel]):
|
||||
logger.info(f"Массовое обновление записей {self.model.__name__}")
|
||||
try:
|
||||
updated_count = 0
|
||||
for record in records:
|
||||
record_dict = record.model_dump(exclude_unset=True)
|
||||
if "id" not in record_dict:
|
||||
continue
|
||||
|
||||
update_data = {k: v for k, v in record_dict.items() if k != "id"}
|
||||
stmt = (
|
||||
sqlalchemy_update(self.model)
|
||||
.filter_by(id=record_dict["id"])
|
||||
.values(**update_data)
|
||||
)
|
||||
result = await self._session.execute(stmt)
|
||||
updated_count += result.rowcount
|
||||
|
||||
logger.info(f"Обновлено {updated_count} записей")
|
||||
await self._session.flush()
|
||||
return updated_count
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Ошибка при массовом обновлении: {e}")
|
||||
raise
|
||||
@@ -1,13 +0,0 @@
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
async_sessionmaker,
|
||||
create_async_engine,
|
||||
AsyncSession,
|
||||
)
|
||||
|
||||
from app.config import database_url
|
||||
|
||||
engine = create_async_engine(url=database_url)
|
||||
|
||||
async_session_maker = async_sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
@@ -1,6 +0,0 @@
|
||||
from app.models import User
|
||||
from .base import BaseDAO
|
||||
|
||||
|
||||
class UsersDAO(BaseDAO):
|
||||
model = User
|
||||
26
backend/app/database.py
Normal file
26
backend/app/database.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from .config import settings
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = settings.database_url
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -1,86 +0,0 @@
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import Request, Depends
|
||||
from jose import jwt, JWTError, ExpiredSignatureError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dao import UsersDAO
|
||||
from app.models import User
|
||||
from app.config import settings
|
||||
from app.dependencies.dao_dep import get_session_without_commit
|
||||
from app.exceptions import (
|
||||
TokenNoFound,
|
||||
NoJwtException,
|
||||
TokenExpiredException,
|
||||
NoUserIdException,
|
||||
ForbiddenException,
|
||||
UserNotFoundException,
|
||||
)
|
||||
|
||||
|
||||
def get_access_token(request: Request) -> str:
|
||||
"""Извлекаем access_token из кук."""
|
||||
token = request.cookies.get("user_access_token")
|
||||
if not token:
|
||||
raise TokenNoFound
|
||||
return token
|
||||
|
||||
|
||||
def get_refresh_token(request: Request) -> str:
|
||||
"""Извлекаем refresh_token из кук."""
|
||||
token = request.cookies.get("user_refresh_token")
|
||||
if not token:
|
||||
raise TokenNoFound
|
||||
return token
|
||||
|
||||
|
||||
async def check_refresh_token(
|
||||
token: str = Depends(get_refresh_token),
|
||||
session: AsyncSession = Depends(get_session_without_commit),
|
||||
) -> User:
|
||||
"""Проверяем refresh_token и возвращаем пользователя."""
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||
)
|
||||
user_id = payload.get("sub")
|
||||
if not user_id:
|
||||
raise NoJwtException
|
||||
|
||||
user = await UsersDAO(session).find_one_or_none_by_id(data_id=int(user_id))
|
||||
if not user:
|
||||
raise NoJwtException
|
||||
|
||||
return user
|
||||
except JWTError:
|
||||
raise NoJwtException
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: str = Depends(get_access_token),
|
||||
session: AsyncSession = Depends(get_session_without_commit),
|
||||
) -> User:
|
||||
"""Проверяем access_token и возвращаем пользователя."""
|
||||
try:
|
||||
# Декодируем токен
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||
)
|
||||
except ExpiredSignatureError:
|
||||
raise TokenExpiredException
|
||||
except JWTError:
|
||||
# Общая ошибка для токенов
|
||||
raise NoJwtException
|
||||
|
||||
expire: str = payload.get("exp")
|
||||
expire_time = datetime.fromtimestamp(int(expire), tz=timezone.utc)
|
||||
if (not expire) or (expire_time < datetime.now(timezone.utc)):
|
||||
raise TokenExpiredException
|
||||
|
||||
user_id: str = payload.get("sub")
|
||||
if not user_id:
|
||||
raise NoUserIdException
|
||||
|
||||
user = await UsersDAO(session).find_one_or_none_by_id(data_id=int(user_id))
|
||||
if not user:
|
||||
raise UserNotFoundException
|
||||
return user
|
||||
@@ -1,30 +0,0 @@
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dao import async_session_maker
|
||||
|
||||
|
||||
async def get_session_with_commit() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Асинхронная сессия с автоматическим коммитом."""
|
||||
async with async_session_maker() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
|
||||
async def get_session_without_commit() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Асинхронная сессия без автоматического коммита."""
|
||||
async with async_session_maker() as session:
|
||||
try:
|
||||
yield session
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
finally:
|
||||
await session.close()
|
||||
@@ -1,58 +0,0 @@
|
||||
from fastapi import status, HTTPException
|
||||
|
||||
# Пользователь уже существует
|
||||
UserAlreadyExistsException = HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Пользователь уже существует"
|
||||
)
|
||||
|
||||
# Пользователь не найден
|
||||
UserNotFoundException = HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден"
|
||||
)
|
||||
|
||||
# Отсутствует идентификатор пользователя
|
||||
UserIdNotFoundException = HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Отсутствует идентификатор пользователя",
|
||||
)
|
||||
|
||||
# Неверная почта или пароль
|
||||
IncorrectEmailOrPasswordException = HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Неверная почта или пароль"
|
||||
)
|
||||
|
||||
# Токен истек
|
||||
TokenExpiredException = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Токен истек"
|
||||
)
|
||||
|
||||
# Некорректный формат токена
|
||||
InvalidTokenFormatException = HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Некорректный формат токена"
|
||||
)
|
||||
|
||||
|
||||
# Токен отсутствует в заголовке
|
||||
TokenNoFound = HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Токен отсутствует в заголовке"
|
||||
)
|
||||
|
||||
# Невалидный JWT токен
|
||||
NoJwtException = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Токен не валидный"
|
||||
)
|
||||
|
||||
# Не найден ID пользователя
|
||||
NoUserIdException = HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Не найден ID пользователя"
|
||||
)
|
||||
|
||||
# Недостаточно прав
|
||||
ForbiddenException = HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="Недостаточно прав"
|
||||
)
|
||||
|
||||
TokenInvalidFormatException = HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Неверный формат токена. Ожидается 'Bearer <токен>'",
|
||||
)
|
||||
@@ -1,67 +1,16 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from loguru import logger
|
||||
from . import models
|
||||
from .database import engine
|
||||
from app.routers import users_router, auth_router
|
||||
|
||||
from app.auth import router as auth_router
|
||||
from app.routers import all_routers
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(users_router, prefix="/api/users", tags=["users"])
|
||||
app.include_router(auth_router, prefix="/api/auth", tags=["auth"])
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[dict, None]:
|
||||
"""Управление жизненным циклом приложения."""
|
||||
logger.info("Инициализация приложения...")
|
||||
yield
|
||||
logger.info("Завершение работы приложения...")
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""
|
||||
Создание и конфигурация FastAPI приложения.
|
||||
|
||||
Returns:
|
||||
Сконфигурированное приложение FastAPI
|
||||
"""
|
||||
app = FastAPI(
|
||||
title="Стартовая сборка FastAPI",
|
||||
description=(
|
||||
"Стартовая сборка с интегрированной SQLAlchemy 2 для разработки FastAPI приложений с продвинутой "
|
||||
"архитектурой, включающей авторизацию, аутентификацию и управление ролями пользователей."
|
||||
),
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Настройка CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Монтирование статических файлов
|
||||
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||
|
||||
# Регистрация роутеров
|
||||
register_routers(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def register_routers(app: FastAPI) -> None:
|
||||
"""Регистрация роутеров приложения."""
|
||||
|
||||
# Подключение роутеров
|
||||
app.include_router(auth_router, prefix="/auth", tags=["Auth"])
|
||||
|
||||
for router in all_routers:
|
||||
app.include_router(router)
|
||||
|
||||
|
||||
# Создание экземпляра приложения
|
||||
app = create_app()
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Hello from FastAPI!"}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Generic single-database configuration with an async dbapi.
|
||||
17
backend/app/models.py
Normal file
17
backend/app/models.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean
|
||||
from sqlalchemy.sql.sqltypes import TIMESTAMP
|
||||
from sqlalchemy.sql.expression import text
|
||||
from .database import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String(255), unique=True, index=True)
|
||||
username = Column(String(50), unique=True, index=True)
|
||||
hashed_password = Column(String(255))
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(
|
||||
TIMESTAMP(timezone=True), nullable=False, server_default=text("now()")
|
||||
)
|
||||
@@ -1,6 +0,0 @@
|
||||
from .facult import Facult
|
||||
from .grade_book import GradeBook
|
||||
from .group import Group
|
||||
from .model import Model
|
||||
from .specialization import Specialization
|
||||
from .user import User
|
||||
@@ -1,8 +0,0 @@
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class Discipline(Model):
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False, unique=True)
|
||||
@@ -1,10 +0,0 @@
|
||||
from sqlalchemy import ForeignKey, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class Facult(Model):
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False, unique=True)
|
||||
dean_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
||||
dean: Mapped["User"] = relationship("User", lazy="joined")
|
||||
@@ -1,29 +0,0 @@
|
||||
import enum
|
||||
|
||||
from sqlalchemy import ForeignKey, Enum
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class StudentStatus(enum.Enum):
|
||||
STUDY = "Обучается"
|
||||
ACADEM = "Академ"
|
||||
EXPLUSION = "Отчислен"
|
||||
GRADUATED = "Выпущен"
|
||||
|
||||
|
||||
class GradeBook(Model):
|
||||
student_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
||||
student: Mapped["User"] = relationship("User", lazy="joined")
|
||||
specialization_id: Mapped[int] = mapped_column(ForeignKey("specializations.id"), nullable=False)
|
||||
specialization: Mapped["Specialization"] = relationship(
|
||||
"Specialization", lazy="joined"
|
||||
)
|
||||
status: Mapped[StudentStatus] = mapped_column(
|
||||
Enum(StudentStatus), nullable=False, default=StudentStatus.STUDY
|
||||
)
|
||||
group_id: Mapped[int] = mapped_column(ForeignKey("groups.id"))
|
||||
group: Mapped["Group"] = relationship(
|
||||
"Group", lazy="joined"
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
from sqlalchemy import ForeignKey, Integer, CheckConstraint, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class Group(Model):
|
||||
course: Mapped[int] = mapped_column(Integer, CheckConstraint("course > 0"), nullable=False)
|
||||
specialization_id: Mapped[int] = mapped_column(ForeignKey("specializations.id"))
|
||||
specialization: Mapped["Specialization"] = relationship(
|
||||
"Specialization", lazy="joined"
|
||||
)
|
||||
max_students_count: Mapped[int] = mapped_column(Integer, CheckConstraint("max_students_count >= 0"), nullable=False)
|
||||
number: Mapped[int] = mapped_column(Integer, CheckConstraint("number > 0"), nullable=False)
|
||||
__table_args__ = (UniqueConstraint("course", "specialization_id", "number", name="_spec_parallel_groups_unique"),)
|
||||
@@ -1,49 +0,0 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Integer, inspect
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr
|
||||
|
||||
|
||||
class Model(AsyncAttrs, DeclarativeBase):
|
||||
__abstract__ = True
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
@declared_attr
|
||||
def __tablename__(cls) -> str:
|
||||
return cls.__name__.lower() + "s"
|
||||
|
||||
def to_dict(self, exclude_none: bool = False):
|
||||
"""
|
||||
Преобразует объект модели в словарь.
|
||||
|
||||
Args:
|
||||
exclude_none (bool): Исключать ли None значения из результата
|
||||
|
||||
Returns:
|
||||
dict: Словарь с данными объекта
|
||||
"""
|
||||
result = {}
|
||||
for column in inspect(self.__class__).columns:
|
||||
value = getattr(self, column.key)
|
||||
|
||||
# Преобразование специальных типов данных
|
||||
if isinstance(value, datetime):
|
||||
value = value.isoformat()
|
||||
elif isinstance(value, Decimal):
|
||||
value = float(value)
|
||||
elif isinstance(value, uuid.UUID):
|
||||
value = str(value)
|
||||
|
||||
# Добавляем значение в результат
|
||||
if not exclude_none or value is not None:
|
||||
result[column.key] = value
|
||||
|
||||
return result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Строковое представление объекта для удобства отладки."""
|
||||
return f"<{self.__class__.__name__}(id={self.id})>"
|
||||
@@ -1,9 +0,0 @@
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class Specialization(Model):
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False, unique=True)
|
||||
code: Mapped[str] = mapped_column(String(50), nullable=False, unique=True)
|
||||
@@ -1,28 +0,0 @@
|
||||
import enum
|
||||
|
||||
from sqlalchemy import String, Enum
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class UserRole(enum.Enum):
|
||||
GUEST = "Гость"
|
||||
STUDENT = "Студент"
|
||||
TEACHER = "Преподаватель"
|
||||
DEAN = "Декан"
|
||||
RECTOR = "Ректор"
|
||||
ADMIN = "Админ"
|
||||
|
||||
|
||||
class User(Model):
|
||||
email: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
|
||||
password: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
surname: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
patronymic: Mapped[str | None] = mapped_column(
|
||||
String(50), nullable=True, default=None
|
||||
)
|
||||
role: Mapped[UserRole] = mapped_column(
|
||||
Enum(UserRole), nullable=False, default=UserRole.GUEST
|
||||
)
|
||||
@@ -1,5 +1,4 @@
|
||||
from .auth import router as auth_router
|
||||
from .users import router as users_router
|
||||
|
||||
all_routers = [
|
||||
users_router
|
||||
]
|
||||
__all__ = ["auth_router", "users_router"]
|
||||
|
||||
79
backend/app/routers/auth.py
Normal file
79
backend/app/routers/auth.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime, timedelta
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from .. import schemas, models, database
|
||||
from ..config import settings
|
||||
|
||||
router = APIRouter(tags=["auth"])
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str):
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str):
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(
|
||||
to_encode, settings.secret_key, algorithm=settings.algorithm
|
||||
)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: str = Depends(oauth2_scheme), db: Session = Depends(database.get_db)
|
||||
):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, settings.secret_key, algorithms=[settings.algorithm]
|
||||
)
|
||||
email: str = payload.get("sub")
|
||||
if email is None:
|
||||
raise credentials_exception
|
||||
token_data = schemas.TokenData(email=email)
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
user = db.query(models.User).filter(models.User.email == token_data.email).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
|
||||
@router.post("/token", response_model=schemas.Token)
|
||||
async def login_for_access_token(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: Session = Depends(database.get_db),
|
||||
):
|
||||
user = db.query(models.User).filter(models.User.email == form_data.username).first()
|
||||
if not user or not verify_password(form_data.password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.email}, expires_delta=access_token_expires
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
@@ -1,58 +1,39 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from datetime import timedelta
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.dao import UsersDAO
|
||||
from app.dependencies.dao_dep import get_session_with_commit, get_session_without_commit
|
||||
from app.exceptions import UserAlreadyExistsException
|
||||
from app.schemas import UserSchema, EmailSchema, UserAddDBSchema
|
||||
from . import auth
|
||||
from .. import schemas, models, database
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["Users"])
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/{user_id}")
|
||||
async def get_user_by_id(user_id: int, session: AsyncSession = Depends(get_session_without_commit)) -> UserSchema:
|
||||
user = await UsersDAO(session).find_one_or_none_by_id(data_id=user_id)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
return UserSchema.model_validate(user)
|
||||
@router.post("/", response_model=schemas.User)
|
||||
def create_user(user: schemas.UserCreate, db: Session = Depends(database.get_db)):
|
||||
db_user = db.query(models.User).filter(models.User.email == user.email).first()
|
||||
if db_user:
|
||||
raise HTTPException(status_code=400, detail="Email already registered")
|
||||
hashed_password = auth.get_password_hash(user.password)
|
||||
db_user = models.User(email=user.email, hashed_password=hashed_password)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_all_users(session: AsyncSession = Depends(get_session_without_commit)) -> list[UserSchema]:
|
||||
users_dao = UsersDAO(session)
|
||||
users = await users_dao.find_all()
|
||||
return [UserSchema.model_validate(user) for user in users]
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_user(user_data, session: AsyncSession = Depends(get_session_with_commit)) -> dict:
|
||||
# Проверка существования пользователя
|
||||
user_dao = UsersDAO(session)
|
||||
|
||||
existing_user = await user_dao.find_one_or_none(
|
||||
filters=EmailSchema(email=user_data.email)
|
||||
@router.post("/token", response_model=schemas.Token)
|
||||
def login_for_access_token(
|
||||
user: schemas.UserCreate, db: Session = Depends(database.get_db)
|
||||
):
|
||||
db_user = db.query(models.User).filter(models.User.email == user.email).first()
|
||||
if not db_user or not auth.verify_password(user.password, db_user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
if existing_user:
|
||||
raise UserAlreadyExistsException
|
||||
|
||||
# Подготовка данных для добавления
|
||||
user_data_dict = user_data.model_dump()
|
||||
user_data_dict.pop("confirm_password", None)
|
||||
|
||||
# Добавление пользователя
|
||||
await user_dao.add(values=UserAddDBSchema(**user_data_dict))
|
||||
|
||||
return {"message": "Вы успешно зарегистрированы!"}
|
||||
|
||||
|
||||
@router.put("/{user_id}")
|
||||
async def update_user(user_id: int, user_data, session: AsyncSession = Depends(get_session_with_commit)):
|
||||
pass
|
||||
|
||||
|
||||
@router.delete("/{user_id}")
|
||||
async def delete_user(user_id: int, session: AsyncSession = Depends(get_session_with_commit)):
|
||||
pass
|
||||
access_token = auth.create_access_token(
|
||||
data={"sub": db_user.email},
|
||||
expires_delta=timedelta(minutes=auth.ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
26
backend/app/schemas.py
Normal file
26
backend/app/schemas.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str
|
||||
|
||||
|
||||
class User(UserBase):
|
||||
id: int
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
email: str | None = None
|
||||
@@ -1,2 +0,0 @@
|
||||
from .general import EmailSchema
|
||||
from .user import UserBaseSchema, UserSchema, UserAddDBSchema, UserCreateSchema, UserAuthSchema
|
||||
@@ -1,27 +0,0 @@
|
||||
from typing import Self
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
EmailStr,
|
||||
Field, model_validator,
|
||||
)
|
||||
|
||||
from app.auth.utils import get_password_hash
|
||||
|
||||
|
||||
class EmailSchema(BaseModel):
|
||||
email: EmailStr = Field(description="Электронная почта")
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class PasswordSchema(BaseModel):
|
||||
password: str = Field(
|
||||
min_length=5, max_length=50, description="Пароль, от 5 до 50 знаков"
|
||||
)
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_password(self) -> Self:
|
||||
self.password = get_password_hash(self.password)
|
||||
return self
|
||||
@@ -1,40 +0,0 @@
|
||||
from pydantic import (
|
||||
Field,
|
||||
)
|
||||
|
||||
from app.models.user import UserRole
|
||||
from .general import EmailSchema, PasswordSchema
|
||||
|
||||
|
||||
class UserBaseSchema(EmailSchema):
|
||||
name: str = Field(
|
||||
min_length=3, max_length=50, description="Имя, от 3 до 50 символов"
|
||||
)
|
||||
surname: str = Field(
|
||||
min_length=3, max_length=50, description="Фамилия, от 3 до 50 символов"
|
||||
)
|
||||
patronymic: str = Field(
|
||||
min_length=3,
|
||||
max_length=50,
|
||||
description="Отчество, от 3 до 50 символов",
|
||||
default=None,
|
||||
)
|
||||
role: UserRole = Field(description="Роль пользователя в системе", default=UserRole.GUEST)
|
||||
|
||||
|
||||
class UserCreateSchema(PasswordSchema, UserBaseSchema):
|
||||
pass
|
||||
|
||||
|
||||
class UserAddDBSchema(UserBaseSchema):
|
||||
password: str = Field(min_length=5, description="Пароль в формате HASH-строки")
|
||||
|
||||
|
||||
class UserAuthSchema(EmailSchema):
|
||||
password: str = Field(
|
||||
min_length=5, max_length=50, description="Пароль, от 5 до 50 знаков"
|
||||
)
|
||||
|
||||
|
||||
class UserSchema(UserBaseSchema):
|
||||
id: int = Field(description="Идентификатор пользователя")
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
alembic upgrade head
|
||||
|
||||
exec "$@"
|
||||
7
backend/poetry.lock
generated
Normal file
7
backend/poetry.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
package = []
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "*"
|
||||
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
|
||||
29
backend/pyproject.toml
Normal file
29
backend/pyproject.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[project]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "Your Name",email = "you@example.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"fastapi[all] (>=0.115.12,<0.116.0)",
|
||||
"sqlalchemy (>=2.0.41,<3.0.0)",
|
||||
"pymysql (>=1.1.1,<2.0.0)",
|
||||
"passlib (>=1.7.4,<2.0.0)",
|
||||
"python-jose (>=3.4.0,<4.0.0)",
|
||||
"pydantic (>=2.11.4,<3.0.0)",
|
||||
"alembic (>=1.15.2,<2.0.0)",
|
||||
"dotenv (>=0.9.9,<0.10.0)",
|
||||
"pydantic-settings (>=2.9.1,<3.0.0)",
|
||||
"cryptography (>=45.0.2,<46.0.0)",
|
||||
"bcrypt (>=4.3.0,<5.0.0)"
|
||||
]
|
||||
|
||||
[tool.poetry]
|
||||
package-mode = false
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -1,58 +0,0 @@
|
||||
aiomysql==0.2.0
|
||||
aiosqlite==0.20.0
|
||||
alembic==1.13.3
|
||||
annotated-types==0.7.0
|
||||
anyio==4.9.0
|
||||
bcrypt==4.0.1
|
||||
certifi==2025.4.26
|
||||
cffi==1.17.1
|
||||
click==8.2.1
|
||||
cryptography==45.0.3
|
||||
dnspython==2.7.0
|
||||
ecdsa==0.19.1
|
||||
email_validator==2.2.0
|
||||
fastapi==0.115.0
|
||||
fastapi-cli==0.0.7
|
||||
greenlet==3.2.3
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httptools==0.6.4
|
||||
httpx==0.28.1
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.4
|
||||
loguru==0.7.2
|
||||
Mako==1.3.10
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==3.0.2
|
||||
mdurl==0.1.2
|
||||
mysql-connector-python==9.3.0
|
||||
orjson==3.10.18
|
||||
passlib==1.7.4
|
||||
pyasn1==0.6.1
|
||||
pycparser==2.22
|
||||
pydantic==2.9.2
|
||||
pydantic-extra-types==2.10.5
|
||||
pydantic-settings==2.5.2
|
||||
pydantic_core==2.23.4
|
||||
Pygments==2.19.1
|
||||
PyMySQL==1.1.1
|
||||
python-dotenv==1.1.0
|
||||
python-jose==3.3.0
|
||||
python-multipart==0.0.20
|
||||
PyYAML==6.0.2
|
||||
rich==14.0.0
|
||||
rich-toolkit==0.14.7
|
||||
rsa==4.9.1
|
||||
shellingham==1.5.4
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
SQLAlchemy==2.0.35
|
||||
starlette==0.38.6
|
||||
typer==0.16.0
|
||||
typing_extensions==4.14.0
|
||||
ujson==5.10.0
|
||||
uvicorn==0.31.0
|
||||
# uvloop==0.21.0
|
||||
watchfiles==1.0.5
|
||||
websockets==15.0.1
|
||||
33
compose.yml
33
compose.yml
@@ -2,35 +2,28 @@ services:
|
||||
db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
|
||||
MYSQL_USER: ${MYSQL_USER:-appuser}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-secret}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-supersecret}
|
||||
MYSQL_DATABASE: myapp
|
||||
MYSQL_USER: myappuser
|
||||
MYSQL_PASSWORD: mypassword
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
ports:
|
||||
- 127.0.0.1:3306:3306
|
||||
- "127.0.0.1:3306:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- 127.0.0.1:8000:8000
|
||||
- "127.0.0.1:8000:8000"
|
||||
environment:
|
||||
DB_HOST: ${DB_HOST:-db}
|
||||
DB_PORT: ${DB_PORT:-3306}
|
||||
DB_DRIVER: ${DB_DRIVER:-mysql}
|
||||
DB_NAME: ${DB_NAME:-app}
|
||||
DB_USER: ${DB_USER:-appuser}
|
||||
DB_PASSWORD: ${DB_PASSWORD:-secret}
|
||||
SECRET_KEY: ${SECRET_KEY:-supersecretkey}
|
||||
ALGORITHM: ${ALGORITHM:-HS256}
|
||||
DATABASE_URL: mysql+pymysql://myappuser:mypassword@db/myapp
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
adminer:
|
||||
image: adminer:latest
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
|
||||
400
contingent_report_desktop/.gitignore
vendored
Normal file
400
contingent_report_desktop/.gitignore
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Controller\Controller.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34714.143
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App_contingent_university", "App_contingent_university.csproj", "{4AAF178E-2E63-49C7-A054-1CF8618B9DB1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataModels", "..\DataModels\DataModels.csproj", "{9C5F04AA-81FA-4EDD-88D7-8A774DAF58C1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controller", "..\Controller\Controller.csproj", "{ABF0591E-2C5D-426A-B542-30A89162E1A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4AAF178E-2E63-49C7-A054-1CF8618B9DB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4AAF178E-2E63-49C7-A054-1CF8618B9DB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4AAF178E-2E63-49C7-A054-1CF8618B9DB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4AAF178E-2E63-49C7-A054-1CF8618B9DB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9C5F04AA-81FA-4EDD-88D7-8A774DAF58C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9C5F04AA-81FA-4EDD-88D7-8A774DAF58C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9C5F04AA-81FA-4EDD-88D7-8A774DAF58C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9C5F04AA-81FA-4EDD-88D7-8A774DAF58C1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ABF0591E-2C5D-426A-B542-30A89162E1A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ABF0591E-2C5D-426A-B542-30A89162E1A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ABF0591E-2C5D-426A-B542-30A89162E1A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ABF0591E-2C5D-426A-B542-30A89162E1A8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6C5452B1-7500-4C58-9F7A-340A4254757F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
83
contingent_report_desktop/App_contingent_university/Forms/FormAuth.Designer.cs
generated
Normal file
83
contingent_report_desktop/App_contingent_university/Forms/FormAuth.Designer.cs
generated
Normal file
@@ -0,0 +1,83 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
buttonEntrance = new Button();
|
||||
textBoxEmail = new TextBox();
|
||||
textBoxPassword = new TextBox();
|
||||
SuspendLayout();
|
||||
//
|
||||
// buttonEntrance
|
||||
//
|
||||
buttonEntrance.Location = new Point(144, 176);
|
||||
buttonEntrance.Name = "buttonEntrance";
|
||||
buttonEntrance.Size = new Size(94, 29);
|
||||
buttonEntrance.TabIndex = 0;
|
||||
buttonEntrance.Text = "Вход";
|
||||
buttonEntrance.UseVisualStyleBackColor = true;
|
||||
buttonEntrance.Click += buttonEntrance_Click;
|
||||
//
|
||||
// textBoxEmail
|
||||
//
|
||||
textBoxEmail.Anchor = AnchorStyles.Top;
|
||||
textBoxEmail.Location = new Point(102, 36);
|
||||
textBoxEmail.Name = "textBoxEmail";
|
||||
textBoxEmail.Size = new Size(170, 27);
|
||||
textBoxEmail.TabIndex = 1;
|
||||
//
|
||||
// textBoxPassword
|
||||
//
|
||||
textBoxPassword.Anchor = AnchorStyles.Top;
|
||||
textBoxPassword.Location = new Point(104, 95);
|
||||
textBoxPassword.Name = "textBoxPassword";
|
||||
textBoxPassword.Size = new Size(170, 27);
|
||||
textBoxPassword.TabIndex = 2;
|
||||
//
|
||||
// FormAuth
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
BackColor = Color.CornflowerBlue;
|
||||
ClientSize = new Size(378, 217);
|
||||
Controls.Add(textBoxPassword);
|
||||
Controls.Add(textBoxEmail);
|
||||
Controls.Add(buttonEntrance);
|
||||
Name = "FormAuth";
|
||||
Text = "Авторизация";
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Button buttonEntrance;
|
||||
private TextBox textBoxEmail;
|
||||
private TextBox textBoxPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Controller.Contracts;
|
||||
using Controller.Repository;
|
||||
using DataModels.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormAuth : Form
|
||||
{
|
||||
private readonly DeanRepository _deanRepository;
|
||||
|
||||
public FormAuth(DeanRepository deanRepository)
|
||||
{
|
||||
_deanRepository = deanRepository;
|
||||
InitializeComponent();
|
||||
textBoxEmail.PlaceholderText = "dean1@gmail.com";
|
||||
textBoxPassword.PlaceholderText = "password123456";
|
||||
|
||||
textBoxEmail.Text = "dean1@gmail.com";
|
||||
textBoxPassword.Text = "123456";
|
||||
}
|
||||
|
||||
private async void buttonEntrance_Click(object sender, EventArgs e)
|
||||
{
|
||||
string email = textBoxEmail.Text;
|
||||
string password = textBoxPassword.Text;
|
||||
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
MessageBox.Show("Введите email и пароль", "Ошибка",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var search = new DeanSearch
|
||||
{
|
||||
Email = email,
|
||||
Password = password
|
||||
};
|
||||
|
||||
var deans = await _deanRepository.GetFilteredList(search);
|
||||
var dean = deans.FirstOrDefault();
|
||||
|
||||
if (dean == null)
|
||||
{
|
||||
MessageBox.Show("Неверные учетные данные", "Ошибка авторизации",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
StaticSession.Login(dean.Id, dean.FacultId ?? 0);
|
||||
|
||||
// Устанавливаем результат и закрываем форму
|
||||
DialogResult = DialogResult.OK;
|
||||
Close(); // После этого управление вернется в Program.Main()
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Ошибка авторизации: {ex.Message}", "Ошибка",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
176
contingent_report_desktop/App_contingent_university/Forms/FormFilterStudents.Designer.cs
generated
Normal file
176
contingent_report_desktop/App_contingent_university/Forms/FormFilterStudents.Designer.cs
generated
Normal file
@@ -0,0 +1,176 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormFilterStudents
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.buttonOK = new System.Windows.Forms.Button();
|
||||
this.labelF = new System.Windows.Forms.Label();
|
||||
this.textBoxLastName = new System.Windows.Forms.TextBox();
|
||||
this.textBoxName = new System.Windows.Forms.TextBox();
|
||||
this.labelI = new System.Windows.Forms.Label();
|
||||
this.textBoxPatronimic = new System.Windows.Forms.TextBox();
|
||||
this.labelPatronimic = new System.Windows.Forms.Label();
|
||||
this.comboBoxSpec = new System.Windows.Forms.ComboBox();
|
||||
this.labelSpec = new System.Windows.Forms.Label();
|
||||
this.labelGroup = new System.Windows.Forms.Label();
|
||||
this.comboBoxGroup = new System.Windows.Forms.ComboBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonOK.BackColor = System.Drawing.Color.Orange;
|
||||
this.buttonOK.ForeColor = System.Drawing.Color.Black;
|
||||
this.buttonOK.Location = new System.Drawing.Point(154, 284);
|
||||
this.buttonOK.Name = "buttonOK";
|
||||
this.buttonOK.Size = new System.Drawing.Size(113, 37);
|
||||
this.buttonOK.TabIndex = 0;
|
||||
this.buttonOK.Text = "Применить";
|
||||
this.buttonOK.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// labelF
|
||||
//
|
||||
this.labelF.AutoSize = true;
|
||||
this.labelF.Location = new System.Drawing.Point(12, 26);
|
||||
this.labelF.Name = "labelF";
|
||||
this.labelF.Size = new System.Drawing.Size(66, 16);
|
||||
this.labelF.TabIndex = 1;
|
||||
this.labelF.Text = "Фамилия";
|
||||
//
|
||||
// textBoxLastName
|
||||
//
|
||||
this.textBoxLastName.Location = new System.Drawing.Point(107, 20);
|
||||
this.textBoxLastName.Name = "textBoxLastName";
|
||||
this.textBoxLastName.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxLastName.TabIndex = 2;
|
||||
//
|
||||
// textBoxName
|
||||
//
|
||||
this.textBoxName.Location = new System.Drawing.Point(107, 56);
|
||||
this.textBoxName.Name = "textBoxName";
|
||||
this.textBoxName.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxName.TabIndex = 4;
|
||||
//
|
||||
// labelI
|
||||
//
|
||||
this.labelI.AutoSize = true;
|
||||
this.labelI.Location = new System.Drawing.Point(12, 62);
|
||||
this.labelI.Name = "labelI";
|
||||
this.labelI.Size = new System.Drawing.Size(33, 16);
|
||||
this.labelI.TabIndex = 3;
|
||||
this.labelI.Text = "Имя";
|
||||
//
|
||||
// textBoxPatronimic
|
||||
//
|
||||
this.textBoxPatronimic.Location = new System.Drawing.Point(107, 94);
|
||||
this.textBoxPatronimic.Name = "textBoxPatronimic";
|
||||
this.textBoxPatronimic.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxPatronimic.TabIndex = 6;
|
||||
//
|
||||
// labelPatronimic
|
||||
//
|
||||
this.labelPatronimic.AutoSize = true;
|
||||
this.labelPatronimic.Location = new System.Drawing.Point(12, 100);
|
||||
this.labelPatronimic.Name = "labelPatronimic";
|
||||
this.labelPatronimic.Size = new System.Drawing.Size(70, 16);
|
||||
this.labelPatronimic.TabIndex = 5;
|
||||
this.labelPatronimic.Text = "Отчество";
|
||||
//
|
||||
// comboBoxSpec
|
||||
//
|
||||
this.comboBoxSpec.FormattingEnabled = true;
|
||||
this.comboBoxSpec.Location = new System.Drawing.Point(123, 134);
|
||||
this.comboBoxSpec.Name = "comboBoxSpec";
|
||||
this.comboBoxSpec.Size = new System.Drawing.Size(145, 24);
|
||||
this.comboBoxSpec.TabIndex = 7;
|
||||
//
|
||||
// labelSpec
|
||||
//
|
||||
this.labelSpec.AutoSize = true;
|
||||
this.labelSpec.Location = new System.Drawing.Point(12, 142);
|
||||
this.labelSpec.Name = "labelSpec";
|
||||
this.labelSpec.Size = new System.Drawing.Size(97, 16);
|
||||
this.labelSpec.TabIndex = 8;
|
||||
this.labelSpec.Text = "Направление";
|
||||
//
|
||||
// labelGroup
|
||||
//
|
||||
this.labelGroup.AutoSize = true;
|
||||
this.labelGroup.Location = new System.Drawing.Point(12, 183);
|
||||
this.labelGroup.Name = "labelGroup";
|
||||
this.labelGroup.Size = new System.Drawing.Size(54, 16);
|
||||
this.labelGroup.TabIndex = 10;
|
||||
this.labelGroup.Text = "Группа";
|
||||
//
|
||||
// comboBoxGroup
|
||||
//
|
||||
this.comboBoxGroup.FormattingEnabled = true;
|
||||
this.comboBoxGroup.Location = new System.Drawing.Point(125, 180);
|
||||
this.comboBoxGroup.Name = "comboBoxGroup";
|
||||
this.comboBoxGroup.Size = new System.Drawing.Size(142, 24);
|
||||
this.comboBoxGroup.TabIndex = 9;
|
||||
//
|
||||
// FormFilterStudents
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.LightSteelBlue;
|
||||
this.ClientSize = new System.Drawing.Size(280, 333);
|
||||
this.Controls.Add(this.labelGroup);
|
||||
this.Controls.Add(this.comboBoxGroup);
|
||||
this.Controls.Add(this.labelSpec);
|
||||
this.Controls.Add(this.comboBoxSpec);
|
||||
this.Controls.Add(this.textBoxPatronimic);
|
||||
this.Controls.Add(this.labelPatronimic);
|
||||
this.Controls.Add(this.textBoxName);
|
||||
this.Controls.Add(this.labelI);
|
||||
this.Controls.Add(this.textBoxLastName);
|
||||
this.Controls.Add(this.labelF);
|
||||
this.Controls.Add(this.buttonOK);
|
||||
this.Name = "FormFilterStudents";
|
||||
this.Text = "Фильтр";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Button buttonOK;
|
||||
private System.Windows.Forms.Label labelF;
|
||||
private System.Windows.Forms.TextBox textBoxLastName;
|
||||
private System.Windows.Forms.TextBox textBoxName;
|
||||
private System.Windows.Forms.Label labelI;
|
||||
private System.Windows.Forms.TextBox textBoxPatronimic;
|
||||
private System.Windows.Forms.Label labelPatronimic;
|
||||
private System.Windows.Forms.ComboBox comboBoxSpec;
|
||||
private System.Windows.Forms.Label labelSpec;
|
||||
private System.Windows.Forms.Label labelGroup;
|
||||
private System.Windows.Forms.ComboBox comboBoxGroup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormFilterStudents : Form
|
||||
{
|
||||
public FormFilterStudents()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
343
contingent_report_desktop/App_contingent_university/Forms/FormMain.Designer.cs
generated
Normal file
343
contingent_report_desktop/App_contingent_university/Forms/FormMain.Designer.cs
generated
Normal file
@@ -0,0 +1,343 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormMain
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
MainPages = new TabControl();
|
||||
StudentsPage = new TabPage();
|
||||
progressBarStudents = new ProgressBar();
|
||||
dataGridViewStudents = new DataGridView();
|
||||
menuStrip1 = new MenuStrip();
|
||||
фильтрацияToolStripMenuItem = new ToolStripMenuItem();
|
||||
SpecPage = new TabPage();
|
||||
progressBarSpecs = new ProgressBar();
|
||||
dataGridViewSpecs = new DataGridView();
|
||||
menuStrip2 = new MenuStrip();
|
||||
добавитьНаправлениеToolStripMenuItem = new ToolStripMenuItem();
|
||||
GroupsPage = new TabPage();
|
||||
progressBarGroups = new ProgressBar();
|
||||
dataGridViewGroups = new DataGridView();
|
||||
menuStrip3 = new MenuStrip();
|
||||
toolStripMenuItem2 = new ToolStripMenuItem();
|
||||
добавитьГруппуToolStripMenuItem = new ToolStripMenuItem();
|
||||
OrdersPage = new TabPage();
|
||||
menuStrip4 = new MenuStrip();
|
||||
создатьПриказToolStripMenuItem = new ToolStripMenuItem();
|
||||
создатьПриказToolStripMenuItem1 = new ToolStripMenuItem();
|
||||
MainPages.SuspendLayout();
|
||||
StudentsPage.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewStudents).BeginInit();
|
||||
menuStrip1.SuspendLayout();
|
||||
SpecPage.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewSpecs).BeginInit();
|
||||
menuStrip2.SuspendLayout();
|
||||
GroupsPage.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewGroups).BeginInit();
|
||||
menuStrip3.SuspendLayout();
|
||||
OrdersPage.SuspendLayout();
|
||||
menuStrip4.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// MainPages
|
||||
//
|
||||
MainPages.Controls.Add(StudentsPage);
|
||||
MainPages.Controls.Add(SpecPage);
|
||||
MainPages.Controls.Add(GroupsPage);
|
||||
MainPages.Controls.Add(OrdersPage);
|
||||
MainPages.Dock = DockStyle.Fill;
|
||||
MainPages.Location = new Point(0, 0);
|
||||
MainPages.Margin = new Padding(3, 4, 3, 4);
|
||||
MainPages.Name = "MainPages";
|
||||
MainPages.SelectedIndex = 0;
|
||||
MainPages.Size = new Size(1082, 751);
|
||||
MainPages.TabIndex = 0;
|
||||
MainPages.SelectedIndexChanged += MainPages_SelectedIndexChanged;
|
||||
MainPages.VisibleChanged += MainPages_SelectedIndexChanged;
|
||||
MainPages.KeyDown += MainPages_KeyDown;
|
||||
//
|
||||
// StudentsPage
|
||||
//
|
||||
StudentsPage.Controls.Add(progressBarStudents);
|
||||
StudentsPage.Controls.Add(dataGridViewStudents);
|
||||
StudentsPage.Controls.Add(menuStrip1);
|
||||
StudentsPage.Location = new Point(4, 29);
|
||||
StudentsPage.Margin = new Padding(3, 4, 3, 4);
|
||||
StudentsPage.Name = "StudentsPage";
|
||||
StudentsPage.Padding = new Padding(3, 4, 3, 4);
|
||||
StudentsPage.Size = new Size(1074, 718);
|
||||
StudentsPage.TabIndex = 0;
|
||||
StudentsPage.Text = "Студенты";
|
||||
StudentsPage.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// progressBarStudents
|
||||
//
|
||||
progressBarStudents.Location = new Point(475, 345);
|
||||
progressBarStudents.Name = "progressBarStudents";
|
||||
progressBarStudents.Size = new Size(125, 29);
|
||||
progressBarStudents.TabIndex = 5;
|
||||
//
|
||||
// dataGridViewStudents
|
||||
//
|
||||
dataGridViewStudents.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
|
||||
dataGridViewStudents.BackgroundColor = Color.AliceBlue;
|
||||
dataGridViewStudents.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridViewStudents.Dock = DockStyle.Fill;
|
||||
dataGridViewStudents.Location = new Point(3, 32);
|
||||
dataGridViewStudents.Margin = new Padding(3, 4, 3, 4);
|
||||
dataGridViewStudents.Name = "dataGridViewStudents";
|
||||
dataGridViewStudents.RowHeadersVisible = false;
|
||||
dataGridViewStudents.RowHeadersWidth = 51;
|
||||
dataGridViewStudents.RowTemplate.Height = 24;
|
||||
dataGridViewStudents.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dataGridViewStudents.Size = new Size(1068, 682);
|
||||
dataGridViewStudents.TabIndex = 2;
|
||||
//
|
||||
// menuStrip1
|
||||
//
|
||||
menuStrip1.ImageScalingSize = new Size(20, 20);
|
||||
menuStrip1.Items.AddRange(new ToolStripItem[] { фильтрацияToolStripMenuItem });
|
||||
menuStrip1.Location = new Point(3, 4);
|
||||
menuStrip1.Name = "menuStrip1";
|
||||
menuStrip1.Size = new Size(1068, 28);
|
||||
menuStrip1.TabIndex = 3;
|
||||
menuStrip1.Text = "menuStrip1";
|
||||
//
|
||||
// фильтрацияToolStripMenuItem
|
||||
//
|
||||
фильтрацияToolStripMenuItem.Name = "фильтрацияToolStripMenuItem";
|
||||
фильтрацияToolStripMenuItem.Size = new Size(108, 24);
|
||||
фильтрацияToolStripMenuItem.Text = "Фильтрация";
|
||||
//
|
||||
// SpecPage
|
||||
//
|
||||
SpecPage.Controls.Add(progressBarSpecs);
|
||||
SpecPage.Controls.Add(dataGridViewSpecs);
|
||||
SpecPage.Controls.Add(menuStrip2);
|
||||
SpecPage.Location = new Point(4, 29);
|
||||
SpecPage.Margin = new Padding(3, 4, 3, 4);
|
||||
SpecPage.Name = "SpecPage";
|
||||
SpecPage.Padding = new Padding(3, 4, 3, 4);
|
||||
SpecPage.Size = new Size(1074, 718);
|
||||
SpecPage.TabIndex = 1;
|
||||
SpecPage.Text = "Направления";
|
||||
SpecPage.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// progressBarSpecs
|
||||
//
|
||||
progressBarSpecs.Location = new Point(475, 345);
|
||||
progressBarSpecs.Name = "progressBarSpecs";
|
||||
progressBarSpecs.Size = new Size(125, 29);
|
||||
progressBarSpecs.TabIndex = 5;
|
||||
//
|
||||
// dataGridViewSpecs
|
||||
//
|
||||
dataGridViewSpecs.AllowUserToAddRows = false;
|
||||
dataGridViewSpecs.AllowUserToDeleteRows = false;
|
||||
dataGridViewSpecs.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
|
||||
dataGridViewSpecs.BackgroundColor = Color.AliceBlue;
|
||||
dataGridViewSpecs.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridViewSpecs.Dock = DockStyle.Fill;
|
||||
dataGridViewSpecs.Location = new Point(3, 32);
|
||||
dataGridViewSpecs.Margin = new Padding(3, 4, 3, 4);
|
||||
dataGridViewSpecs.Name = "dataGridViewSpecs";
|
||||
dataGridViewSpecs.RowHeadersVisible = false;
|
||||
dataGridViewSpecs.RowHeadersWidth = 51;
|
||||
dataGridViewSpecs.RowTemplate.Height = 24;
|
||||
dataGridViewSpecs.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dataGridViewSpecs.Size = new Size(1068, 682);
|
||||
dataGridViewSpecs.TabIndex = 2;
|
||||
dataGridViewSpecs.CellDoubleClick += dataGridViewSpecs_CellDoubleClick;
|
||||
dataGridViewSpecs.KeyDown += DataGridViewSpecs_KeyDown;
|
||||
//
|
||||
// menuStrip2
|
||||
//
|
||||
menuStrip2.ImageScalingSize = new Size(20, 20);
|
||||
menuStrip2.Items.AddRange(new ToolStripItem[] { добавитьНаправлениеToolStripMenuItem });
|
||||
menuStrip2.Location = new Point(3, 4);
|
||||
menuStrip2.Name = "menuStrip2";
|
||||
menuStrip2.Size = new Size(1068, 28);
|
||||
menuStrip2.TabIndex = 3;
|
||||
menuStrip2.Text = "menuStrip2";
|
||||
//
|
||||
// добавитьНаправлениеToolStripMenuItem
|
||||
//
|
||||
добавитьНаправлениеToolStripMenuItem.Name = "добавитьНаправлениеToolStripMenuItem";
|
||||
добавитьНаправлениеToolStripMenuItem.Size = new Size(187, 24);
|
||||
добавитьНаправлениеToolStripMenuItem.Text = "Добавить направление";
|
||||
добавитьНаправлениеToolStripMenuItem.Click += добавитьНаправлениеToolStripMenuItem_Click;
|
||||
//
|
||||
// GroupsPage
|
||||
//
|
||||
GroupsPage.Controls.Add(progressBarGroups);
|
||||
GroupsPage.Controls.Add(dataGridViewGroups);
|
||||
GroupsPage.Controls.Add(menuStrip3);
|
||||
GroupsPage.Location = new Point(4, 29);
|
||||
GroupsPage.Margin = new Padding(3, 4, 3, 4);
|
||||
GroupsPage.Name = "GroupsPage";
|
||||
GroupsPage.Size = new Size(1074, 718);
|
||||
GroupsPage.TabIndex = 2;
|
||||
GroupsPage.Text = "Группы";
|
||||
GroupsPage.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// progressBarGroups
|
||||
//
|
||||
progressBarGroups.Location = new Point(475, 345);
|
||||
progressBarGroups.Name = "progressBarGroups";
|
||||
progressBarGroups.Size = new Size(125, 29);
|
||||
progressBarGroups.TabIndex = 4;
|
||||
//
|
||||
// dataGridViewGroups
|
||||
//
|
||||
dataGridViewGroups.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
|
||||
dataGridViewGroups.BackgroundColor = Color.AliceBlue;
|
||||
dataGridViewGroups.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridViewGroups.Dock = DockStyle.Fill;
|
||||
dataGridViewGroups.Location = new Point(0, 28);
|
||||
dataGridViewGroups.Margin = new Padding(3, 4, 3, 4);
|
||||
dataGridViewGroups.Name = "dataGridViewGroups";
|
||||
dataGridViewGroups.RowHeadersVisible = false;
|
||||
dataGridViewGroups.RowHeadersWidth = 51;
|
||||
dataGridViewGroups.RowTemplate.Height = 24;
|
||||
dataGridViewGroups.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dataGridViewGroups.Size = new Size(1074, 690);
|
||||
dataGridViewGroups.TabIndex = 2;
|
||||
//
|
||||
// menuStrip3
|
||||
//
|
||||
menuStrip3.ImageScalingSize = new Size(20, 20);
|
||||
menuStrip3.Items.AddRange(new ToolStripItem[] { toolStripMenuItem2, добавитьГруппуToolStripMenuItem });
|
||||
menuStrip3.Location = new Point(0, 0);
|
||||
menuStrip3.Name = "menuStrip3";
|
||||
menuStrip3.Size = new Size(1074, 28);
|
||||
menuStrip3.TabIndex = 3;
|
||||
menuStrip3.Text = "menuStrip3";
|
||||
//
|
||||
// toolStripMenuItem2
|
||||
//
|
||||
toolStripMenuItem2.Name = "toolStripMenuItem2";
|
||||
toolStripMenuItem2.Size = new Size(108, 24);
|
||||
toolStripMenuItem2.Text = "Фильтрация";
|
||||
//
|
||||
// добавитьГруппуToolStripMenuItem
|
||||
//
|
||||
добавитьГруппуToolStripMenuItem.Name = "добавитьГруппуToolStripMenuItem";
|
||||
добавитьГруппуToolStripMenuItem.Size = new Size(141, 24);
|
||||
добавитьГруппуToolStripMenuItem.Text = "Добавить группу";
|
||||
//
|
||||
// OrdersPage
|
||||
//
|
||||
OrdersPage.BackColor = Color.LightSteelBlue;
|
||||
OrdersPage.Controls.Add(menuStrip4);
|
||||
OrdersPage.Location = new Point(4, 29);
|
||||
OrdersPage.Margin = new Padding(3, 4, 3, 4);
|
||||
OrdersPage.Name = "OrdersPage";
|
||||
OrdersPage.Size = new Size(1074, 718);
|
||||
OrdersPage.TabIndex = 3;
|
||||
OrdersPage.Text = "Приказы";
|
||||
//
|
||||
// menuStrip4
|
||||
//
|
||||
menuStrip4.ImageScalingSize = new Size(20, 20);
|
||||
menuStrip4.Items.AddRange(new ToolStripItem[] { создатьПриказToolStripMenuItem, создатьПриказToolStripMenuItem1 });
|
||||
menuStrip4.Location = new Point(0, 0);
|
||||
menuStrip4.Name = "menuStrip4";
|
||||
menuStrip4.Size = new Size(1074, 28);
|
||||
menuStrip4.TabIndex = 2;
|
||||
menuStrip4.Text = "menuStrip4";
|
||||
//
|
||||
// создатьПриказToolStripMenuItem
|
||||
//
|
||||
создатьПриказToolStripMenuItem.Name = "создатьПриказToolStripMenuItem";
|
||||
создатьПриказToolStripMenuItem.Size = new Size(108, 24);
|
||||
создатьПриказToolStripMenuItem.Text = "Фильтрация";
|
||||
//
|
||||
// создатьПриказToolStripMenuItem1
|
||||
//
|
||||
создатьПриказToolStripMenuItem1.Name = "создатьПриказToolStripMenuItem1";
|
||||
создатьПриказToolStripMenuItem1.Size = new Size(131, 24);
|
||||
создатьПриказToolStripMenuItem1.Text = "Создать приказ";
|
||||
//
|
||||
// FormMain
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
AutoSize = true;
|
||||
ClientSize = new Size(1082, 751);
|
||||
Controls.Add(MainPages);
|
||||
Margin = new Padding(3, 4, 3, 4);
|
||||
Name = "FormMain";
|
||||
Text = "Главная";
|
||||
KeyDown += FormMain_KeyDown;
|
||||
MainPages.ResumeLayout(false);
|
||||
StudentsPage.ResumeLayout(false);
|
||||
StudentsPage.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewStudents).EndInit();
|
||||
menuStrip1.ResumeLayout(false);
|
||||
menuStrip1.PerformLayout();
|
||||
SpecPage.ResumeLayout(false);
|
||||
SpecPage.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewSpecs).EndInit();
|
||||
menuStrip2.ResumeLayout(false);
|
||||
menuStrip2.PerformLayout();
|
||||
GroupsPage.ResumeLayout(false);
|
||||
GroupsPage.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dataGridViewGroups).EndInit();
|
||||
menuStrip3.ResumeLayout(false);
|
||||
menuStrip3.PerformLayout();
|
||||
OrdersPage.ResumeLayout(false);
|
||||
OrdersPage.PerformLayout();
|
||||
menuStrip4.ResumeLayout(false);
|
||||
menuStrip4.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TabControl MainPages;
|
||||
private System.Windows.Forms.TabPage StudentsPage;
|
||||
private System.Windows.Forms.TabPage SpecPage;
|
||||
private System.Windows.Forms.TabPage GroupsPage;
|
||||
private System.Windows.Forms.TabPage OrdersPage;
|
||||
private System.Windows.Forms.DataGridView dataGridViewStudents;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem фильтрацияToolStripMenuItem;
|
||||
private System.Windows.Forms.DataGridView dataGridViewSpecs;
|
||||
private System.Windows.Forms.MenuStrip menuStrip2;
|
||||
private System.Windows.Forms.DataGridView dataGridViewGroups;
|
||||
private System.Windows.Forms.MenuStrip menuStrip3;
|
||||
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem2;
|
||||
private System.Windows.Forms.ToolStripMenuItem добавитьНаправлениеToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem добавитьГруппуToolStripMenuItem;
|
||||
private ProgressBar progressBarGroups;
|
||||
private MenuStrip menuStrip4;
|
||||
private ToolStripMenuItem создатьПриказToolStripMenuItem;
|
||||
private ToolStripMenuItem создатьПриказToolStripMenuItem1;
|
||||
private ProgressBar progressBarStudents;
|
||||
private ProgressBar progressBarSpecs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
using Controller.BusinessLogic;
|
||||
using Controller.Contracts;
|
||||
using Controller.Repository;
|
||||
using DataModels.Models;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormMain : Form
|
||||
{
|
||||
private readonly StudentLogic _studentLogic;
|
||||
private readonly GroupLogic _groupLogic;
|
||||
private readonly SpecializationLogic _specializationLogic;
|
||||
private readonly OrderLogic _orderLogic;
|
||||
|
||||
private readonly StudentRepository studentRepository;
|
||||
private readonly GroupRepository groupRepository;
|
||||
private readonly SpecializationRepository specializationRepository;
|
||||
private readonly OrderRepository orderRepository;
|
||||
private readonly FacultReposirory facultReposirory;
|
||||
|
||||
private int? _id;
|
||||
public int Id
|
||||
{
|
||||
set { _id = value; }
|
||||
}
|
||||
|
||||
private string _currentSearchText = string.Empty;
|
||||
private FormSearch _searchForm;
|
||||
|
||||
public FormMain(StudentLogic studentLogic, GroupLogic groupLogic, SpecializationLogic specializationLogic,
|
||||
OrderLogic orderLogic,
|
||||
StudentRepository studentRepository, GroupRepository groupRepository, SpecializationRepository specializationRepository,
|
||||
OrderRepository orderRepository, FacultReposirory facultReposirory
|
||||
)
|
||||
{
|
||||
_studentLogic = studentLogic;
|
||||
_groupLogic = groupLogic;
|
||||
_specializationLogic = specializationLogic;
|
||||
_orderLogic = orderLogic;
|
||||
|
||||
this.studentRepository = studentRepository;
|
||||
this.groupRepository = groupRepository;
|
||||
this.specializationRepository = specializationRepository;
|
||||
this.orderRepository = orderRepository;
|
||||
this.facultReposirory = facultReposirory;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
dataGridViewSpecs.KeyDown += DataGridViewSpecs_KeyDown;
|
||||
this.KeyPreview = true;
|
||||
this.KeyDown += FormMain_KeyDown;
|
||||
}
|
||||
|
||||
protected async override void OnShown(EventArgs e)
|
||||
{
|
||||
base.OnShown(e);
|
||||
|
||||
if (!StaticSession.IsLogin)
|
||||
{
|
||||
MessageBox.Show("Требуется авторизация");
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var facult = await facultReposirory.Get(StaticSession.CurrentFacultyId);
|
||||
|
||||
// Устанавливаем заголовок с информацией о пользователе
|
||||
this.Text = $"Контингент университета (Факультет {facult.Name})";
|
||||
|
||||
// Загружаем данные
|
||||
LoadInitialData();
|
||||
}
|
||||
|
||||
private async void LoadInitialData()
|
||||
{
|
||||
await LoadSpecializationData();
|
||||
await LoadGroupsData();
|
||||
await LoadStudentsData();
|
||||
}
|
||||
|
||||
private bool _studentsLoaded = false;
|
||||
private bool _groupsLoaded = false;
|
||||
private bool _specsLoaded = false;
|
||||
|
||||
private async void MainPages_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (MainPages.SelectedTab == StudentsPage && !_studentsLoaded)
|
||||
{
|
||||
await LoadStudentsData();
|
||||
_studentsLoaded = true;
|
||||
}
|
||||
else if (MainPages.SelectedTab == GroupsPage && !_groupsLoaded)
|
||||
{
|
||||
await LoadGroupsData();
|
||||
_groupsLoaded = true;
|
||||
}
|
||||
else if (MainPages.SelectedTab == SpecPage && !_specsLoaded)
|
||||
{
|
||||
await LoadSpecializationData();
|
||||
_specsLoaded = true;
|
||||
}
|
||||
else if (MainPages.SelectedTab == OrdersPage)
|
||||
{
|
||||
// Дополнительная логика для этой вкладки
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowLoading(bool show, ProgressBar progressBar)
|
||||
{
|
||||
if (progressBar.InvokeRequired)
|
||||
{
|
||||
progressBar.Invoke(new Action(() => ShowLoading(show, progressBar)));
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.Visible = show;
|
||||
progressBar.Style = show ? ProgressBarStyle.Marquee : ProgressBarStyle.Blocks;
|
||||
if (show) progressBar.BringToFront();
|
||||
}
|
||||
|
||||
private async Task LoadStudentsData()
|
||||
{
|
||||
await ShowLoading(true, progressBarStudents);
|
||||
try
|
||||
{
|
||||
var studLogic = new StudentLogic(studentRepository, groupRepository, specializationRepository);
|
||||
var students = await studLogic.GetViewModel();
|
||||
|
||||
dataGridViewStudents.DataSource = students;
|
||||
dataGridViewStudents.AutoGenerateColumns = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ShowLoading(false, progressBarStudents);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DataGridViewSpecs_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Delete && dataGridViewSpecs.SelectedRows.Count > 0)
|
||||
{
|
||||
// Получаем выбранную строку
|
||||
var selectedRow = dataGridViewSpecs.SelectedRows[0];
|
||||
var specId = (int)selectedRow.Cells["Id"].Value;
|
||||
var specName = selectedRow.Cells["Name"].Value?.ToString() ?? "без названия";
|
||||
|
||||
// Запрос подтверждения
|
||||
var result = MessageBox.Show(
|
||||
$"Вы действительно хотите удалить направление '{specName}' (\nЭто действие нельзя отменить!",
|
||||
"Подтверждение удаления",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning,
|
||||
MessageBoxDefaultButton.Button2);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ShowLoading(true, progressBarSpecs);
|
||||
|
||||
// Удаляем специализацию
|
||||
var deleted = await specializationRepository.Remove(specId);
|
||||
|
||||
if (deleted != null)
|
||||
{
|
||||
// Обновляем данные
|
||||
await LoadSpecializationData();
|
||||
MessageBox.Show("Направление успешно удалено", "Успех",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("Не удалось удалить направление", "Ошибка",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Ошибка при удалении: {ex.Message}", "Ошибка",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ShowLoading(false, progressBarSpecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadGroupsData()
|
||||
{
|
||||
await ShowLoading(true, progressBarGroups);
|
||||
try
|
||||
{
|
||||
var groupLogic = new GroupLogic(specializationRepository, groupRepository);
|
||||
var groups = await groupLogic.GetViewModel();
|
||||
|
||||
dataGridViewGroups.DataSource = groups;
|
||||
dataGridViewGroups.AutoGenerateColumns = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ShowLoading(false, progressBarGroups);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSpecializationData()
|
||||
{
|
||||
await ShowLoading(true, progressBarSpecs);
|
||||
try
|
||||
{
|
||||
var byFacultModel = new SpecializationSearch
|
||||
{
|
||||
FacultId = StaticSession.CurrentFacultyId,
|
||||
Name = _currentSearchText
|
||||
};
|
||||
var specLogic = new SpecializationLogic(specializationRepository);
|
||||
var specs = await specLogic.GetViewModel(byFacultModel);
|
||||
|
||||
dataGridViewSpecs.DataSource = specs;
|
||||
dataGridViewSpecs.AutoGenerateColumns = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ShowLoading(false, progressBarSpecs);
|
||||
}
|
||||
}
|
||||
|
||||
private void добавитьНаправлениеToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (dataGridViewSpecs.SelectedRows.Count > 0)
|
||||
{
|
||||
var service = Program.ServiceProvider?.GetService(typeof(FormAddUpdSpec));
|
||||
if (service is FormAddUpdSpec form)
|
||||
{
|
||||
if (form.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
LoadSpecializationData();
|
||||
form.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void dataGridViewSpecs_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
if (e.RowIndex >= 0)
|
||||
{
|
||||
var selectedRow = dataGridViewSpecs.Rows[e.RowIndex];
|
||||
var specId = (int)selectedRow.Cells["Id"].Value;
|
||||
var service = Program.ServiceProvider?.GetService(typeof(FormAddUpdSpec));
|
||||
if (service is FormAddUpdSpec form)
|
||||
{
|
||||
form.Id = specId;
|
||||
if (form.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
LoadSpecializationData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FormMain_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.F2 && !e.Control)
|
||||
{
|
||||
ShowSearchForm();
|
||||
}
|
||||
else if (e.KeyCode == Keys.F2 && e.Control)
|
||||
{
|
||||
ResetSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowSearchForm()
|
||||
{
|
||||
if (_searchForm == null || _searchForm.IsDisposed)
|
||||
{
|
||||
_searchForm = new FormSearch();
|
||||
_searchForm.OnSearchTextChanged += (s, e) =>
|
||||
{
|
||||
_currentSearchText = _searchForm.SearchText;
|
||||
LoadSpecializationData();
|
||||
};
|
||||
_searchForm.FormClosed += (s, e) =>
|
||||
{
|
||||
if (_searchForm.DialogResult == DialogResult.OK && _searchForm.ResetSearch)
|
||||
{
|
||||
ResetSearch();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_searchForm.Show();
|
||||
_searchForm.Activate();
|
||||
}
|
||||
|
||||
// Сброс поиска
|
||||
private void ResetSearch()
|
||||
{
|
||||
_currentSearchText = string.Empty;
|
||||
LoadSpecializationData();
|
||||
}
|
||||
|
||||
private void MainPages_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (MainPages.SelectedTab == StudentsPage && !_studentsLoaded)
|
||||
{
|
||||
|
||||
}
|
||||
else if (MainPages.SelectedTab == GroupsPage && !_groupsLoaded)
|
||||
{
|
||||
|
||||
}
|
||||
else if (MainPages.SelectedTab == SpecPage && !_specsLoaded)
|
||||
{
|
||||
if (e.KeyCode == Keys.Insert)
|
||||
{
|
||||
var service = Program.ServiceProvider?.GetService(typeof(FormAddUpdSpec));
|
||||
if (service is FormAddUpdSpec form)
|
||||
{
|
||||
if (form.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
LoadSpecializationData();
|
||||
form.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (MainPages.SelectedTab == OrdersPage)
|
||||
{
|
||||
// Дополнительная логика для этой вкладки
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>153, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>289, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip4.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>425, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>153, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>289, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip4.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>425, 17</value>
|
||||
</metadata>
|
||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>25</value>
|
||||
</metadata>
|
||||
</root>
|
||||
59
contingent_report_desktop/App_contingent_university/Forms/FormSearch.Designer.cs
generated
Normal file
59
contingent_report_desktop/App_contingent_university/Forms/FormSearch.Designer.cs
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormSearch
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
textBoxSearch = new TextBox();
|
||||
SuspendLayout();
|
||||
//
|
||||
// textBoxSearch
|
||||
//
|
||||
textBoxSearch.Location = new Point(16, 10);
|
||||
textBoxSearch.Name = "textBoxSearch";
|
||||
textBoxSearch.Size = new Size(293, 27);
|
||||
textBoxSearch.TabIndex = 0;
|
||||
textBoxSearch.TextChanged += textBoxSearch_TextChanged;
|
||||
//
|
||||
// FormSearch
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(321, 46);
|
||||
Controls.Add(textBoxSearch);
|
||||
Name = "FormSearch";
|
||||
Text = "Поиск";
|
||||
KeyDown += FormSearch_KeyDown;
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private TextBox textBoxSearch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormSearch : Form
|
||||
{
|
||||
public string SearchText { get; private set; } = string.Empty;
|
||||
public bool ResetSearch { get; private set; } = false;
|
||||
|
||||
public FormSearch()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.KeyPreview = true;
|
||||
}
|
||||
|
||||
private void textBoxSearch_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
SearchText = textBoxSearch.Text;
|
||||
OnSearchTextChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void FormSearch_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter)
|
||||
{
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
else if (e.KeyCode == Keys.F2 && e.Control)
|
||||
{
|
||||
ResetSearch = true;
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
else if (e.KeyCode == Keys.Escape)
|
||||
{
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler OnSearchTextChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
108
contingent_report_desktop/App_contingent_university/Forms/FormSpecs.Designer.cs
generated
Normal file
108
contingent_report_desktop/App_contingent_university/Forms/FormSpecs.Designer.cs
generated
Normal file
@@ -0,0 +1,108 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormSpecsFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.buttonOK = new System.Windows.Forms.Button();
|
||||
this.textBoxCode = new System.Windows.Forms.TextBox();
|
||||
this.labelCode = new System.Windows.Forms.Label();
|
||||
this.textBoxName = new System.Windows.Forms.TextBox();
|
||||
this.labelName = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonOK.BackColor = System.Drawing.Color.Orange;
|
||||
this.buttonOK.ForeColor = System.Drawing.Color.Black;
|
||||
this.buttonOK.Location = new System.Drawing.Point(158, 100);
|
||||
this.buttonOK.Name = "buttonOK";
|
||||
this.buttonOK.Size = new System.Drawing.Size(113, 37);
|
||||
this.buttonOK.TabIndex = 1;
|
||||
this.buttonOK.Text = "Применить";
|
||||
this.buttonOK.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// textBoxCode
|
||||
//
|
||||
this.textBoxCode.Location = new System.Drawing.Point(107, 48);
|
||||
this.textBoxCode.Name = "textBoxCode";
|
||||
this.textBoxCode.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxCode.TabIndex = 8;
|
||||
//
|
||||
// labelCode
|
||||
//
|
||||
this.labelCode.AutoSize = true;
|
||||
this.labelCode.Location = new System.Drawing.Point(12, 54);
|
||||
this.labelCode.Name = "labelCode";
|
||||
this.labelCode.Size = new System.Drawing.Size(31, 16);
|
||||
this.labelCode.TabIndex = 7;
|
||||
this.labelCode.Text = "Код";
|
||||
//
|
||||
// textBoxName
|
||||
//
|
||||
this.textBoxName.Location = new System.Drawing.Point(107, 12);
|
||||
this.textBoxName.Name = "textBoxName";
|
||||
this.textBoxName.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxName.TabIndex = 6;
|
||||
//
|
||||
// labelName
|
||||
//
|
||||
this.labelName.AutoSize = true;
|
||||
this.labelName.Location = new System.Drawing.Point(12, 18);
|
||||
this.labelName.Name = "labelName";
|
||||
this.labelName.Size = new System.Drawing.Size(73, 16);
|
||||
this.labelName.TabIndex = 5;
|
||||
this.labelName.Text = "Название";
|
||||
//
|
||||
// FormSpecsFilter
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.LightSteelBlue;
|
||||
this.ClientSize = new System.Drawing.Size(283, 149);
|
||||
this.Controls.Add(this.textBoxCode);
|
||||
this.Controls.Add(this.labelCode);
|
||||
this.Controls.Add(this.textBoxName);
|
||||
this.Controls.Add(this.labelName);
|
||||
this.Controls.Add(this.buttonOK);
|
||||
this.Name = "FormSpecsFilter";
|
||||
this.Text = "Фильтр";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Button buttonOK;
|
||||
private System.Windows.Forms.TextBox textBoxCode;
|
||||
private System.Windows.Forms.Label labelCode;
|
||||
private System.Windows.Forms.TextBox textBoxName;
|
||||
private System.Windows.Forms.Label labelName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormSpecsFilter : Form
|
||||
{
|
||||
public FormSpecsFilter()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
113
contingent_report_desktop/App_contingent_university/Forms/FormUpdSpec.Designer.cs
generated
Normal file
113
contingent_report_desktop/App_contingent_university/Forms/FormUpdSpec.Designer.cs
generated
Normal file
@@ -0,0 +1,113 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormAddUpdSpec
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
textBoxCode = new TextBox();
|
||||
labelCode = new Label();
|
||||
textBoxName = new TextBox();
|
||||
labelName = new Label();
|
||||
buttonOK = new Button();
|
||||
SuspendLayout();
|
||||
//
|
||||
// textBoxCode
|
||||
//
|
||||
textBoxCode.Location = new Point(109, 60);
|
||||
textBoxCode.Margin = new Padding(3, 4, 3, 4);
|
||||
textBoxCode.Name = "textBoxCode";
|
||||
textBoxCode.Size = new Size(160, 27);
|
||||
textBoxCode.TabIndex = 13;
|
||||
//
|
||||
// labelCode
|
||||
//
|
||||
labelCode.AutoSize = true;
|
||||
labelCode.Location = new Point(14, 68);
|
||||
labelCode.Name = "labelCode";
|
||||
labelCode.Size = new Size(35, 20);
|
||||
labelCode.TabIndex = 12;
|
||||
labelCode.Text = "Код";
|
||||
//
|
||||
// textBoxName
|
||||
//
|
||||
textBoxName.Location = new Point(109, 15);
|
||||
textBoxName.Margin = new Padding(3, 4, 3, 4);
|
||||
textBoxName.Name = "textBoxName";
|
||||
textBoxName.Size = new Size(160, 27);
|
||||
textBoxName.TabIndex = 11;
|
||||
//
|
||||
// labelName
|
||||
//
|
||||
labelName.AutoSize = true;
|
||||
labelName.Location = new Point(14, 22);
|
||||
labelName.Name = "labelName";
|
||||
labelName.Size = new Size(77, 20);
|
||||
labelName.TabIndex = 10;
|
||||
labelName.Text = "Название";
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
buttonOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
buttonOK.BackColor = Color.Orange;
|
||||
buttonOK.ForeColor = Color.Black;
|
||||
buttonOK.Location = new Point(163, 168);
|
||||
buttonOK.Margin = new Padding(3, 4, 3, 4);
|
||||
buttonOK.Name = "buttonOK";
|
||||
buttonOK.Size = new Size(113, 46);
|
||||
buttonOK.TabIndex = 9;
|
||||
buttonOK.Text = "Сохранить";
|
||||
buttonOK.UseVisualStyleBackColor = false;
|
||||
buttonOK.Click += buttonOK_Click;
|
||||
//
|
||||
// FormAddUpdSpec
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
BackColor = Color.LightSteelBlue;
|
||||
ClientSize = new Size(288, 229);
|
||||
Controls.Add(textBoxCode);
|
||||
Controls.Add(labelCode);
|
||||
Controls.Add(textBoxName);
|
||||
Controls.Add(labelName);
|
||||
Controls.Add(buttonOK);
|
||||
Margin = new Padding(3, 4, 3, 4);
|
||||
Name = "FormAddUpdSpec";
|
||||
Text = "Создать/редактировать";
|
||||
Load += FormAddUpdSpec_Load;
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TextBox textBoxCode;
|
||||
private System.Windows.Forms.Label labelCode;
|
||||
private System.Windows.Forms.TextBox textBoxName;
|
||||
private System.Windows.Forms.Label labelName;
|
||||
private System.Windows.Forms.Button buttonOK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Controller.BusinessLogic;
|
||||
using Controller.Repository;
|
||||
using DataModels.Models;
|
||||
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormAddUpdSpec : Form
|
||||
{
|
||||
private readonly SpecializationRepository specializationRepository;
|
||||
private readonly SpecializationLogic specializationLogic;
|
||||
|
||||
private int? _id;
|
||||
public int Id
|
||||
{
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
}
|
||||
}
|
||||
public FormAddUpdSpec(SpecializationLogic logic, SpecializationRepository repo)
|
||||
{
|
||||
specializationLogic = logic;
|
||||
specializationRepository = repo;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
if (!_id.HasValue) return;
|
||||
try
|
||||
{
|
||||
// Добавляем индикатор загрузки
|
||||
textBoxCode.Text = "Загрузка...";
|
||||
textBoxName.Text = "Загрузка...";
|
||||
|
||||
// Явно ожидаем загрузку
|
||||
var spec = await specializationRepository.Get(_id.Value);
|
||||
|
||||
if (spec != null)
|
||||
{
|
||||
textBoxCode.Text = spec.Code ?? "Не указано";
|
||||
textBoxName.Text = spec.Name ?? "Не указано";
|
||||
|
||||
if (spec.FacultyId == 0) // Проверка на недопустимое значение
|
||||
{
|
||||
MessageBox.Show("Ошибка: не указан факультет");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("Специализация не найдена");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Ошибка загрузки: {ex.Message}");
|
||||
textBoxCode.Text = "Ошибка";
|
||||
textBoxName.Text = "Ошибка";
|
||||
}
|
||||
}
|
||||
|
||||
private async void buttonOK_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_id.HasValue)
|
||||
{
|
||||
// Получаем существующую специализацию
|
||||
var existingSpec = await specializationRepository.Get(_id.Value);
|
||||
if (existingSpec == null)
|
||||
{
|
||||
MessageBox.Show("Специализация не найдена");
|
||||
return;
|
||||
}
|
||||
|
||||
// Обновляем поля
|
||||
existingSpec.Code = textBoxCode.Text;
|
||||
existingSpec.Name = textBoxName.Text;
|
||||
existingSpec.FacultyId = StaticSession.CurrentFacultyId;
|
||||
|
||||
await specializationRepository.Update(existingSpec);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Создаем новую специализацию
|
||||
var newSpec = new Specialization
|
||||
{
|
||||
Code = textBoxCode.Text,
|
||||
Name = textBoxName.Text,
|
||||
FacultyId = StaticSession.CurrentFacultyId,
|
||||
};
|
||||
await specializationRepository.Add(newSpec);
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Ошибка: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void FormAddUpdSpec_Load(object sender, EventArgs e)
|
||||
{
|
||||
LoadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
153
contingent_report_desktop/App_contingent_university/Forms/FormUpdStudent.Designer.cs
generated
Normal file
153
contingent_report_desktop/App_contingent_university/Forms/FormUpdStudent.Designer.cs
generated
Normal file
@@ -0,0 +1,153 @@
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
partial class FormUpdStudent
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.labelGroup = new System.Windows.Forms.Label();
|
||||
this.comboBoxGroup = new System.Windows.Forms.ComboBox();
|
||||
this.textBoxPatronimic = new System.Windows.Forms.TextBox();
|
||||
this.labelPatronimic = new System.Windows.Forms.Label();
|
||||
this.textBoxName = new System.Windows.Forms.TextBox();
|
||||
this.labelI = new System.Windows.Forms.Label();
|
||||
this.textBoxLastName = new System.Windows.Forms.TextBox();
|
||||
this.labelF = new System.Windows.Forms.Label();
|
||||
this.buttonOK = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// labelGroup
|
||||
//
|
||||
this.labelGroup.AutoSize = true;
|
||||
this.labelGroup.Location = new System.Drawing.Point(572, 7);
|
||||
this.labelGroup.Name = "labelGroup";
|
||||
this.labelGroup.Size = new System.Drawing.Size(54, 16);
|
||||
this.labelGroup.TabIndex = 16;
|
||||
this.labelGroup.Text = "Группа";
|
||||
//
|
||||
// comboBoxGroup
|
||||
//
|
||||
this.comboBoxGroup.FormattingEnabled = true;
|
||||
this.comboBoxGroup.Location = new System.Drawing.Point(575, 26);
|
||||
this.comboBoxGroup.Name = "comboBoxGroup";
|
||||
this.comboBoxGroup.Size = new System.Drawing.Size(142, 24);
|
||||
this.comboBoxGroup.TabIndex = 15;
|
||||
//
|
||||
// textBoxPatronimic
|
||||
//
|
||||
this.textBoxPatronimic.Location = new System.Drawing.Point(389, 28);
|
||||
this.textBoxPatronimic.Name = "textBoxPatronimic";
|
||||
this.textBoxPatronimic.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxPatronimic.TabIndex = 14;
|
||||
//
|
||||
// labelPatronimic
|
||||
//
|
||||
this.labelPatronimic.AutoSize = true;
|
||||
this.labelPatronimic.Location = new System.Drawing.Point(386, 9);
|
||||
this.labelPatronimic.Name = "labelPatronimic";
|
||||
this.labelPatronimic.Size = new System.Drawing.Size(70, 16);
|
||||
this.labelPatronimic.TabIndex = 13;
|
||||
this.labelPatronimic.Text = "Отчество";
|
||||
//
|
||||
// textBoxName
|
||||
//
|
||||
this.textBoxName.Location = new System.Drawing.Point(204, 28);
|
||||
this.textBoxName.Name = "textBoxName";
|
||||
this.textBoxName.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxName.TabIndex = 12;
|
||||
//
|
||||
// labelI
|
||||
//
|
||||
this.labelI.AutoSize = true;
|
||||
this.labelI.Location = new System.Drawing.Point(201, 9);
|
||||
this.labelI.Name = "labelI";
|
||||
this.labelI.Size = new System.Drawing.Size(33, 16);
|
||||
this.labelI.TabIndex = 11;
|
||||
this.labelI.Text = "Имя";
|
||||
//
|
||||
// textBoxLastName
|
||||
//
|
||||
this.textBoxLastName.Location = new System.Drawing.Point(17, 28);
|
||||
this.textBoxLastName.Name = "textBoxLastName";
|
||||
this.textBoxLastName.Size = new System.Drawing.Size(160, 22);
|
||||
this.textBoxLastName.TabIndex = 18;
|
||||
//
|
||||
// labelF
|
||||
//
|
||||
this.labelF.AutoSize = true;
|
||||
this.labelF.Location = new System.Drawing.Point(14, 9);
|
||||
this.labelF.Name = "labelF";
|
||||
this.labelF.Size = new System.Drawing.Size(66, 16);
|
||||
this.labelF.TabIndex = 17;
|
||||
this.labelF.Text = "Фамилия";
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonOK.BackColor = System.Drawing.Color.Orange;
|
||||
this.buttonOK.ForeColor = System.Drawing.Color.Black;
|
||||
this.buttonOK.Location = new System.Drawing.Point(604, 81);
|
||||
this.buttonOK.Name = "buttonOK";
|
||||
this.buttonOK.Size = new System.Drawing.Size(113, 37);
|
||||
this.buttonOK.TabIndex = 19;
|
||||
this.buttonOK.Text = "Сохранить";
|
||||
this.buttonOK.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// FormUpdStudent
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.LightSteelBlue;
|
||||
this.ClientSize = new System.Drawing.Size(746, 130);
|
||||
this.Controls.Add(this.buttonOK);
|
||||
this.Controls.Add(this.textBoxLastName);
|
||||
this.Controls.Add(this.labelF);
|
||||
this.Controls.Add(this.labelGroup);
|
||||
this.Controls.Add(this.comboBoxGroup);
|
||||
this.Controls.Add(this.textBoxPatronimic);
|
||||
this.Controls.Add(this.labelPatronimic);
|
||||
this.Controls.Add(this.textBoxName);
|
||||
this.Controls.Add(this.labelI);
|
||||
this.Name = "FormUpdStudent";
|
||||
this.Text = "Редактировать студента";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label labelGroup;
|
||||
private System.Windows.Forms.ComboBox comboBoxGroup;
|
||||
private System.Windows.Forms.TextBox textBoxPatronimic;
|
||||
private System.Windows.Forms.Label labelPatronimic;
|
||||
private System.Windows.Forms.TextBox textBoxName;
|
||||
private System.Windows.Forms.Label labelI;
|
||||
private System.Windows.Forms.TextBox textBoxLastName;
|
||||
private System.Windows.Forms.Label labelF;
|
||||
private System.Windows.Forms.Button buttonOK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace App_contingent_university.Forms
|
||||
{
|
||||
public partial class FormUpdStudent : Form
|
||||
{
|
||||
public FormUpdStudent()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -0,0 +1,70 @@
|
||||
using App_contingent_university.Forms;
|
||||
using Controller.BusinessLogic;
|
||||
using Controller.Repository;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace App_contingent_university
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
public static IServiceProvider? ServiceProvider { get; private set; }
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
// Настройка сервисов
|
||||
var services = ConfigureServices();
|
||||
ServiceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Сначала показываем форму аутентификации
|
||||
using (var authScope = ServiceProvider.CreateScope())
|
||||
{
|
||||
var authForm = authScope.ServiceProvider.GetRequiredService<FormAuth>();
|
||||
if (authForm.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
// После успешной аутентификации показываем главную форму
|
||||
using (var mainScope = ServiceProvider.CreateScope())
|
||||
{
|
||||
var mainForm = mainScope.ServiceProvider.GetRequiredService<FormMain>();
|
||||
Application.Run(mainForm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ServiceCollection ConfigureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
// Регистрация репозиториев
|
||||
services.AddScoped<StudentRepository>();
|
||||
services.AddScoped<GroupRepository>();
|
||||
services.AddScoped<SpecializationRepository>();
|
||||
services.AddScoped<OrderRepository>();
|
||||
services.AddScoped<DeanRepository>();
|
||||
services.AddScoped<FacultReposirory>();
|
||||
|
||||
// Регистрация бизнес-логики
|
||||
services.AddScoped<StudentLogic>();
|
||||
services.AddScoped<GroupLogic>();
|
||||
services.AddScoped<SpecializationLogic>();
|
||||
services.AddScoped<OrderLogic>();
|
||||
services.AddScoped<DeanLogic>();
|
||||
|
||||
// Регистрация форм
|
||||
services.AddScoped<FormMain>();
|
||||
services.AddScoped<FormAddUpdSpec>();
|
||||
services.AddScoped<FormAuth>();
|
||||
services.AddScoped<FormSearch>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// Общие сведения об этой сборке предоставляются следующим набором
|
||||
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
|
||||
// связанных со сборкой.
|
||||
[assembly: AssemblyTitle("App_contingent_university")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("App_contingent_university")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
|
||||
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
|
||||
// COM, следует установить атрибут ComVisible в TRUE для этого типа.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
|
||||
[assembly: Guid("4aaf178e-2e63-49c7-a054-1cf8618b9db1")]
|
||||
|
||||
// Сведения о версии сборки состоят из указанных ниже четырех значений:
|
||||
//
|
||||
// Основной номер версии
|
||||
// Дополнительный номер версии
|
||||
// Номер сборки
|
||||
// Редакция
|
||||
//
|
||||
// Можно задать все значения или принять номера сборки и редакции по умолчанию
|
||||
// используя "*", как показано ниже:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
71
contingent_report_desktop/App_contingent_university/Properties/Resources.Designer.cs
generated
Normal file
71
contingent_report_desktop/App_contingent_university/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Этот код создан программным средством.
|
||||
// Версия среды выполнения: 4.0.30319.42000
|
||||
//
|
||||
// Изменения в этом файле могут привести к неправильному поведению и будут утрачены, если
|
||||
// код создан повторно.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace App_contingent_university.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Класс ресурсов со строгим типом для поиска локализованных строк и пр.
|
||||
/// </summary>
|
||||
// Этот класс был автоматически создан при помощи StronglyTypedResourceBuilder
|
||||
// класс с помощью таких средств, как ResGen или Visual Studio.
|
||||
// Для добавления или удаления члена измените файл .ResX, а затем перезапустите ResGen
|
||||
// с параметром /str или заново постройте свой VS-проект.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возврат кэшированного экземпляра ResourceManager, используемого этим классом.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("App_contingent_university.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Переопределяет свойство CurrentUICulture текущего потока для всех
|
||||
/// подстановки ресурсов с помощью этого класса ресурсов со строгим типом.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
30
contingent_report_desktop/App_contingent_university/Properties/Settings.Designer.cs
generated
Normal file
30
contingent_report_desktop/App_contingent_university/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace App_contingent_university.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
62
contingent_report_desktop/Controller/AbstractRepository.cs
Normal file
62
contingent_report_desktop/Controller/AbstractRepository.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Text.Json;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace Controller
|
||||
{
|
||||
public abstract class AbstractRepository<T, TSearch> where T : class where TSearch : class
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public abstract string BaseUrl { get; set; }
|
||||
|
||||
public AbstractRepository(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<T> Get(int id)
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"{BaseUrl}/{id}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<T>()
|
||||
?? throw new JsonException($"Ошибка при десериализации ПРИ ПОЛУЧЕНИИ объекта {typeof(T)}");
|
||||
}
|
||||
|
||||
public async Task<List<T>> GetAll()
|
||||
{
|
||||
var response = await _httpClient.GetAsync(BaseUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<List<T>>()
|
||||
?? new List<T>();
|
||||
}
|
||||
public async Task<List<T>> GetFilteredList(TSearch model)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
return new List<T>();
|
||||
}
|
||||
var query = BuildQueryString(model);
|
||||
var response = await _httpClient.GetAsync($"{BaseUrl}/filter{query}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<List<T>>()
|
||||
?? new List<T>();
|
||||
}
|
||||
public async Task<T> Add(T entity)
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync(BaseUrl, entity);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<T>()
|
||||
?? throw new JsonException($"Ошибка при десериализации ПРИ СОЗДАНИИ объекта {typeof(T)}");
|
||||
}
|
||||
public async Task<T> Remove(int id)
|
||||
{
|
||||
var response = await _httpClient.DeleteAsync($"{BaseUrl}/{id}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<T>()
|
||||
?? throw new JsonException($"Ошибка при десериализации ПРИ УДАЛЕНИИ объекта {typeof(T)}");
|
||||
}
|
||||
public abstract Task<T> Update(T entity);
|
||||
|
||||
public abstract string BuildQueryString(TSearch model);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class ContingentReportLogic
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class DeanLogic
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class FacultLogic
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Controller.Repository;
|
||||
using Controller.ViewModels;
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class GroupLogic
|
||||
{
|
||||
private readonly SpecializationRepository _specializationRepository;
|
||||
private readonly GroupRepository _groupRepository;
|
||||
public GroupLogic(SpecializationRepository rep, GroupRepository groupRepository)
|
||||
{
|
||||
_specializationRepository = rep;
|
||||
_groupRepository = groupRepository;
|
||||
}
|
||||
|
||||
public async Task<List<GroupViewModel>> GetViewModel()
|
||||
{
|
||||
var groups = await _groupRepository.GetAll();
|
||||
var results = new List<GroupViewModel>();
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
|
||||
var spec = group.SpecializationId.HasValue
|
||||
? await _specializationRepository.Get(group.SpecializationId.Value) : null;
|
||||
|
||||
Console.WriteLine(group.SpecializationId);
|
||||
|
||||
results.Add(new GroupViewModel(group, spec));
|
||||
}
|
||||
return results;
|
||||
|
||||
//var specs = new List<Specialization>
|
||||
//{
|
||||
// new Specialization{Id = 1, Code = "1234", Name = "Программная инженерия"},
|
||||
// new Specialization{Id = 2, Code = "5678", Name = "Информатика"},
|
||||
|
||||
//};
|
||||
|
||||
//var groups = new List<Group>
|
||||
//{
|
||||
// new Group{Id = 1, Course = 3, Name = "ПИ", Number = 1, CountStudents = 29, SpecializationId = 1},
|
||||
// new Group{Id = 2, Course = 3, Name = "ПМ", Number = 4, CountStudents = 24, SpecializationId = 2},
|
||||
|
||||
//};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class LearningPlanLogic
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class OrderLogic
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Controller.Contracts;
|
||||
using Controller.Repository;
|
||||
using Controller.ViewModels;
|
||||
using DataModels.Enums;
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class SpecializationLogic
|
||||
{
|
||||
private readonly SpecializationRepository _specializationRepository;
|
||||
public SpecializationLogic(SpecializationRepository rep)
|
||||
{
|
||||
_specializationRepository = rep;
|
||||
}
|
||||
|
||||
public async Task<List<SpecialisationViewModel>> GetViewModel(SpecializationSearch model)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
model = new SpecializationSearch { FacultId = StaticSession.CurrentFacultyId };
|
||||
}
|
||||
|
||||
var specs = await _specializationRepository.GetFilteredList(model);
|
||||
|
||||
if (!string.IsNullOrEmpty(model.Search))
|
||||
{
|
||||
var searchTerm = model.Name.ToLower();
|
||||
specs = specs.Where(s =>
|
||||
(s.Name != null && s.Name.ToLower().Contains(searchTerm)) ||
|
||||
(s.Code != null && s.Code.ToLower().Contains(searchTerm))
|
||||
).ToList();
|
||||
}
|
||||
|
||||
// Преобразуем в ViewModel
|
||||
return specs.Select(s => new SpecialisationViewModel(s)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Controller.Repository;
|
||||
using Controller.ViewModels;
|
||||
using DataModels.Enums;
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class StudentLogic
|
||||
{
|
||||
private readonly StudentRepository _studentRepository;
|
||||
private readonly GroupRepository _groupRepository;
|
||||
private readonly SpecializationRepository _specializationRepository;
|
||||
|
||||
public StudentLogic(StudentRepository studentRepository, GroupRepository groupRepository, SpecializationRepository specializationRepository)
|
||||
{
|
||||
_studentRepository = studentRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_specializationRepository = specializationRepository;
|
||||
}
|
||||
|
||||
public async Task<List<StudentViewModel>> GetViewModel()
|
||||
{
|
||||
var students = await _studentRepository.GetAll();
|
||||
|
||||
//var students = new List<Student>
|
||||
//{
|
||||
// new Student { Id = 1, Name = "Иванов", GroupId = 1, SpecializationId = null, Status = Status.AcademicLeave },
|
||||
// new Student { Id = 2, Name = "Петров", GroupId = null, SpecializationId = null, Status = Status.Studying },
|
||||
// new Student { Id = 3, Name = "Сидорова", GroupId = 2, SpecializationId = null }
|
||||
//};
|
||||
|
||||
var result = new List<StudentViewModel>();
|
||||
|
||||
//var groups = new List<Group>
|
||||
// {
|
||||
// new Group { Id = 1, Name = "Группа 101", Course = 1, SpecializationId = 1, MaxStudentCount = 25, Number = 101 },
|
||||
// new Group { Id = 2, Name = "Группа 202", Course = 2, SpecializationId = 2, MaxStudentCount = 30, Number = 202 },
|
||||
// new Group { Id = 3, Name = "Группа 303", Course = 3, SpecializationId = 3, MaxStudentCount = 20, Number = 303 }
|
||||
// };
|
||||
|
||||
// Моковые данные специализаций
|
||||
//var specializations = new List<Specialization>
|
||||
// {
|
||||
// new Specialization { Id = 1, Name = "Информатика", Code = "INF" },
|
||||
// new Specialization { Id = 2, Name = "Математика", Code = "MATH" },
|
||||
// new Specialization { Id = 3, Name = "Физика", Code = "PHYS" }
|
||||
// };
|
||||
|
||||
foreach (var student in students)
|
||||
{
|
||||
var group = student.GroupId.HasValue
|
||||
? await _groupRepository.Get(student.GroupId.Value) : null;
|
||||
|
||||
var specialization = student.SpecializationId.HasValue
|
||||
? await _specializationRepository.Get(student.SpecializationId.Value) : null;
|
||||
|
||||
|
||||
//var group = student.GroupId.HasValue
|
||||
// ? groups.FirstOrDefault(g => g.Id == student.GroupId.Value)
|
||||
// : null;
|
||||
|
||||
//var specialization = student.SpecializationId.HasValue
|
||||
// ? specializations.FirstOrDefault(s => s.Id == student.SpecializationId.Value)
|
||||
// : group?.SpecializationId != null
|
||||
// ? specializations.FirstOrDefault(s => s.Id == group.SpecializationId)
|
||||
// : null;
|
||||
|
||||
result.Add(new StudentViewModel(student, group, specialization));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using DataModels.Models;
|
||||
|
||||
namespace Controller.BusinessLogic
|
||||
{
|
||||
public class UserLogic
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class DeanSearch
|
||||
{
|
||||
public string? Email { get; set; }
|
||||
public string? Password { get; set;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class FacultSearch
|
||||
{
|
||||
public string? Name { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class GroupSearch
|
||||
{
|
||||
public string? Name { get; set; } = string.Empty;
|
||||
public int? Number { get; set; }
|
||||
public int? Course { get; set; }
|
||||
public int? MaxStudentCount { get; set; }
|
||||
public int? SpecializationId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class LearningPlanSearch
|
||||
{
|
||||
public int? SpecId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using DataModels.Enums;
|
||||
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class OrderSearch
|
||||
{
|
||||
public OrderType? OrderType { get; set; }
|
||||
public int? Number { get; set; }
|
||||
public DateTime? Date { get; set; } = DateTime.Now;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class SpecializationSearch
|
||||
{
|
||||
public string? Name { get; set; } = string.Empty;
|
||||
public string? Code { get; set; } = string.Empty;
|
||||
public string? Search { get; set; } = string.Empty;
|
||||
public int? FacultId{ get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using DataModels.Enums;
|
||||
|
||||
namespace Controller.Contracts
|
||||
{
|
||||
public class StudentSearch
|
||||
{
|
||||
public string? LastName { get; set; } = string.Empty;
|
||||
public string? Name { get; set; } = string.Empty;
|
||||
public string? Patronymic { get; set; } = string.Empty;
|
||||
public int? SpecializationId { get; set; }
|
||||
public int? GroupId { get; set; }
|
||||
public Status? Status { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user